Datei: NDODLL/Query/NDOQuery.cs

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