Datei: NDODLL/Query/NDOQuery.cs

Last 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
New Commit (f4949d4)
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 { nameof( DeleteDirectly) } ( ) . 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 { nameof( DeleteDirectly ) } ( ) . " ) ;
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