Datei: NDODLL/Query/NDOQuery.cs

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