Datei: NDODLL/Query/NDOQuery.cs

Last Commit (7586755)
1 using System;
2 using System.Collections;
3 using System.Collections.Generic;
4 using System.Linq;
5 using System.Text;
6 using NDOql;
7 using NDOql.Expressions;
8 using NDO.Mapping;
9 using System.Data;
10 using NDOInterfaces;
11 using System.Text.RegularExpressions;
12 using NDO.Linq;
13 using LE=System.Linq.Expressions;
14 using NDO.Configuration;
15 using NDO.SqlPersistenceHandling;
16 using System.IO;
17 using System.Security.Cryptography;
18 namespace NDO.Query
19 {
20 ····internal class FieldMarker
21 ····{
22 ········public static string Instance
23 ········{
24 ············get { return "##FIELDS##"; }
25 ········}
26 ····}
27
28 ····/// <summary>
29 ····/// This class manages string based queries in NDOql and Sql
30 ····/// </summary>
31 ····/// <typeparam name="T"></typeparam>
32 ····public class NDOQuery<T> : IQuery
33 ····{
34 ········private PersistenceManager pm;
35 ········private string queryExpression;
36 ········private bool hollowResults;
37 ········private QueryLanguage queryLanguage;
38 ········private Type resultType = typeof( T );
39 ········private bool allowSubclasses = true;
40 ········private OqlExpression expressionTree;
41 ········private List<QueryOrder> orderings = new List<QueryOrder>();
42 ········private List<QueryContextsEntry> queryContextsForTypes = null;
43 ········private Mappings mappings;
44 ········private List<object> parameters = new List<object>();
45 ········private List<string> prefetches = new List<string>();
46 ········private int skip;
47 ········private int take;
48
49 ········internal bool UseQueryCache => this.pm.UseQueryCache;
50 ········internal Dictionary<string, object> QueryCache => this.pm.QueryCache;
51
52
53 ········/// <summary>
54 ········/// Constructs a NDOQuery object
55 ········/// </summary>
56 ········/// <param name="pm">The PersistenceManager used to manage the objects.</param>
57 ········/// <remarks>
58 ········/// The query will return all available objects of the given type and it's subtypes.
59 ········/// The objects won't be hollow.
60 ········/// </remarks>
61 ········public NDOQuery( PersistenceManager pm )
62 ············: this( pm, null )
63 ········{
64 ········}
65
66 ········/// <summary>
67 ········/// Constructs a NDOQuery object
68 ········/// </summary>
69 ········/// <param name="pm">The PersistenceManager used to manage the objects.</param>
70 ········/// <param name="queryExpression">A NDOql expression.</param>
71 ········/// <remarks>
72 ········/// The query will return all available objects of the given
73 ········/// type and it's subtypes where the expression applies.
74 ········/// The objects won't be hollow.
75 ········/// </remarks>
76 ········public NDOQuery( PersistenceManager pm, string queryExpression )
77 ············: this( pm, queryExpression, false )
78 ········{
79 ········}
80
81 ········/// <summary>
82 ········/// Constructs a NDOQuery object
83 ········/// </summary>
84 ········/// <param name="pm">The PersistenceManager used to manage the objects.</param>
85 ········/// <param name="queryExpression">A NDOql expression.</param>
86 ········/// <param name="hollowResults">Determines, if the objects should be fetched in the hollow state (true) or filled with data (false).</param>
87 ········public NDOQuery( PersistenceManager pm, string queryExpression, bool hollowResults )
88 ············: this( pm, queryExpression, hollowResults, QueryLanguage.NDOql )
89 ········{
90 ········}
91
92 ········/// <summary>
93 ········/// Constructs a NDOQuery object
94 ········/// </summary>
95 ········/// <param name="pm">The PersistenceManager used to manage the objects.</param>
96 ········/// <param name="queryExpression">A NDOql or SQL expression.</param>
97 ········/// <param name="hollowResults">Determines, if the objects should be fetched in the hollow state (true) or filled with data (false).</param>
98 ········/// <param name="queryLanguage">Determines, if the query is a SQL pass-through query or a NDOql expression.</param>
99 ········public NDOQuery( PersistenceManager pm, string queryExpression, bool hollowResults, QueryLanguage queryLanguage )
100 ········{
101 ············this.pm = pm;
102 ············if (pm == null)
103 ················throw new ArgumentException( "Parameter is null", "pm" );
104 ············this.mappings = pm.mappings;
105 ············this.queryExpression = queryExpression;
106 ············this.hollowResults = hollowResults;
107 ············this.queryLanguage = queryLanguage;
108 ········}
109
110 ········/// <summary>
111 ········/// Execute an aggregation query.
112 ········/// </summary>
113 ········/// <typeparam name="K"></typeparam>
114 ········/// <param name="keySelector">A Lambda expression which represents an accessor property of the field which shoule be aggregated.</param>
115 ········/// <param name="aggregateType">One of the <see cref="AggregateType">AggregateType</see> enum members.</param>
116 ········/// <returns>A single value, which represents the aggregate</returns>
117 ········/// <remarks>Before using this function, make shure, that your database product supports the aggregate function, as defined in aggregateType.
118 ········/// Polymorphy: StDev and Var only return the aggregate for the given class. All others return the aggregate for all subclasses.
119 ········/// Transactions: Please note, that the aggregate functions always work against the database.
120 ········/// Unsaved changes in your objects are not recognized.</remarks>
121 ········public object ExecuteAggregate<K>( LE.Expression<Func<T, K>> keySelector, AggregateType aggregateType )
122 ········{
123 ············ExpressionTreeTransformer transformer =
124 ················new ExpressionTreeTransformer( keySelector );
125 ············string field = transformer.Transform();
126 ············return ExecuteAggregate( field, aggregateType );
127 ········}
128
129 ········/// <summary>
130 ········/// Execute an aggregation query.
131 ········/// </summary>
132 ········/// <param name="aggregateType">One of the <see cref="AggregateType">AggregateType</see> enum members.</param>
133 ········/// <returns>A single value, which represents the aggregate</returns>
134 ········/// <remarks>Before using this function, make sure, that your database product supports the aggregate function, as defined in aggregateType.
135 ········/// Polymorphy: StDev and Var only return the aggregate for the given class. All others return the aggregate for all subclasses.
136 ········/// Transactions: Please note, that the aggregate functions always work against the database.
137 ········/// Unsaved changes in your objects are not recognized.</remarks>
138 ········public object ExecuteAggregate( AggregateType aggregateType )
139 ········{
140 ············return ExecuteAggregate( "*", aggregateType );
141 ········}
142
143 ········/// <summary>
144 ········/// Execute an aggregation query.
145 ········/// </summary>
146 ········/// <param name="field">The field, which should be aggregated</param>
147 ········/// <param name="aggregateType">One of the <see cref="AggregateType">AggregateType</see> enum members.</param>
148 ········/// <returns>A single value, which represents the aggregate</returns>
149 ········/// <remarks>Before using this function, make sure, that your database product supports the aggregate function, as defined in aggregateType.
150 ········/// Polymorphy: StDev and Var only return the aggregate for the given class. All others return the aggregate for all subclasses.
151 ········/// Transactions: Please note, that the aggregate functions always work against the database.
152 ········/// Unsaved changes in your objects are not recognized.</remarks>
153 ········public object ExecuteAggregate( string field, AggregateType aggregateType )
154 ········{
155 ············if (aggregateType == AggregateType.StDev || aggregateType == AggregateType.Var)
156 ················this.allowSubclasses = false;
157 ············if (this.queryContextsForTypes == null)
158 ················GenerateQueryContexts();
159
160 ············object[] partResults = new object[this.queryContextsForTypes.Count];
161
162 ············AggregateFunction func = new AggregateFunction( aggregateType );
163
164 ············int i = 0;
165 ············foreach (var queryContextsEntry in this.queryContextsForTypes)
166 ············{
167 ················partResults[i++] = ExecuteAggregateQuery( queryContextsEntry, field, aggregateType );
168 ············}
169 ············this.pm.CheckEndTransaction( !this.pm.DeferredMode && this.pm.TransactionMode == TransactionMode.Optimistic );
170 ············return func.ComputeResult( partResults );
171 ········}
172
173 ········/// <summary>
174 ········/// Deletes records directly without caring for composite relations.
175 ········/// </summary>
176 ········/// <remarks>Only use this method if your class does not use composite relations and you are sure that this will not be the case in the future either. If you are unsure about this, you better use PersistenceManager.Delete().</remarks>
177 ········public void DeleteDirectly()
178 ········{
179 ············string sql = GetDirectDeleteQuery();
180
181 ············using (IPersistenceHandler persistenceHandler = this.pm.PersistenceHandlerManager.GetPersistenceHandler( this.resultType ))
182 ············{
183 ················persistenceHandler.VerboseMode = this.pm.VerboseMode;
184 ················persistenceHandler.LogAdapter = this.pm.LogAdapter;
185 ················this.pm.CheckTransaction( persistenceHandler, this.resultType );
186 ················persistenceHandler.ExecuteBatch( new string[] { sql }, this.parameters );
187 ················this.pm.CheckEndTransaction( true );
188 ············}
189
190 ············//using (var handler = this.pm.GetSqlPassThroughHandler())
191 ············//{
192 ············//····handler.Execute( sql, false, this.parameters.ToArray() );
193 ············//····pm.Save(); // Commit
194 ············//}
195 ········}
196
197 ········/// <summary>
198 ········/// Computes the query used for DeleteDirectly().
199 ········/// </summary>
200 ········/// <returns></returns>
201 ········public string GetDirectDeleteQuery()
202 ········{
203 ············var queryString = GeneratedQuery;
204 ············if (queryString.IndexOf( "UNION" ) > -1)
205 ················throw new QueryException( 10052, $"This kind of query doesn't support {nameof(DeleteDirectly)}(). Please use pm.Delete(yourQueryResults)." );
206
207 ············var p = queryString.IndexOf( "FROM" );
208 ············if (p == -1)
209 ················throw new QueryException( 10051, $"Can't find 'FROM' in generated query. It seems, as if your query doesn't support {nameof( DeleteDirectly )}()." );
210
211 ············var sql = "DELETE " + queryString.Substring( p );
212
213 ············return sql;
214 ········}
215
216 ········private string GetSha()
217 ········{
218 ············MemoryStream ms = new MemoryStream();
219 ············var sw = new StreamWriter(ms);
220 ············
 
 
 
221 ············foreach (var item in parameters)
222 ············{
223 ················if (item is byte[] bytes)
224 ················{
225 ····················foreach (var b in bytes)
226 ····················{
227 ························sw.Write( b );
228 ····················}
229 ····················sw.Write( '|' );
230 ················}
231 ················else
232 ················{
233 ····················sw.Write( item );
234 ····················sw.Write( '|' );
235 ················}
236 ············}
237
238 ············foreach (var item in orderings)
239 ············{
240 ················sw.Write( item.FieldName );
241 ················sw.Write( item.IsAscending );
242 ················sw.Write( '|' );
243 ············}
244
245 ············foreach (var item in prefetches)
246 ············{
247 ················sw.Write( item );
248 ················sw.Write( '|' );
249 ············}
250
251 ············sw.Write( $"skip{this.skip}|" );
252 ············sw.Write( $"take{this.take}" );
253
254 ············sw.Flush();
255 ············ms.Seek( 0L, SeekOrigin.Begin );
256 ············var sr = new StreamReader(ms);
257 ············var q = (this.queryExpression ?? "") + '|' + sr.ReadToEnd();
258 ············var qbytes = Encoding.UTF8.GetBytes(q);
259 ············var sha = SHA256.Create();
260 ············var hash = sha.ComputeHash( qbytes );
261 ············return Convert.ToBase64String( hash );
262 ········}
263
264 ········/// <summary>
265 ········/// Executes the query and returns a list of result objects.
266 ········/// </summary>
267 ········/// <returns></returns>
268 ········public List<T> Execute()
269 ········{
270 ············string sha = null;
271 ············if (UseQueryCache)
272 ············{
273 ················sha = GetSha();
274 ················if (QueryCache.ContainsKey( sha ))
275 ················{
276 ····················if (this.pm.VerboseMode)
277 ························this.pm.LogAdapter.Info( "Getting results from QueryCache" );
278 ····················return (List<T>) QueryCache[sha];
279 ················}
280 ············}
281
282 ············var result = GetResultList();
283
284 ············if (UseQueryCache)
285 ············{
286 ················QueryCache[sha] = result;
287 ············}
288
289 ············return result;
290 ········}
291
292 ········/// <summary>
293 ········/// Retrieves the SQL code of a NDOql Query.
294 ········/// </summary>
295 ········public string GeneratedQuery
296 ········{
297 ············get
298 ············{
299 ················if (this.queryLanguage == QueryLanguage.Sql)
300 ················{
301 ····················return this.queryExpression;
302 ················}
303
304 ················if (this.queryContextsForTypes == null)
305 ····················GenerateQueryContexts();
306
307 ················PrepareParameters();
308 ················IQueryGenerator queryGenerator = ConfigContainer.Resolve<IQueryGenerator>();
309 ················return queryGenerator.GenerateQueryStringForAllTypes( this.queryContextsForTypes, this.expressionTree, this.hollowResults, this.orderings, this.skip, this.take );
310 ············}
311 ········}
312
313 ········private List<T> GetResultList()
314 ········{
315 ············List<T> result = new List<T>();
316
317 ············if (this.queryContextsForTypes == null)
318 ················GenerateQueryContexts();
319
320 ············// this.pm.CheckTransaction happens in ExecuteOrderedSubQuery or in ExecuteSubQuery
321
322 ············if (this.queryContextsForTypes.Count > 1 && this.orderings.Count > 0)
323 ············{
324 ················result = QueryOrderedPolymorphicList();
325 ············}
326 ············else
327 ············{
328 ················foreach (var queryContextsEntry in this.queryContextsForTypes)
329 ················{
330 ····················foreach (var item in ExecuteSubQuery( queryContextsEntry ))
331 ····················{
332 ························result.Add( item );
333 ····················}
334 ················}
335 ············}
336
337 ············//GetPrefetches( result );
338 ············this.pm.CheckEndTransaction( !this.pm.DeferredMode && this.pm.TransactionMode == TransactionMode.Optimistic );
339 ············if (!this.pm.GetClass( resultType ).Provider.SupportsFetchLimit)
340 ············{
341 ················List<T> fetchResult = new List<T>();
342 ················for (int i = this.skip; i < Math.Min( result.Count, i + this.take ); i++)
343 ····················fetchResult.Add( result[i] );
344 ············}
345 ············return result;
346 ········}
347
348 #if MaskOutPrefetches
349 ········Type GetPrefetchResultType( Type t, string relation )
350 ········{
351 ············string[] parts = relation.Split( '.' );
352 ············Class cl = pm.GetClass( t );
353 ············Relation rel = cl.FindRelation( parts[0] );
354 ············if (parts.Length == 1)
355 ················return rel.ReferencedType;
356 ············else
357 ················return GetPrefetchResultType( rel.ReferencedType,
358 ····················relation.Substring( relation.IndexOf( '.' ) + 1 ) );
359 ········}
360
361 ········void GetPrefetches( List<T> parents )
362 ········{
363 ············var queryGenerator = this.ConfigContainer.Resolve<IQueryGenerator>();
364 ············if (parents.Count == 0)
365 ················return;
366
367 ············var mustUseInClause = false;
368
369 ············if (this.expressionTree != null)
370 ················mustUseInClause = this.expressionTree.GetAll( n => n.Operator == "IN" ).Any();
371
372 ············foreach (string prefetch in this.Prefetches)
373 ············{
374 ················Type t = GetPrefetchResultType( this.resultType, prefetch );
375 ················using (IPersistenceHandler persistenceHandler = this.pm.PersistenceHandlerManager.GetPersistenceHandler( t ))
376 ················{
377 ····················persistenceHandler.VerboseMode = this.pm.VerboseMode;
378 ····················persistenceHandler.LogAdapter = this.pm.LogAdapter;
379 ····················DataTable table = null;
380 ····················foreach (var queryContextsEntry in this.queryContextsForTypes)
381 ····················{
382 ························Type parentType = queryContextsEntry.Type;
383 ························var parentCls = mappings.FindClass( parentType );
384 ························var isMultiple = parentCls.Oid.OidColumns.Count > 1;
385 ························var selectedParents = parents.Where( p => p.GetType() == parentType ).Select(p=>(IPersistenceCapable)p).ToList();
386 ························if (selectedParents.Count == 0)
387 ····························continue;
388
389 ························string generatedQuery;
390 ························if (!mustUseInClause && ( parents.Count > 100 || isMultiple ) && this.skip == 0 && this.take == 0)
391 ························{
392 ····························generatedQuery = queryGenerator.GenerateQueryString( queryContextsEntry, this.expressionTree, false, true, new List<QueryOrder>(), 0, 0, prefetch );
393 ························}
394 ························else
395 ························{
396 ····························if (isMultiple)
397 ································throw new QueryException( 10050, "Can't process a prefetch with skip, take & multiple oid columns" );
398 ····························generatedQuery = queryGenerator.GeneratePrefetchQuery( parentType, selectedParents, prefetch );
399 ························}
400 #warning Prefetches: Überprüfen, ob das in der normalen Transaktion mitläuft
401 ························//····················this.pm.CheckTransaction( persistenceHandler, t );
402
403 ························this.pm.CheckTransaction( persistenceHandler, t );
404
405 ························table = persistenceHandler.PerformQuery( generatedQuery, this.parameters, this.pm.DataSet );
406
407 ························var result = pm.DataTableToIList( t, table.Rows, false );
408 ························MatchRelations( parents, result, prefetch );
409 ····················}
410 ················}
411 ············}
412 ········}
413
414 ········void MatchRelations( List<T> parents, IList childs, string relationName )
415 ········{
416 ············if (parents.Count == 0)
417 ················return;
418 ············if (childs.Count == 0)
419 ················return;
420 ············Class cl = pm.GetClass( resultType );
421 ············Relation r = cl.FindRelation( relationName );
422 ············RelationCollector rc = new RelationCollector( cl );
423 ············rc.CollectRelations();
424 ············string[] parentColumns = rc.ForeignKeyColumns.ToArray();
425 ············cl = pm.GetClass( r.ReferencedType );
426 ············rc = new RelationCollector( cl );
427 ············rc.CollectRelations();
428 ············string[] childColumns = rc.ForeignKeyColumns.ToArray();
429 ············// Used to determine, if the relation has been collected
430 ············string testColumnName = r.ForeignKeyColumns.First().Name;
431 ············if (r.Multiplicity == RelationMultiplicity.Element && parentColumns.Contains( testColumnName ))
432 ············{
433 ················foreach (IPersistenceCapable parent in parents)
434 ················{
435 ····················foreach (IPersistenceCapable child in childs)
436 ····················{
437 ························if (parent.NDOLoadState.LostRowsEqualsOid( child.NDOObjectId.Id, r ))
438 ····························mappings.SetRelationField( parent, r.FieldName, child );
439 ························//KeyValuePair kvp = ((KeyValueList)parent.NDOLoadState.LostRowInfo)[r.ForeignKeyColumnName];
440 ························//if (kvp.Value.Equals(child.NDOObjectId.Id.Value))
441 ························//····mappings.SetRelationField(parent, r.FieldName, child);
442 ····················}
443 ················}
444 ············}
445 ············else if (r.Multiplicity == RelationMultiplicity.List && childColumns.Contains( testColumnName ))
446 ············{
447 ················foreach (IPersistenceCapable parent in parents)
448 ················{
449 ····················IList container = mappings.GetRelationContainer( parent, r );
450 ····················foreach (IPersistenceCapable child in childs)
451 ····················{
452 ························if (child.NDOLoadState.LostRowsEqualsOid( parent.NDOObjectId.Id, r ))
453 ····························container.Add( child );
454 ····················}
455
456 ····················parent.NDOSetLoadState( r.Ordinal, true );
457 ················}
458 ············}
459 ········}
460 #endif
461
462 ········private object ExecuteAggregateQuery( QueryContextsEntry queryContextsEntry, string field, AggregateType aggregateType )
463 ········{
464 ············Type t = queryContextsEntry.Type;
465 ············IQueryGenerator queryGenerator = ConfigContainer.Resolve<IQueryGenerator>();
466 ············string generatedQuery = queryGenerator.GenerateAggregateQueryString( field, queryContextsEntry, this.expressionTree, this.queryContextsForTypes.Count > 1, aggregateType );
467
468 ············using (IPersistenceHandler persistenceHandler = this.pm.PersistenceHandlerManager.GetPersistenceHandler( t ))
469 ············{
470 ················persistenceHandler.VerboseMode = this.pm.VerboseMode;
471 ················persistenceHandler.LogAdapter = this.pm.LogAdapter;
472 ················this.pm.CheckTransaction( persistenceHandler, t );
473
474 ················// Note, that we can't execute all subQueries in one batch, because
475 ················// the subqueries could be executed against different connections.
476 ················// TODO: This could be optimized, if we made clear whether the involved tables
477 ················// can be reached with the same connection.
478 ················var l = persistenceHandler.ExecuteBatch( new string[] { generatedQuery }, this.parameters );
479 ················if (l.Count == 0)
480 ····················return null;
481
482 ················return ( l[0] )["AggrResult"];
483 ············}
484 ········}
485
486 ········private List<T> ExecuteSqlQuery()
487 ········{
488 ············Type t = this.resultType;
489 ············using (IPersistenceHandler persistenceHandler = this.pm.PersistenceHandlerManager.GetPersistenceHandler( t ))
490 ············{
491 ················persistenceHandler.VerboseMode = this.pm.VerboseMode;
492 ················persistenceHandler.LogAdapter = this.pm.LogAdapter;
493 ················this.pm.CheckTransaction( persistenceHandler, t );
494 ················DataTable table = persistenceHandler.PerformQuery( this.queryExpression, this.parameters, this.pm.DataSet );
495 ················return (List<T>) pm.DataTableToIList( t, table.Rows, this.hollowResults );
496 ············}
497 ········}
498
499 ········private bool PrepareParameters()
500 ········{
501 ············if (this.expressionTree == null)
502 ················return false;
503 ············var expressions = this.expressionTree.GetAll( e => e is ParameterExpression ).Select( e => (ParameterExpression)e ).ToList();
504 ············if (expressions.Count == 0)
505 ················return true;
506 ············if (expressions[0].ParameterValue != null)
507 ················return false;
508 ············foreach (ParameterExpression item in expressions)
509 ············{
510 ················item.ParameterValue = this.parameters[item.Ordinal];
511 ············}
512
513 ············return true;
514 ········}
515
516 ········private void WriteBackParameters()
517 ········{
518 ············if (this.expressionTree == null)
519 ················return;
520 ············var expressions = this.expressionTree.GetAll( e => e is ParameterExpression ).Select( e => (ParameterExpression)e ).ToList();
521 ············if (expressions.Count == 0)
522 ················return;
523 ············var count = (from e in expressions select e.Ordinal).Max();
524 ············this.parameters = new List<object>( count );
525 ············for (int i = 0; i < count + 1; i++)
526 ················this.parameters.Add( null );
527 ············foreach (ParameterExpression item in expressions)
528 ············{
529 ················this.parameters[item.Ordinal] = item.ParameterValue;
530 ············}
531 ········}
532
533 ········private IList ExecuteSubQuery( Type t, QueryContextsEntry queryContextsEntry )
534 ········{
535 ············IQueryGenerator queryGenerator = ConfigContainer.Resolve<IQueryGenerator>();
536 ············bool hasBeenPrepared = PrepareParameters();
537 ············string generatedQuery;
538
539 ············if (this.queryLanguage == QueryLanguage.NDOql)
540 ················generatedQuery = queryGenerator.GenerateQueryString( queryContextsEntry, this.expressionTree, this.hollowResults, this.queryContextsForTypes.Count > 1, this.orderings, this.skip, this.take );
541 ············else
542 ················generatedQuery = (string)this.expressionTree.Value;
543
544 ············if (hasBeenPrepared)
545 ············{
546 ················WriteBackParameters();
547 ············}
548
549 ············using (IPersistenceHandler persistenceHandler = this.pm.PersistenceHandlerManager.GetPersistenceHandler( t ))
550 ············{
551 ················persistenceHandler.VerboseMode = this.pm.VerboseMode;
552 ················persistenceHandler.LogAdapter = this.pm.LogAdapter;
553 ················this.pm.CheckTransaction( persistenceHandler, t );
554
555 ················DataTable table = persistenceHandler.PerformQuery( generatedQuery, this.parameters, this.pm.DataSet );
556 ················return pm.DataTableToIList( t, table.Rows, this.hollowResults );
557 ············}
558 ········}
559
560 ········private IEnumerable<T> ExecuteSubQuery( QueryContextsEntry queryContextsEntry )
561 ········{
562 ············var subResult = ExecuteSubQuery( queryContextsEntry.Type, queryContextsEntry );
563 ············foreach (var item in subResult)
564 ············{
565 ················yield return (T)item;
566 ············}
567 ········}
568
569 ········private List<T> QueryOrderedPolymorphicList()
570 ········{
571 ············List<ObjectRowPair<T>> rowPairList = new List<ObjectRowPair<T>>();
572 ············foreach (var queryContextsEntry in this.queryContextsForTypes)
573 ················rowPairList.AddRange( ExecuteOrderedSubQuery( queryContextsEntry ) );
574 ············rowPairList.Sort();
575 ············List<T> result = new List<T>( rowPairList.Count );
576 ············foreach (ObjectRowPair<T> orp in rowPairList)
577 ················result.Add( (T)orp.Obj );
578 ············return result;
579 ········}
580
581 ········private List<ObjectRowPair<T>> ExecuteOrderedSubQuery( QueryContextsEntry queryContextsEntry )
582 ········{
583 ············Type t = queryContextsEntry.Type;
584 ············Class resultSubClass = this.pm.GetClass( t );
585 ············DataTable comparismTable = new DataTable( "ComparismTable" );
586 ············foreach (QueryOrder order in this.orderings)
587 ············{
588 ················DataColumn col = comparismTable.Columns.Add( order.FieldName );
589 ················if (order.IsAscending)
590 ····················col.AutoIncrementStep = 1;
591 ················else
592 ····················col.AutoIncrementStep = -1;
593 ············}
594
595 ············DataTable table = null;
596 ············using (IPersistenceHandler persistenceHandler = this.pm.PersistenceHandlerManager.GetPersistenceHandler( t ))
597 ············{
598 ················persistenceHandler.VerboseMode = this.pm.VerboseMode;
599 ················persistenceHandler.LogAdapter = this.pm.LogAdapter;
600 ················this.pm.CheckTransaction( persistenceHandler, t );
601
602 ················bool hasBeenPrepared = PrepareParameters();
603 ················IQueryGenerator queryGenerator = ConfigContainer.Resolve<IQueryGenerator>();
604 ················string generatedQuery = queryGenerator.GenerateQueryString( queryContextsEntry, this.expressionTree, this.hollowResults, this.queryContextsForTypes.Count > 1, this.orderings, this.skip, this.take );
605
606 ················if (hasBeenPrepared)
607 ················{
608 ····················WriteBackParameters();
609 ················}
610
611 ················table = persistenceHandler.PerformQuery( generatedQuery, this.parameters, this.pm.DataSet );
612 ············}
613
614 ············DataRow[] rows = table.Select();
615 ············var objects = pm.DataTableToIList( t, rows, this.hollowResults );
616 ············List<ObjectRowPair<T>> result = new List<ObjectRowPair<T>>( objects.Count );
617 ············int i = 0;
618 ············IProvider provider = mappings.GetProvider( t );
619 ············foreach (T obj in objects)
620 ············{
621 ················DataRow row = rows[i++];
622 ················DataRow newRow = comparismTable.NewRow();
623 ················foreach (QueryOrder order in this.orderings)
624 ················{
625 ····················string newColumnName = order.FieldName;
626 ····················if (!comparismTable.Columns.Contains( newColumnName ))
627 ························throw new InternalException( 558, "Query.cs - Column not found." );
628 ····················string oldColumnName = resultSubClass.FindField( order.FieldName ).Column.Name;
629 ····················if (!table.Columns.Contains( oldColumnName ))
630 ························throw new InternalException( 561, "Query.cs - Column not found." );
631 ····················newRow[newColumnName] = row[oldColumnName];
632 ················}
633 ················result.Add( new ObjectRowPair<T>( obj, newRow ) );
634 ············}
635
636 ············return result;
637 ········}
638
639
640 ········/// <summary>
641 ········/// Executes the query and returns a single object.
642 ········/// </summary>
643 ········/// <returns>The fetched object or null, if the object wasn't found. If the query has more than one result, the first of the results will be returned.</returns>
644 ········public T ExecuteSingle()
645 ········{
646 ············return ExecuteSingle( false );
647 ········}
648
649 ········/// <summary>
650 ········/// Executes the query and returns a single object.
651 ········/// </summary>
652 ········/// <param name="throwIfResultCountIsWrong"></param>
653 ········/// <returns>The fetched object or null, if the object wasn't found and throwIfResultCountIsWrong is false.</returns>
654 ········/// <remarks>
655 ········/// If throwIfResultCountIsWrong is true, an Exception will be throwed, if the result count isn't exactly 1.
656 ········/// If throwIfResultCountIsWrong is false and the query has more than one result, the first of the results will be returned.
657 ········/// </remarks>
658 ········public T ExecuteSingle( bool throwIfResultCountIsWrong )
659 ········{
660 ············var resultList = Execute();
661 ············int count = resultList.Count;
662 ············if (count == 1 || (!throwIfResultCountIsWrong && count > 0))
663 ············{
664 ················return resultList[0];
665 ············}
666 ············else
667 ············{
668 ················if (throwIfResultCountIsWrong)
669 ····················throw new QueryException( 10002, count.ToString() + " result objects in ExecuteSingle call" );
670 ················else
671 ····················return default( T );
672 ············}
673 ········}
674
675 ········/// <summary>
676 ········/// Constructs the subqueries necessary to fetch all objects of a
677 ········/// class and its subclasses.
678 ········/// </summary>
679 ········/// <remarks>
680 ········/// The function isn't actually recursive. The subclasses have been
681 ········/// recursively collected in NDOMapping.
682 ········/// </remarks>
683 ········private void CreateQueryContextsForTypes()
684 ········{
685 ············Dictionary<string, Type> usedTables = new Dictionary<string, Type>();
686 ············if (!resultType.IsAbstract)
687 ············{
688 ················Class cl = pm.GetClass( this.resultType );
689 ················usedTables.Add( cl.TableName, cl.SystemType );
690 ············}
691
692 ············if (this.allowSubclasses)
693 ············{
694 ················// Check if subclasses are mapped to the same table.
695 ················// Always fetch for the base class in the table
696 ················foreach (Class cl in pm.GetClass( resultType ).Subclasses)
697 ················{
698 ····················string tn = cl.TableName;
699 ····················if (usedTables.ContainsKey( tn ))
700 ····················{
701 ························Type t = (Type)usedTables[tn];
702 ························if (t.IsSubclassOf( cl.SystemType ))
703 ························{
704 ····························usedTables.Remove( tn );
705 ····························usedTables.Add( tn, cl.SystemType );
706 ························}
707 ························break;
708 ····················}
709 ····················usedTables.Add( tn, cl.SystemType );
710 ················}
711 ············}
712
713 ············var contextGenerator = ConfigContainer.Resolve<RelationContextGenerator>( null, new ParameterOverride( this.pm.mappings ) );
714 ············this.queryContextsForTypes = new List<QueryContextsEntry>();
715 ············// usedTables now contains all assignable classes of our result type
716 ············foreach (var de in usedTables)
717 ············{
718 ················Type t2 = (Type)de.Value;
719 ················// Now we have to iterate through all mutations of
720 ················// polymorphic relations, used in the filter expression
721 ················var queryContexts = contextGenerator.GetContexts( this.pm.GetClass( t2 ), this.expressionTree );
722 ················this.queryContextsForTypes.Add( new QueryContextsEntry() { Type = t2, QueryContexts = queryContexts } );
723 ············}
724 ········}
725
726 ········private void GenerateQueryContexts()
727 ········{
728 ············//if ((int) this.queryLanguage == OldQuery.LoadRelations)
729 ············//····LoadRelatedTables();
730 ············//else if (this.queryLanguage == Language.NDOql)
731 ············if (this.queryLanguage == QueryLanguage.Sql)
732 ············{
733 ················var selectList = new SqlColumnListGenerator( pm.NDOMapping.FindClass( typeof( T ) ) ).SelectList;
734 ················var sql = Regex.Replace( this.queryExpression, @"SELECT\s+\*", "SELECT " + selectList );
735 ················this.expressionTree = new RawIdentifierExpression( sql, 0, 0 );
736 ············}
737 ············else
738 ············{
739 ················NDOql.OqlParser parser = new NDOql.OqlParser();
740 ················var parsedTree = parser.Parse( this.queryExpression );
741 ················if (parsedTree != null)
742 ················{
743 ····················// The root expression tree might get exchanged.
744 ····················// To make this possible we make it the child of a dummy expression.
745 ····················this.expressionTree = new OqlExpression( 0, 0 );
746 ····················this.expressionTree.Add( parsedTree );
747 ····················((IManageExpression)parsedTree).SetParent( this.expressionTree );
748 ················}
749 ············}
750
751 ············CreateQueryContextsForTypes();
752 ········}
753
754 ········INDOContainer ConfigContainer
755 ········{
756 ············get { return this.pm.ConfigContainer; }
757 ········}
758
759 ········/// <summary>
760 ········/// Gets the query parameters
761 ········/// </summary>
762 ········public ICollection<object> Parameters
763 ········{
764 ············get { return this.parameters; }
765 ········}
766
767 ········/// <summary>
768 ········/// Indicates whether subclasses are allowed in the query
769 ········/// </summary>
770 ········public bool AllowSubclasses
771 ········{
772 ············get { return this.allowSubclasses; }
773 ············set { this.allowSubclasses = value; }
774 ········}
775
776 ········/// <summary>
777 ········/// Gets or sets the relations to be queried in the query
778 ········/// </summary>
779 ········public IEnumerable<string> Prefetches
780 ········{
781 ············get { return this.prefetches; }
782 ············set { this.prefetches = value.ToList(); }
783 ········}
784
785 ········/// <summary>
786 ········/// Gets or sets orderings
787 ········/// </summary>
788 ········public List<QueryOrder> Orderings
789 ········{
790 ············get { return this.orderings; }
791 ············set { this.orderings = value.ToList(); }
792 ········}
793
794 ········/// <summary>
795 ········/// Gets or sets the amount of elements to be skipped in an ordered query
796 ········/// </summary>
797 ········/// <remarks>This is for paging support.</remarks>
798 ········public int Skip
799 ········{
800 ············get { return this.skip; }
801 ············set { this.skip = value; }
802 ········}
803
804 ········/// <summary>
805 ········/// Gets or sets the amount of elements to be taken in an ordered query
806 ········/// </summary>
807 ········/// <remarks>This is for paging support.</remarks>
808 ········public int Take
809 ········{
810 ············get { return this.take; }
811 ············set { this.take = value; }
812 ········}
813
814 ········/// <summary>
815 ········/// Adds a prefetch to the query
816 ········/// </summary>
817 ········/// <param name="s"></param>
818 ········[Obsolete("Prefetches are not yet implemented, but will be in future.")]
819 ········public void AddPrefetch( string s )
820 ········{
821 ············this.prefetches.Add( s );
822 ········}
823
824 ········/// <summary>
825 ········/// Removes a prefetch from the query
826 ········/// </summary>
827 ········/// <param name="s"></param>
828 ········public void RemovePrefetch( string s )
829 ········{
830 ············if (this.prefetches.Contains( s ))
831 ················this.prefetches.Remove( s );
832 ········}
833
834 #region IQuery Interface
835
836 ········System.Collections.IList IQuery.Execute()
837 ········{
838 ············return this.Execute();
839 ········}
840
841 ········IPersistenceCapable IQuery.ExecuteSingle()
842 ········{
843 ············return (IPersistenceCapable)this.ExecuteSingle( false );
844 ········}
845
846 ········IPersistenceCapable IQuery.ExecuteSingle( bool throwIfResultCountIsWrong )
847 ········{
848 ············return (IPersistenceCapable)this.ExecuteSingle( throwIfResultCountIsWrong );
849 ········}
850
851 ········ICollection<object> IQuery.Parameters => this.parameters;
852 ········ICollection<QueryOrder> IQuery.Orderings => this.orderings;
853 ········object IQuery.ExecuteAggregate( string field, AggregateType aggregateType ) => ExecuteAggregate( field, aggregateType );
854 ········bool IQuery.AllowSubclasses { get => this.allowSubclasses; set => this.allowSubclasses = value; }
855 ········int IQuery.Skip { get => this.skip; set => this.skip = value; }
856 ········int IQuery.Take { get => this.take; set => this.take = value; }
857 ········string IQuery.GeneratedQuery { get { return this.GeneratedQuery; } }
858
859 #endregion
860
861 ····}
862 }
863
New Commit (f20a957)
1 using System;
2 using System.Collections;
3 using System.Collections.Generic;
4 using System.Linq;
5 using System.Text;
6 using NDOql;
7 using NDOql.Expressions;
8 using NDO.Mapping;
9 using System.Data;
10 using NDOInterfaces;
11 using System.Text.RegularExpressions;
12 using NDO.Linq;
13 using LE=System.Linq.Expressions;
14 using NDO.Configuration;
15 using NDO.SqlPersistenceHandling;
16 using System.IO;
17 using System.Security.Cryptography;
18 namespace NDO.Query
19 {
20 ····internal class FieldMarker
21 ····{
22 ········public static string Instance
23 ········{
24 ············get { return "##FIELDS##"; }
25 ········}
26 ····}
27
28 ····/// <summary>
29 ····/// This class manages string based queries in NDOql and Sql
30 ····/// </summary>
31 ····/// <typeparam name="T"></typeparam>
32 ····public class NDOQuery<T> : IQuery
33 ····{
34 ········private PersistenceManager pm;
35 ········private string queryExpression;
36 ········private bool hollowResults;
37 ········private QueryLanguage queryLanguage;
38 ········private Type resultType = typeof( T );
39 ········private bool allowSubclasses = true;
40 ········private OqlExpression expressionTree;
41 ········private List<QueryOrder> orderings = new List<QueryOrder>();
42 ········private List<QueryContextsEntry> queryContextsForTypes = null;
43 ········private Mappings mappings;
44 ········private List<object> parameters = new List<object>();
45 ········private List<string> prefetches = new List<string>();
46 ········private int skip;
47 ········private int take;
48
49 ········internal bool UseQueryCache => this.pm.UseQueryCache;
50 ········internal Dictionary<string, object> QueryCache => this.pm.QueryCache;
51
52
53 ········/// <summary>
54 ········/// Constructs a NDOQuery object
55 ········/// </summary>
56 ········/// <param name="pm">The PersistenceManager used to manage the objects.</param>
57 ········/// <remarks>
58 ········/// The query will return all available objects of the given type and it's subtypes.
59 ········/// The objects won't be hollow.
60 ········/// </remarks>
61 ········public NDOQuery( PersistenceManager pm )
62 ············: this( pm, null )
63 ········{
64 ········}
65
66 ········/// <summary>
67 ········/// Constructs a NDOQuery object
68 ········/// </summary>
69 ········/// <param name="pm">The PersistenceManager used to manage the objects.</param>
70 ········/// <param name="queryExpression">A NDOql expression.</param>
71 ········/// <remarks>
72 ········/// The query will return all available objects of the given
73 ········/// type and it's subtypes where the expression applies.
74 ········/// The objects won't be hollow.
75 ········/// </remarks>
76 ········public NDOQuery( PersistenceManager pm, string queryExpression )
77 ············: this( pm, queryExpression, false )
78 ········{
79 ········}
80
81 ········/// <summary>
82 ········/// Constructs a NDOQuery object
83 ········/// </summary>
84 ········/// <param name="pm">The PersistenceManager used to manage the objects.</param>
85 ········/// <param name="queryExpression">A NDOql expression.</param>
86 ········/// <param name="hollowResults">Determines, if the objects should be fetched in the hollow state (true) or filled with data (false).</param>
87 ········public NDOQuery( PersistenceManager pm, string queryExpression, bool hollowResults )
88 ············: this( pm, queryExpression, hollowResults, QueryLanguage.NDOql )
89 ········{
90 ········}
91
92 ········/// <summary>
93 ········/// Constructs a NDOQuery object
94 ········/// </summary>
95 ········/// <param name="pm">The PersistenceManager used to manage the objects.</param>
96 ········/// <param name="queryExpression">A NDOql or SQL expression.</param>
97 ········/// <param name="hollowResults">Determines, if the objects should be fetched in the hollow state (true) or filled with data (false).</param>
98 ········/// <param name="queryLanguage">Determines, if the query is a SQL pass-through query or a NDOql expression.</param>
99 ········public NDOQuery( PersistenceManager pm, string queryExpression, bool hollowResults, QueryLanguage queryLanguage )
100 ········{
101 ············this.pm = pm;
102 ············if (pm == null)
103 ················throw new ArgumentException( "Parameter is null", "pm" );
104 ············this.mappings = pm.mappings;
105 ············this.queryExpression = queryExpression;
106 ············this.hollowResults = hollowResults;
107 ············this.queryLanguage = queryLanguage;
108 ········}
109
110 ········/// <summary>
111 ········/// Execute an aggregation query.
112 ········/// </summary>
113 ········/// <typeparam name="K"></typeparam>
114 ········/// <param name="keySelector">A Lambda expression which represents an accessor property of the field which shoule be aggregated.</param>
115 ········/// <param name="aggregateType">One of the <see cref="AggregateType">AggregateType</see> enum members.</param>
116 ········/// <returns>A single value, which represents the aggregate</returns>
117 ········/// <remarks>Before using this function, make shure, that your database product supports the aggregate function, as defined in aggregateType.
118 ········/// Polymorphy: StDev and Var only return the aggregate for the given class. All others return the aggregate for all subclasses.
119 ········/// Transactions: Please note, that the aggregate functions always work against the database.
120 ········/// Unsaved changes in your objects are not recognized.</remarks>
121 ········public object ExecuteAggregate<K>( LE.Expression<Func<T, K>> keySelector, AggregateType aggregateType )
122 ········{
123 ············ExpressionTreeTransformer transformer =
124 ················new ExpressionTreeTransformer( keySelector );
125 ············string field = transformer.Transform();
126 ············return ExecuteAggregate( field, aggregateType );
127 ········}
128
129 ········/// <summary>
130 ········/// Execute an aggregation query.
131 ········/// </summary>
132 ········/// <param name="aggregateType">One of the <see cref="AggregateType">AggregateType</see> enum members.</param>
133 ········/// <returns>A single value, which represents the aggregate</returns>
134 ········/// <remarks>Before using this function, make sure, that your database product supports the aggregate function, as defined in aggregateType.
135 ········/// Polymorphy: StDev and Var only return the aggregate for the given class. All others return the aggregate for all subclasses.
136 ········/// Transactions: Please note, that the aggregate functions always work against the database.
137 ········/// Unsaved changes in your objects are not recognized.</remarks>
138 ········public object ExecuteAggregate( AggregateType aggregateType )
139 ········{
140 ············return ExecuteAggregate( "*", aggregateType );
141 ········}
142
143 ········/// <summary>
144 ········/// Execute an aggregation query.
145 ········/// </summary>
146 ········/// <param name="field">The field, which should be aggregated</param>
147 ········/// <param name="aggregateType">One of the <see cref="AggregateType">AggregateType</see> enum members.</param>
148 ········/// <returns>A single value, which represents the aggregate</returns>
149 ········/// <remarks>Before using this function, make sure, that your database product supports the aggregate function, as defined in aggregateType.
150 ········/// Polymorphy: StDev and Var only return the aggregate for the given class. All others return the aggregate for all subclasses.
151 ········/// Transactions: Please note, that the aggregate functions always work against the database.
152 ········/// Unsaved changes in your objects are not recognized.</remarks>
153 ········public object ExecuteAggregate( string field, AggregateType aggregateType )
154 ········{
155 ············if (aggregateType == AggregateType.StDev || aggregateType == AggregateType.Var)
156 ················this.allowSubclasses = false;
157 ············if (this.queryContextsForTypes == null)
158 ················GenerateQueryContexts();
159
160 ············object[] partResults = new object[this.queryContextsForTypes.Count];
161
162 ············AggregateFunction func = new AggregateFunction( aggregateType );
163
164 ············int i = 0;
165 ············foreach (var queryContextsEntry in this.queryContextsForTypes)
166 ············{
167 ················partResults[i++] = ExecuteAggregateQuery( queryContextsEntry, field, aggregateType );
168 ············}
169 ············this.pm.CheckEndTransaction( !this.pm.DeferredMode && this.pm.TransactionMode == TransactionMode.Optimistic );
170 ············return func.ComputeResult( partResults );
171 ········}
172
173 ········/// <summary>
174 ········/// Deletes records directly without caring for composite relations.
175 ········/// </summary>
176 ········/// <remarks>Only use this method if your class does not use composite relations and you are sure that this will not be the case in the future either. If you are unsure about this, you better use PersistenceManager.Delete().</remarks>
177 ········public void DeleteDirectly()
178 ········{
179 ············string sql = GetDirectDeleteQuery();
180
181 ············using (IPersistenceHandler persistenceHandler = this.pm.PersistenceHandlerManager.GetPersistenceHandler( this.resultType ))
182 ············{
183 ················persistenceHandler.VerboseMode = this.pm.VerboseMode;
184 ················persistenceHandler.LogAdapter = this.pm.LogAdapter;
185 ················this.pm.CheckTransaction( persistenceHandler, this.resultType );
186 ················persistenceHandler.ExecuteBatch( new string[] { sql }, this.parameters );
187 ················this.pm.CheckEndTransaction( true );
188 ············}
189
190 ············//using (var handler = this.pm.GetSqlPassThroughHandler())
191 ············//{
192 ············//····handler.Execute( sql, false, this.parameters.ToArray() );
193 ············//····pm.Save(); // Commit
194 ············//}
195 ········}
196
197 ········/// <summary>
198 ········/// Computes the query used for DeleteDirectly().
199 ········/// </summary>
200 ········/// <returns></returns>
201 ········public string GetDirectDeleteQuery()
202 ········{
203 ············var queryString = GeneratedQuery;
204 ············if (queryString.IndexOf( "UNION" ) > -1)
205 ················throw new QueryException( 10052, $"This kind of query doesn't support {nameof(DeleteDirectly)}(). Please use pm.Delete(yourQueryResults)." );
206
207 ············var p = queryString.IndexOf( "FROM" );
208 ············if (p == -1)
209 ················throw new QueryException( 10051, $"Can't find 'FROM' in generated query. It seems, as if your query doesn't support {nameof( DeleteDirectly )}()." );
210
211 ············var sql = "DELETE " + queryString.Substring( p );
212
213 ············return sql;
214 ········}
215
216 ········private string GetSha()
217 ········{
218 ············MemoryStream ms = new MemoryStream();
219 ············var sw = new StreamWriter(ms);
220
221 ············sw.Write( typeof( T ).FullName );
222 ············sw.Write( '|' );
223 ············
224 ············foreach (var item in parameters)
225 ············{
226 ················if (item is byte[] bytes)
227 ················{
228 ····················foreach (var b in bytes)
229 ····················{
230 ························sw.Write( b );
231 ····················}
232 ····················sw.Write( '|' );
233 ················}
234 ················else
235 ················{
236 ····················sw.Write( item );
237 ····················sw.Write( '|' );
238 ················}
239 ············}
240
241 ············foreach (var item in orderings)
242 ············{
243 ················sw.Write( item.FieldName );
244 ················sw.Write( item.IsAscending );
245 ················sw.Write( '|' );
246 ············}
247
248 ············foreach (var item in prefetches)
249 ············{
250 ················sw.Write( item );
251 ················sw.Write( '|' );
252 ············}
253
254 ············sw.Write( $"skip{this.skip}|" );
255 ············sw.Write( $"take{this.take}" );
256
257 ············sw.Flush();
258 ············ms.Seek( 0L, SeekOrigin.Begin );
259 ············var sr = new StreamReader(ms);
260 ············var q = (this.queryExpression ?? "") + '|' + sr.ReadToEnd();
261 ············var qbytes = Encoding.UTF8.GetBytes(q);
262 ············var sha = SHA256.Create();
263 ············var hash = sha.ComputeHash( qbytes );
264 ············return Convert.ToBase64String( hash );
265 ········}
266
267 ········/// <summary>
268 ········/// Executes the query and returns a list of result objects.
269 ········/// </summary>
270 ········/// <returns></returns>
271 ········public List<T> Execute()
272 ········{
273 ············string sha = null;
274 ············if (UseQueryCache)
275 ············{
276 ················sha = GetSha();
277 ················if (QueryCache.ContainsKey( sha ))
278 ················{
279 ····················if (this.pm.VerboseMode)
280 ························this.pm.LogAdapter.Info( "Getting results from QueryCache" );
281 ····················return (List<T>) QueryCache[sha];
282 ················}
283 ············}
284
285 ············var result = GetResultList();
286
287 ············if (UseQueryCache)
288 ············{
289 ················QueryCache[sha] = result;
290 ············}
291
292 ············return result;
293 ········}
294
295 ········/// <summary>
296 ········/// Retrieves the SQL code of a NDOql Query.
297 ········/// </summary>
298 ········public string GeneratedQuery
299 ········{
300 ············get
301 ············{
302 ················if (this.queryLanguage == QueryLanguage.Sql)
303 ················{
304 ····················return this.queryExpression;
305 ················}
306
307 ················if (this.queryContextsForTypes == null)
308 ····················GenerateQueryContexts();
309
310 ················PrepareParameters();
311 ················IQueryGenerator queryGenerator = ConfigContainer.Resolve<IQueryGenerator>();
312 ················return queryGenerator.GenerateQueryStringForAllTypes( this.queryContextsForTypes, this.expressionTree, this.hollowResults, this.orderings, this.skip, this.take );
313 ············}
314 ········}
315
316 ········private List<T> GetResultList()
317 ········{
318 ············List<T> result = new List<T>();
319
320 ············if (this.queryContextsForTypes == null)
321 ················GenerateQueryContexts();
322
323 ············// this.pm.CheckTransaction happens in ExecuteOrderedSubQuery or in ExecuteSubQuery
324
325 ············if (this.queryContextsForTypes.Count > 1 && this.orderings.Count > 0)
326 ············{
327 ················result = QueryOrderedPolymorphicList();
328 ············}
329 ············else
330 ············{
331 ················foreach (var queryContextsEntry in this.queryContextsForTypes)
332 ················{
333 ····················foreach (var item in ExecuteSubQuery( queryContextsEntry ))
334 ····················{
335 ························result.Add( item );
336 ····················}
337 ················}
338 ············}
339
340 ············//GetPrefetches( result );
341 ············this.pm.CheckEndTransaction( !this.pm.DeferredMode && this.pm.TransactionMode == TransactionMode.Optimistic );
342 ············if (!this.pm.GetClass( resultType ).Provider.SupportsFetchLimit)
343 ············{
344 ················List<T> fetchResult = new List<T>();
345 ················for (int i = this.skip; i < Math.Min( result.Count, i + this.take ); i++)
346 ····················fetchResult.Add( result[i] );
347 ············}
348 ············return result;
349 ········}
350
351 #if MaskOutPrefetches
352 ········Type GetPrefetchResultType( Type t, string relation )
353 ········{
354 ············string[] parts = relation.Split( '.' );
355 ············Class cl = pm.GetClass( t );
356 ············Relation rel = cl.FindRelation( parts[0] );
357 ············if (parts.Length == 1)
358 ················return rel.ReferencedType;
359 ············else
360 ················return GetPrefetchResultType( rel.ReferencedType,
361 ····················relation.Substring( relation.IndexOf( '.' ) + 1 ) );
362 ········}
363
364 ········void GetPrefetches( List<T> parents )
365 ········{
366 ············var queryGenerator = this.ConfigContainer.Resolve<IQueryGenerator>();
367 ············if (parents.Count == 0)
368 ················return;
369
370 ············var mustUseInClause = false;
371
372 ············if (this.expressionTree != null)
373 ················mustUseInClause = this.expressionTree.GetAll( n => n.Operator == "IN" ).Any();
374
375 ············foreach (string prefetch in this.Prefetches)
376 ············{
377 ················Type t = GetPrefetchResultType( this.resultType, prefetch );
378 ················using (IPersistenceHandler persistenceHandler = this.pm.PersistenceHandlerManager.GetPersistenceHandler( t ))
379 ················{
380 ····················persistenceHandler.VerboseMode = this.pm.VerboseMode;
381 ····················persistenceHandler.LogAdapter = this.pm.LogAdapter;
382 ····················DataTable table = null;
383 ····················foreach (var queryContextsEntry in this.queryContextsForTypes)
384 ····················{
385 ························Type parentType = queryContextsEntry.Type;
386 ························var parentCls = mappings.FindClass( parentType );
387 ························var isMultiple = parentCls.Oid.OidColumns.Count > 1;
388 ························var selectedParents = parents.Where( p => p.GetType() == parentType ).Select(p=>(IPersistenceCapable)p).ToList();
389 ························if (selectedParents.Count == 0)
390 ····························continue;
391
392 ························string generatedQuery;
393 ························if (!mustUseInClause && ( parents.Count > 100 || isMultiple ) && this.skip == 0 && this.take == 0)
394 ························{
395 ····························generatedQuery = queryGenerator.GenerateQueryString( queryContextsEntry, this.expressionTree, false, true, new List<QueryOrder>(), 0, 0, prefetch );
396 ························}
397 ························else
398 ························{
399 ····························if (isMultiple)
400 ································throw new QueryException( 10050, "Can't process a prefetch with skip, take & multiple oid columns" );
401 ····························generatedQuery = queryGenerator.GeneratePrefetchQuery( parentType, selectedParents, prefetch );
402 ························}
403 #warning Prefetches: Überprüfen, ob das in der normalen Transaktion mitläuft
404 ························//····················this.pm.CheckTransaction( persistenceHandler, t );
405
406 ························this.pm.CheckTransaction( persistenceHandler, t );
407
408 ························table = persistenceHandler.PerformQuery( generatedQuery, this.parameters, this.pm.DataSet );
409
410 ························var result = pm.DataTableToIList( t, table.Rows, false );
411 ························MatchRelations( parents, result, prefetch );
412 ····················}
413 ················}
414 ············}
415 ········}
416
417 ········void MatchRelations( List<T> parents, IList childs, string relationName )
418 ········{
419 ············if (parents.Count == 0)
420 ················return;
421 ············if (childs.Count == 0)
422 ················return;
423 ············Class cl = pm.GetClass( resultType );
424 ············Relation r = cl.FindRelation( relationName );
425 ············RelationCollector rc = new RelationCollector( cl );
426 ············rc.CollectRelations();
427 ············string[] parentColumns = rc.ForeignKeyColumns.ToArray();
428 ············cl = pm.GetClass( r.ReferencedType );
429 ············rc = new RelationCollector( cl );
430 ············rc.CollectRelations();
431 ············string[] childColumns = rc.ForeignKeyColumns.ToArray();
432 ············// Used to determine, if the relation has been collected
433 ············string testColumnName = r.ForeignKeyColumns.First().Name;
434 ············if (r.Multiplicity == RelationMultiplicity.Element && parentColumns.Contains( testColumnName ))
435 ············{
436 ················foreach (IPersistenceCapable parent in parents)
437 ················{
438 ····················foreach (IPersistenceCapable child in childs)
439 ····················{
440 ························if (parent.NDOLoadState.LostRowsEqualsOid( child.NDOObjectId.Id, r ))
441 ····························mappings.SetRelationField( parent, r.FieldName, child );
442 ························//KeyValuePair kvp = ((KeyValueList)parent.NDOLoadState.LostRowInfo)[r.ForeignKeyColumnName];
443 ························//if (kvp.Value.Equals(child.NDOObjectId.Id.Value))
444 ························//····mappings.SetRelationField(parent, r.FieldName, child);
445 ····················}
446 ················}
447 ············}
448 ············else if (r.Multiplicity == RelationMultiplicity.List && childColumns.Contains( testColumnName ))
449 ············{
450 ················foreach (IPersistenceCapable parent in parents)
451 ················{
452 ····················IList container = mappings.GetRelationContainer( parent, r );
453 ····················foreach (IPersistenceCapable child in childs)
454 ····················{
455 ························if (child.NDOLoadState.LostRowsEqualsOid( parent.NDOObjectId.Id, r ))
456 ····························container.Add( child );
457 ····················}
458
459 ····················parent.NDOSetLoadState( r.Ordinal, true );
460 ················}
461 ············}
462 ········}
463 #endif
464
465 ········private object ExecuteAggregateQuery( QueryContextsEntry queryContextsEntry, string field, AggregateType aggregateType )
466 ········{
467 ············Type t = queryContextsEntry.Type;
468 ············IQueryGenerator queryGenerator = ConfigContainer.Resolve<IQueryGenerator>();
469 ············string generatedQuery = queryGenerator.GenerateAggregateQueryString( field, queryContextsEntry, this.expressionTree, this.queryContextsForTypes.Count > 1, aggregateType );
470
471 ············using (IPersistenceHandler persistenceHandler = this.pm.PersistenceHandlerManager.GetPersistenceHandler( t ))
472 ············{
473 ················persistenceHandler.VerboseMode = this.pm.VerboseMode;
474 ················persistenceHandler.LogAdapter = this.pm.LogAdapter;
475 ················this.pm.CheckTransaction( persistenceHandler, t );
476
477 ················// Note, that we can't execute all subQueries in one batch, because
478 ················// the subqueries could be executed against different connections.
479 ················// TODO: This could be optimized, if we made clear whether the involved tables
480 ················// can be reached with the same connection.
481 ················var l = persistenceHandler.ExecuteBatch( new string[] { generatedQuery }, this.parameters );
482 ················if (l.Count == 0)
483 ····················return null;
484
485 ················return ( l[0] )["AggrResult"];
486 ············}
487 ········}
488
489 ········private List<T> ExecuteSqlQuery()
490 ········{
491 ············Type t = this.resultType;
492 ············using (IPersistenceHandler persistenceHandler = this.pm.PersistenceHandlerManager.GetPersistenceHandler( t ))
493 ············{
494 ················persistenceHandler.VerboseMode = this.pm.VerboseMode;
495 ················persistenceHandler.LogAdapter = this.pm.LogAdapter;
496 ················this.pm.CheckTransaction( persistenceHandler, t );
497 ················DataTable table = persistenceHandler.PerformQuery( this.queryExpression, this.parameters, this.pm.DataSet );
498 ················return (List<T>) pm.DataTableToIList( t, table.Rows, this.hollowResults );
499 ············}
500 ········}
501
502 ········private bool PrepareParameters()
503 ········{
504 ············if (this.expressionTree == null)
505 ················return false;
506 ············var expressions = this.expressionTree.GetAll( e => e is ParameterExpression ).Select( e => (ParameterExpression)e ).ToList();
507 ············if (expressions.Count == 0)
508 ················return true;
509 ············if (expressions[0].ParameterValue != null)
510 ················return false;
511 ············foreach (ParameterExpression item in expressions)
512 ············{
513 ················item.ParameterValue = this.parameters[item.Ordinal];
514 ············}
515
516 ············return true;
517 ········}
518
519 ········private void WriteBackParameters()
520 ········{
521 ············if (this.expressionTree == null)
522 ················return;
523 ············var expressions = this.expressionTree.GetAll( e => e is ParameterExpression ).Select( e => (ParameterExpression)e ).ToList();
524 ············if (expressions.Count == 0)
525 ················return;
526 ············var count = (from e in expressions select e.Ordinal).Max();
527 ············this.parameters = new List<object>( count );
528 ············for (int i = 0; i < count + 1; i++)
529 ················this.parameters.Add( null );
530 ············foreach (ParameterExpression item in expressions)
531 ············{
532 ················this.parameters[item.Ordinal] = item.ParameterValue;
533 ············}
534 ········}
535
536 ········private IList ExecuteSubQuery( Type t, QueryContextsEntry queryContextsEntry )
537 ········{
538 ············IQueryGenerator queryGenerator = ConfigContainer.Resolve<IQueryGenerator>();
539 ············bool hasBeenPrepared = PrepareParameters();
540 ············string generatedQuery;
541
542 ············if (this.queryLanguage == QueryLanguage.NDOql)
543 ················generatedQuery = queryGenerator.GenerateQueryString( queryContextsEntry, this.expressionTree, this.hollowResults, this.queryContextsForTypes.Count > 1, this.orderings, this.skip, this.take );
544 ············else
545 ················generatedQuery = (string)this.expressionTree.Value;
546
547 ············if (hasBeenPrepared)
548 ············{
549 ················WriteBackParameters();
550 ············}
551
552 ············using (IPersistenceHandler persistenceHandler = this.pm.PersistenceHandlerManager.GetPersistenceHandler( t ))
553 ············{
554 ················persistenceHandler.VerboseMode = this.pm.VerboseMode;
555 ················persistenceHandler.LogAdapter = this.pm.LogAdapter;
556 ················this.pm.CheckTransaction( persistenceHandler, t );
557
558 ················DataTable table = persistenceHandler.PerformQuery( generatedQuery, this.parameters, this.pm.DataSet );
559 ················return pm.DataTableToIList( t, table.Rows, this.hollowResults );
560 ············}
561 ········}
562
563 ········private IEnumerable<T> ExecuteSubQuery( QueryContextsEntry queryContextsEntry )
564 ········{
565 ············var subResult = ExecuteSubQuery( queryContextsEntry.Type, queryContextsEntry );
566 ············foreach (var item in subResult)
567 ············{
568 ················yield return (T)item;
569 ············}
570 ········}
571
572 ········private List<T> QueryOrderedPolymorphicList()
573 ········{
574 ············List<ObjectRowPair<T>> rowPairList = new List<ObjectRowPair<T>>();
575 ············foreach (var queryContextsEntry in this.queryContextsForTypes)
576 ················rowPairList.AddRange( ExecuteOrderedSubQuery( queryContextsEntry ) );
577 ············rowPairList.Sort();
578 ············List<T> result = new List<T>( rowPairList.Count );
579 ············foreach (ObjectRowPair<T> orp in rowPairList)
580 ················result.Add( (T)orp.Obj );
581 ············return result;
582 ········}
583
584 ········private List<ObjectRowPair<T>> ExecuteOrderedSubQuery( QueryContextsEntry queryContextsEntry )
585 ········{
586 ············Type t = queryContextsEntry.Type;
587 ············Class resultSubClass = this.pm.GetClass( t );
588 ············DataTable comparismTable = new DataTable( "ComparismTable" );
589 ············foreach (QueryOrder order in this.orderings)
590 ············{
591 ················DataColumn col = comparismTable.Columns.Add( order.FieldName );
592 ················if (order.IsAscending)
593 ····················col.AutoIncrementStep = 1;
594 ················else
595 ····················col.AutoIncrementStep = -1;
596 ············}
597
598 ············DataTable table = null;
599 ············using (IPersistenceHandler persistenceHandler = this.pm.PersistenceHandlerManager.GetPersistenceHandler( t ))
600 ············{
601 ················persistenceHandler.VerboseMode = this.pm.VerboseMode;
602 ················persistenceHandler.LogAdapter = this.pm.LogAdapter;
603 ················this.pm.CheckTransaction( persistenceHandler, t );
604
605 ················bool hasBeenPrepared = PrepareParameters();
606 ················IQueryGenerator queryGenerator = ConfigContainer.Resolve<IQueryGenerator>();
607 ················string generatedQuery = queryGenerator.GenerateQueryString( queryContextsEntry, this.expressionTree, this.hollowResults, this.queryContextsForTypes.Count > 1, this.orderings, this.skip, this.take );
608
609 ················if (hasBeenPrepared)
610 ················{
611 ····················WriteBackParameters();
612 ················}
613
614 ················table = persistenceHandler.PerformQuery( generatedQuery, this.parameters, this.pm.DataSet );
615 ············}
616
617 ············DataRow[] rows = table.Select();
618 ············var objects = pm.DataTableToIList( t, rows, this.hollowResults );
619 ············List<ObjectRowPair<T>> result = new List<ObjectRowPair<T>>( objects.Count );
620 ············int i = 0;
621 ············IProvider provider = mappings.GetProvider( t );
622 ············foreach (T obj in objects)
623 ············{
624 ················DataRow row = rows[i++];
625 ················DataRow newRow = comparismTable.NewRow();
626 ················foreach (QueryOrder order in this.orderings)
627 ················{
628 ····················string newColumnName = order.FieldName;
629 ····················if (!comparismTable.Columns.Contains( newColumnName ))
630 ························throw new InternalException( 558, "Query.cs - Column not found." );
631 ····················string oldColumnName = resultSubClass.FindField( order.FieldName ).Column.Name;
632 ····················if (!table.Columns.Contains( oldColumnName ))
633 ························throw new InternalException( 561, "Query.cs - Column not found." );
634 ····················newRow[newColumnName] = row[oldColumnName];
635 ················}
636 ················result.Add( new ObjectRowPair<T>( obj, newRow ) );
637 ············}
638
639 ············return result;
640 ········}
641
642
643 ········/// <summary>
644 ········/// Executes the query and returns a single object.
645 ········/// </summary>
646 ········/// <returns>The fetched object or null, if the object wasn't found. If the query has more than one result, the first of the results will be returned.</returns>
647 ········public T ExecuteSingle()
648 ········{
649 ············return ExecuteSingle( false );
650 ········}
651
652 ········/// <summary>
653 ········/// Executes the query and returns a single object.
654 ········/// </summary>
655 ········/// <param name="throwIfResultCountIsWrong"></param>
656 ········/// <returns>The fetched object or null, if the object wasn't found and throwIfResultCountIsWrong is false.</returns>
657 ········/// <remarks>
658 ········/// If throwIfResultCountIsWrong is true, an Exception will be throwed, if the result count isn't exactly 1.
659 ········/// If throwIfResultCountIsWrong is false and the query has more than one result, the first of the results will be returned.
660 ········/// </remarks>
661 ········public T ExecuteSingle( bool throwIfResultCountIsWrong )
662 ········{
663 ············var resultList = Execute();
664 ············int count = resultList.Count;
665 ············if (count == 1 || (!throwIfResultCountIsWrong && count > 0))
666 ············{
667 ················return resultList[0];
668 ············}
669 ············else
670 ············{
671 ················if (throwIfResultCountIsWrong)
672 ····················throw new QueryException( 10002, count.ToString() + " result objects in ExecuteSingle call" );
673 ················else
674 ····················return default( T );
675 ············}
676 ········}
677
678 ········/// <summary>
679 ········/// Constructs the subqueries necessary to fetch all objects of a
680 ········/// class and its subclasses.
681 ········/// </summary>
682 ········/// <remarks>
683 ········/// The function isn't actually recursive. The subclasses have been
684 ········/// recursively collected in NDOMapping.
685 ········/// </remarks>
686 ········private void CreateQueryContextsForTypes()
687 ········{
688 ············Dictionary<string, Type> usedTables = new Dictionary<string, Type>();
689 ············if (!resultType.IsAbstract)
690 ············{
691 ················Class cl = pm.GetClass( this.resultType );
692 ················usedTables.Add( cl.TableName, cl.SystemType );
693 ············}
694
695 ············if (this.allowSubclasses)
696 ············{
697 ················// Check if subclasses are mapped to the same table.
698 ················// Always fetch for the base class in the table
699 ················foreach (Class cl in pm.GetClass( resultType ).Subclasses)
700 ················{
701 ····················string tn = cl.TableName;
702 ····················if (usedTables.ContainsKey( tn ))
703 ····················{
704 ························Type t = (Type)usedTables[tn];
705 ························if (t.IsSubclassOf( cl.SystemType ))
706 ························{
707 ····························usedTables.Remove( tn );
708 ····························usedTables.Add( tn, cl.SystemType );
709 ························}
710 ························break;
711 ····················}
712 ····················usedTables.Add( tn, cl.SystemType );
713 ················}
714 ············}
715
716 ············var contextGenerator = ConfigContainer.Resolve<RelationContextGenerator>( null, new ParameterOverride( this.pm.mappings ) );
717 ············this.queryContextsForTypes = new List<QueryContextsEntry>();
718 ············// usedTables now contains all assignable classes of our result type
719 ············foreach (var de in usedTables)
720 ············{
721 ················Type t2 = (Type)de.Value;
722 ················// Now we have to iterate through all mutations of
723 ················// polymorphic relations, used in the filter expression
724 ················var queryContexts = contextGenerator.GetContexts( this.pm.GetClass( t2 ), this.expressionTree );
725 ················this.queryContextsForTypes.Add( new QueryContextsEntry() { Type = t2, QueryContexts = queryContexts } );
726 ············}
727 ········}
728
729 ········private void GenerateQueryContexts()
730 ········{
731 ············//if ((int) this.queryLanguage == OldQuery.LoadRelations)
732 ············//····LoadRelatedTables();
733 ············//else if (this.queryLanguage == Language.NDOql)
734 ············if (this.queryLanguage == QueryLanguage.Sql)
735 ············{
736 ················var selectList = new SqlColumnListGenerator( pm.NDOMapping.FindClass( typeof( T ) ) ).SelectList;
737 ················var sql = Regex.Replace( this.queryExpression, @"SELECT\s+\*", "SELECT " + selectList );
738 ················this.expressionTree = new RawIdentifierExpression( sql, 0, 0 );
739 ············}
740 ············else
741 ············{
742 ················NDOql.OqlParser parser = new NDOql.OqlParser();
743 ················var parsedTree = parser.Parse( this.queryExpression );
744 ················if (parsedTree != null)
745 ················{
746 ····················// The root expression tree might get exchanged.
747 ····················// To make this possible we make it the child of a dummy expression.
748 ····················this.expressionTree = new OqlExpression( 0, 0 );
749 ····················this.expressionTree.Add( parsedTree );
750 ····················((IManageExpression)parsedTree).SetParent( this.expressionTree );
751 ················}
752 ············}
753
754 ············CreateQueryContextsForTypes();
755 ········}
756
757 ········INDOContainer ConfigContainer
758 ········{
759 ············get { return this.pm.ConfigContainer; }
760 ········}
761
762 ········/// <summary>
763 ········/// Gets the query parameters
764 ········/// </summary>
765 ········public ICollection<object> Parameters
766 ········{
767 ············get { return this.parameters; }
768 ········}
769
770 ········/// <summary>
771 ········/// Indicates whether subclasses are allowed in the query
772 ········/// </summary>
773 ········public bool AllowSubclasses
774 ········{
775 ············get { return this.allowSubclasses; }
776 ············set { this.allowSubclasses = value; }
777 ········}
778
779 ········/// <summary>
780 ········/// Gets or sets the relations to be queried in the query
781 ········/// </summary>
782 ········public IEnumerable<string> Prefetches
783 ········{
784 ············get { return this.prefetches; }
785 ············set { this.prefetches = value.ToList(); }
786 ········}
787
788 ········/// <summary>
789 ········/// Gets or sets orderings
790 ········/// </summary>
791 ········public List<QueryOrder> Orderings
792 ········{
793 ············get { return this.orderings; }
794 ············set { this.orderings = value.ToList(); }
795 ········}
796
797 ········/// <summary>
798 ········/// Gets or sets the amount of elements to be skipped in an ordered query
799 ········/// </summary>
800 ········/// <remarks>This is for paging support.</remarks>
801 ········public int Skip
802 ········{
803 ············get { return this.skip; }
804 ············set { this.skip = value; }
805 ········}
806
807 ········/// <summary>
808 ········/// Gets or sets the amount of elements to be taken in an ordered query
809 ········/// </summary>
810 ········/// <remarks>This is for paging support.</remarks>
811 ········public int Take
812 ········{
813 ············get { return this.take; }
814 ············set { this.take = value; }
815 ········}
816
817 ········/// <summary>
818 ········/// Adds a prefetch to the query
819 ········/// </summary>
820 ········/// <param name="s"></param>
821 ········[Obsolete("Prefetches are not yet implemented, but will be in future.")]
822 ········public void AddPrefetch( string s )
823 ········{
824 ············this.prefetches.Add( s );
825 ········}
826
827 ········/// <summary>
828 ········/// Removes a prefetch from the query
829 ········/// </summary>
830 ········/// <param name="s"></param>
831 ········public void RemovePrefetch( string s )
832 ········{
833 ············if (this.prefetches.Contains( s ))
834 ················this.prefetches.Remove( s );
835 ········}
836
837 #region IQuery Interface
838
839 ········System.Collections.IList IQuery.Execute()
840 ········{
841 ············return this.Execute();
842 ········}
843
844 ········IPersistenceCapable IQuery.ExecuteSingle()
845 ········{
846 ············return (IPersistenceCapable)this.ExecuteSingle( false );
847 ········}
848
849 ········IPersistenceCapable IQuery.ExecuteSingle( bool throwIfResultCountIsWrong )
850 ········{
851 ············return (IPersistenceCapable)this.ExecuteSingle( throwIfResultCountIsWrong );
852 ········}
853
854 ········ICollection<object> IQuery.Parameters => this.parameters;
855 ········ICollection<QueryOrder> IQuery.Orderings => this.orderings;
856 ········object IQuery.ExecuteAggregate( string field, AggregateType aggregateType ) => ExecuteAggregate( field, aggregateType );
857 ········bool IQuery.AllowSubclasses { get => this.allowSubclasses; set => this.allowSubclasses = value; }
858 ········int IQuery.Skip { get => this.skip; set => this.skip = value; }
859 ········int IQuery.Take { get => this.take; set => this.take = value; }
860 ········string IQuery.GeneratedQuery { get { return this.GeneratedQuery; } }
861
862 #endregion
863
864 ····}
865 }
866