Datei: NDODLL/Query/NDOQuery.cs

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