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