Mercurial > pub > bltoolkit
comparison Source/Data/Linq/Builder/GroupByBuilder.cs @ 0:f990fcb411a9
Копия текущей версии из github
author | cin |
---|---|
date | Thu, 27 Mar 2014 21:46:09 +0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:f990fcb411a9 |
---|---|
1 using System; | |
2 using System.Collections; | |
3 using System.Collections.Generic; | |
4 using System.Data; | |
5 using System.Linq; | |
6 using System.Linq.Expressions; | |
7 using System.Reflection; | |
8 | |
9 namespace BLToolkit.Data.Linq.Builder | |
10 { | |
11 using BLToolkit.Linq; | |
12 using Data.Sql; | |
13 | |
14 class GroupByBuilder : MethodCallBuilder | |
15 { | |
16 #region Builder Methods | |
17 | |
18 protected override bool CanBuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo) | |
19 { | |
20 if (!methodCall.IsQueryable("GroupBy")) | |
21 return false; | |
22 | |
23 var body = ((LambdaExpression)methodCall.Arguments[1].Unwrap()).Body.Unwrap(); | |
24 | |
25 if (body.NodeType == ExpressionType.MemberInit) | |
26 { | |
27 var mi = (MemberInitExpression)body; | |
28 bool throwExpr; | |
29 | |
30 if (mi.NewExpression.Arguments.Count > 0 || mi.Bindings.Count == 0) | |
31 throwExpr = true; | |
32 else | |
33 throwExpr = mi.Bindings.Any(b => b.BindingType != MemberBindingType.Assignment); | |
34 | |
35 if (throwExpr) | |
36 throw new NotSupportedException(string.Format("Explicit construction of entity type '{0}' in group by is not allowed.", body.Type)); | |
37 } | |
38 | |
39 return (methodCall.Arguments[methodCall.Arguments.Count - 1].Unwrap().NodeType == ExpressionType.Lambda); | |
40 } | |
41 | |
42 protected override IBuildContext BuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo) | |
43 { | |
44 var sequenceExpr = methodCall.Arguments[0]; | |
45 var sequence = builder.BuildSequence(new BuildInfo(buildInfo, sequenceExpr)); | |
46 var groupingType = methodCall.Type.GetGenericArguments()[0]; | |
47 var keySelector = (LambdaExpression)methodCall.Arguments[1].Unwrap(); | |
48 var elementSelector = (LambdaExpression)methodCall.Arguments[2].Unwrap(); | |
49 | |
50 if (methodCall.Arguments[0].NodeType == ExpressionType.Call) | |
51 { | |
52 var call = (MethodCallExpression)methodCall.Arguments[0]; | |
53 | |
54 if (call.Method.Name == "Select") | |
55 { | |
56 var type = ((LambdaExpression)call.Arguments[1].Unwrap()).Body.Type; | |
57 | |
58 if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ExpressionBuilder.GroupSubQuery<,>)) | |
59 { | |
60 sequence = new SubQueryContext(sequence); | |
61 } | |
62 } | |
63 } | |
64 | |
65 var key = new KeyContext(buildInfo.Parent, keySelector, sequence); | |
66 var groupSql = builder.ConvertExpressions(key, keySelector.Body.Unwrap(), ConvertFlags.Key); | |
67 | |
68 if (sequence.SqlQuery.Select.IsDistinct || | |
69 sequence.SqlQuery.GroupBy.Items.Count > 0 || | |
70 groupSql.Any(_ => !(_.Sql is SqlField || _.Sql is SqlQuery.Column))) | |
71 { | |
72 sequence = new SubQueryContext(sequence); | |
73 key = new KeyContext(buildInfo.Parent, keySelector, sequence); | |
74 groupSql = builder.ConvertExpressions(key, keySelector.Body.Unwrap(), ConvertFlags.Key); | |
75 } | |
76 | |
77 //sequence.SqlQuery.GroupBy.Items.Clear(); | |
78 | |
79 foreach (var sql in groupSql) | |
80 sequence.SqlQuery.GroupBy.Expr(sql.Sql); | |
81 | |
82 new QueryVisitor().Visit(sequence.SqlQuery.From, e => | |
83 { | |
84 if (e.ElementType == QueryElementType.JoinedTable) | |
85 { | |
86 var jt = (SqlQuery.JoinedTable)e; | |
87 if (jt.JoinType == SqlQuery.JoinType.Inner) | |
88 jt.IsWeak = false; | |
89 } | |
90 }); | |
91 | |
92 var element = new SelectContext (buildInfo.Parent, elementSelector, sequence/*, key*/); | |
93 var groupBy = new GroupByContext(buildInfo.Parent, sequenceExpr, groupingType, sequence, key, element); | |
94 | |
95 return groupBy; | |
96 } | |
97 | |
98 protected override SequenceConvertInfo Convert( | |
99 ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo, ParameterExpression param) | |
100 { | |
101 return null; | |
102 } | |
103 | |
104 #endregion | |
105 | |
106 #region KeyContext | |
107 | |
108 internal class KeyContext : SelectContext | |
109 { | |
110 public KeyContext(IBuildContext parent, LambdaExpression lambda, params IBuildContext[] sequences) | |
111 : base(parent, lambda, sequences) | |
112 { | |
113 } | |
114 } | |
115 | |
116 #endregion | |
117 | |
118 #region GroupByContext | |
119 | |
120 internal class GroupByContext : SequenceContextBase | |
121 { | |
122 public GroupByContext( | |
123 IBuildContext parent, | |
124 Expression sequenceExpr, | |
125 Type groupingType, | |
126 IBuildContext sequence, | |
127 KeyContext key, | |
128 SelectContext element) | |
129 : base(parent, sequence, null) | |
130 { | |
131 _sequenceExpr = sequenceExpr; | |
132 _key = key; | |
133 _element = element; | |
134 _groupingType = groupingType; | |
135 | |
136 key.Parent = this; | |
137 } | |
138 | |
139 readonly Expression _sequenceExpr; | |
140 readonly KeyContext _key; | |
141 readonly SelectContext _element; | |
142 readonly Type _groupingType; | |
143 | |
144 internal class Grouping<TKey,TElement> : IGrouping<TKey,TElement> | |
145 { | |
146 public Grouping( | |
147 TKey key, | |
148 QueryContext queryContext, | |
149 List<ParameterAccessor> parameters, | |
150 Func<IDataContext,TKey,object[],IQueryable<TElement>> itemReader) | |
151 { | |
152 Key = key; | |
153 | |
154 _queryContext = queryContext; | |
155 _parameters = parameters; | |
156 _itemReader = itemReader; | |
157 | |
158 if (Common.Configuration.Linq.PreloadGroups) | |
159 { | |
160 _items = GetItems(); | |
161 } | |
162 } | |
163 | |
164 private IList<TElement> _items; | |
165 readonly QueryContext _queryContext; | |
166 readonly List<ParameterAccessor> _parameters; | |
167 readonly Func<IDataContext,TKey,object[],IQueryable<TElement>> _itemReader; | |
168 | |
169 public TKey Key { get; private set; } | |
170 | |
171 List<TElement> GetItems() | |
172 { | |
173 var db = _queryContext.GetDataContext(); | |
174 | |
175 try | |
176 { | |
177 var ps = new object[_parameters.Count]; | |
178 | |
179 for (var i = 0; i < ps.Length; i++) | |
180 ps[i] = _parameters[i].Accessor(_queryContext.Expression, _queryContext.CompiledParameters); | |
181 | |
182 return _itemReader(db.DataContextInfo.DataContext, Key, ps).ToList(); | |
183 } | |
184 finally | |
185 { | |
186 _queryContext.ReleaseDataContext(db); | |
187 } | |
188 } | |
189 | |
190 public IEnumerator<TElement> GetEnumerator() | |
191 { | |
192 if (_items == null) | |
193 _items = GetItems(); | |
194 | |
195 return _items.GetEnumerator(); | |
196 } | |
197 | |
198 IEnumerator IEnumerable.GetEnumerator() | |
199 { | |
200 return GetEnumerator(); | |
201 } | |
202 } | |
203 | |
204 interface IGroupByHelper | |
205 { | |
206 Expression GetGrouping(GroupByContext context); | |
207 } | |
208 | |
209 class GroupByHelper<TKey,TElement,TSource> : IGroupByHelper | |
210 { | |
211 public Expression GetGrouping(GroupByContext context) | |
212 { | |
213 var parameters = context.Builder.CurrentSqlParameters | |
214 .Select((p,i) => new { p, i }) | |
215 .ToDictionary(_ => _.p.Expression, _ => _.i); | |
216 var paramArray = Expression.Parameter(typeof(object[]), "ps"); | |
217 | |
218 var groupExpression = context._sequenceExpr.Convert(e => | |
219 { | |
220 int idx; | |
221 | |
222 if (parameters.TryGetValue(e, out idx)) | |
223 { | |
224 return | |
225 Expression.Convert( | |
226 Expression.ArrayIndex(paramArray, Expression.Constant(idx)), | |
227 e.Type); | |
228 } | |
229 | |
230 return e; | |
231 }); | |
232 | |
233 var keyParam = Expression.Parameter(typeof(TKey), "key"); | |
234 | |
235 // ReSharper disable AssignNullToNotNullAttribute | |
236 | |
237 var expr = Expression.Call( | |
238 null, | |
239 ReflectionHelper.Expressor<object>.MethodExpressor(_ => Queryable.Where(null, (Expression<Func<TSource,bool>>)null)), | |
240 groupExpression, | |
241 Expression.Lambda<Func<TSource,bool>>( | |
242 Expression.Equal(context._key.Lambda.Body, keyParam), | |
243 new[] { context._key.Lambda.Parameters[0] })); | |
244 | |
245 expr = Expression.Call( | |
246 null, | |
247 ReflectionHelper.Expressor<object>.MethodExpressor(_ => Queryable.Select(null, (Expression<Func<TSource,TElement>>)null)), | |
248 expr, | |
249 context._element.Lambda); | |
250 | |
251 // ReSharper restore AssignNullToNotNullAttribute | |
252 | |
253 var lambda = Expression.Lambda<Func<IDataContext,TKey,object[],IQueryable<TElement>>>( | |
254 Expression.Convert(expr, typeof(IQueryable<TElement>)), | |
255 Expression.Parameter(typeof(IDataContext), "ctx"), | |
256 keyParam, | |
257 paramArray); | |
258 | |
259 var itemReader = CompiledQuery.Compile(lambda); | |
260 var keyExpr = context._key.BuildExpression(null, 0); | |
261 var keyReader = Expression.Lambda<Func<QueryContext,IDataContext,IDataReader,Expression,object[],TKey>>( | |
262 keyExpr, | |
263 new [] | |
264 { | |
265 ExpressionBuilder.ContextParam, | |
266 ExpressionBuilder.DataContextParam, | |
267 ExpressionBuilder.DataReaderParam, | |
268 ExpressionBuilder.ExpressionParam, | |
269 ExpressionBuilder.ParametersParam, | |
270 }); | |
271 | |
272 return Expression.Call( | |
273 null, | |
274 ReflectionHelper.Expressor<object>.MethodExpressor(_ => GetGrouping(null, null, null, null, null, null, null, null)), | |
275 new Expression[] | |
276 { | |
277 ExpressionBuilder.ContextParam, | |
278 ExpressionBuilder.DataContextParam, | |
279 ExpressionBuilder.DataReaderParam, | |
280 Expression.Constant(context.Builder.CurrentSqlParameters), | |
281 ExpressionBuilder.ExpressionParam, | |
282 ExpressionBuilder.ParametersParam, | |
283 Expression.Constant(keyReader.Compile()), | |
284 Expression.Constant(itemReader), | |
285 }); | |
286 } | |
287 | |
288 static IGrouping<TKey,TElement> GetGrouping( | |
289 QueryContext context, | |
290 IDataContext dataContext, | |
291 IDataReader dataReader, | |
292 List<ParameterAccessor> parameterAccessor, | |
293 Expression expr, | |
294 object[] ps, | |
295 Func<QueryContext,IDataContext,IDataReader,Expression,object[],TKey> keyReader, | |
296 Func<IDataContext,TKey,object[],IQueryable<TElement>> itemReader) | |
297 { | |
298 var key = keyReader(context, dataContext, dataReader, expr, ps); | |
299 return new Grouping<TKey,TElement>(key, context, parameterAccessor, itemReader); | |
300 } | |
301 } | |
302 | |
303 Expression BuildGrouping() | |
304 { | |
305 var gtype = typeof(GroupByHelper<,,>).MakeGenericType( | |
306 _key.Lambda.Body.Type, | |
307 _element.Lambda.Body.Type, | |
308 _key.Lambda.Parameters[0].Type); | |
309 | |
310 var isBlockDisable = Builder.IsBlockDisable; | |
311 | |
312 Builder.IsBlockDisable = true; | |
313 | |
314 var helper = (IGroupByHelper)Activator.CreateInstance(gtype); | |
315 var expr = helper.GetGrouping(this); | |
316 | |
317 Builder.IsBlockDisable = isBlockDisable; | |
318 | |
319 return expr; | |
320 } | |
321 | |
322 public override Expression BuildExpression(Expression expression, int level) | |
323 { | |
324 if (expression == null) | |
325 return BuildGrouping(); | |
326 | |
327 if (level != 0) | |
328 { | |
329 var levelExpression = expression.GetLevelExpression(level); | |
330 | |
331 if (levelExpression.NodeType == ExpressionType.MemberAccess) | |
332 { | |
333 var ma = (MemberExpression)levelExpression; | |
334 | |
335 if (ma.Member.Name == "Key" && ma.Member.DeclaringType == _groupingType) | |
336 { | |
337 return levelExpression == expression ? | |
338 _key.BuildExpression(null, 0) : | |
339 _key.BuildExpression(expression, level + 1); | |
340 } | |
341 } | |
342 } | |
343 | |
344 throw new InvalidOperationException(); | |
345 } | |
346 | |
347 ISqlExpression ConvertEnumerable(MethodCallExpression call) | |
348 { | |
349 if (AggregationBuilder.MethodNames.Contains(call.Method.Name)) | |
350 { | |
351 if (call.Arguments[0].NodeType == ExpressionType.Call) | |
352 { | |
353 var arg = (MethodCallExpression)call.Arguments[0]; | |
354 | |
355 if (arg.Method.Name == "Select") | |
356 { | |
357 if (arg.Arguments[0].NodeType != ExpressionType.Call) | |
358 { | |
359 var l = (LambdaExpression)arg.Arguments[1].Unwrap(); | |
360 var largs = l.Type.GetGenericArguments(); | |
361 | |
362 if (largs.Length == 2) | |
363 { | |
364 var p = _element.Parent; | |
365 var ctx = new ExpressionContext(Parent, _element, l); | |
366 var sql = Builder.ConvertToSql(ctx, l.Body, true); | |
367 | |
368 Builder.ReplaceParent(ctx, p); | |
369 | |
370 return new SqlFunction(call.Type, call.Method.Name, sql); | |
371 } | |
372 } | |
373 } | |
374 } | |
375 } | |
376 | |
377 if (call.Arguments[0].NodeType == ExpressionType.Call) | |
378 { | |
379 var ctx = Builder.GetSubQuery(this, call); | |
380 | |
381 if (Builder.SqlProvider.IsSubQueryColumnSupported) | |
382 return ctx.SqlQuery; | |
383 | |
384 var join = ctx.SqlQuery.CrossApply(); | |
385 | |
386 SqlQuery.From.Tables[0].Joins.Add(join.JoinedTable); | |
387 | |
388 return ctx.SqlQuery.Select.Columns[0]; | |
389 } | |
390 | |
391 var args = new ISqlExpression[call.Arguments.Count - 1]; | |
392 | |
393 if (CountBuilder.MethodNames.Contains(call.Method.Name)) | |
394 { | |
395 if (args.Length > 0) | |
396 throw new InvalidOperationException(); | |
397 | |
398 return SqlFunction.CreateCount(call.Type, SqlQuery); | |
399 } | |
400 | |
401 if (call.Arguments.Count > 1) | |
402 { | |
403 for (var i = 1; i < call.Arguments.Count; i++) | |
404 { | |
405 var ex = call.Arguments[i].Unwrap(); | |
406 | |
407 if (ex is LambdaExpression) | |
408 { | |
409 var l = (LambdaExpression)ex; | |
410 var p = _element.Parent; | |
411 var ctx = new ExpressionContext(Parent, _element, l); | |
412 | |
413 args[i - 1] = Builder.ConvertToSql(ctx, l.Body, true); | |
414 | |
415 Builder.ReplaceParent(ctx, p); | |
416 } | |
417 else | |
418 { | |
419 throw new InvalidOperationException(); | |
420 } | |
421 } | |
422 } | |
423 else | |
424 { | |
425 args = _element.ConvertToSql(null, 0, ConvertFlags.Field).Select(_ => _.Sql).ToArray(); | |
426 } | |
427 | |
428 return new SqlFunction(call.Type, call.Method.Name, args); | |
429 } | |
430 | |
431 PropertyInfo _keyProperty; | |
432 | |
433 public override SqlInfo[] ConvertToSql(Expression expression, int level, ConvertFlags flags) | |
434 { | |
435 if (expression == null) | |
436 return _key.ConvertToSql(null, 0, flags); | |
437 | |
438 if (level > 0) | |
439 { | |
440 switch (expression.NodeType) | |
441 { | |
442 case ExpressionType.Call : | |
443 { | |
444 var e = (MethodCallExpression)expression; | |
445 | |
446 if (e.Method.DeclaringType == typeof(Enumerable)) | |
447 { | |
448 return new[] { new SqlInfo { Sql = ConvertEnumerable(e) } }; | |
449 } | |
450 | |
451 break; | |
452 } | |
453 | |
454 case ExpressionType.MemberAccess : | |
455 { | |
456 var levelExpression = expression.GetLevelExpression(level); | |
457 | |
458 if (levelExpression.NodeType == ExpressionType.MemberAccess) | |
459 { | |
460 var e = (MemberExpression)levelExpression; | |
461 | |
462 if (e.Member.Name == "Key") | |
463 { | |
464 if (_keyProperty == null) | |
465 _keyProperty = _groupingType.GetProperty("Key"); | |
466 | |
467 if (e.Member == _keyProperty) | |
468 { | |
469 if (levelExpression == expression) | |
470 return _key.ConvertToSql(null, 0, flags); | |
471 | |
472 return _key.ConvertToSql(expression, level + 1, flags); | |
473 } | |
474 } | |
475 | |
476 return Sequence.ConvertToSql(expression, level, flags); | |
477 } | |
478 | |
479 break; | |
480 } | |
481 } | |
482 } | |
483 | |
484 throw new InvalidOperationException(); | |
485 } | |
486 | |
487 readonly Dictionary<Tuple<Expression,int,ConvertFlags>,SqlInfo[]> _expressionIndex = new Dictionary<Tuple<Expression,int,ConvertFlags>,SqlInfo[]>(); | |
488 | |
489 public override SqlInfo[] ConvertToIndex(Expression expression, int level, ConvertFlags flags) | |
490 { | |
491 var key = Tuple.Create(expression, level, flags); | |
492 | |
493 SqlInfo[] info; | |
494 | |
495 if (!_expressionIndex.TryGetValue(key, out info)) | |
496 { | |
497 info = ConvertToSql(expression, level, flags); | |
498 | |
499 foreach (var item in info) | |
500 { | |
501 item.Query = SqlQuery; | |
502 item.Index = SqlQuery.Select.Add(item.Sql); | |
503 } | |
504 } | |
505 | |
506 return info; | |
507 } | |
508 | |
509 public override IsExpressionResult IsExpression(Expression expression, int level, RequestFor requestFlag) | |
510 { | |
511 if (level != 0) | |
512 { | |
513 var levelExpression = expression.GetLevelExpression(level); | |
514 | |
515 if (levelExpression.NodeType == ExpressionType.MemberAccess) | |
516 { | |
517 var ma = (MemberExpression)levelExpression; | |
518 | |
519 if (ma.Member.Name == "Key" && ma.Member.DeclaringType == _groupingType) | |
520 { | |
521 return levelExpression == expression ? | |
522 _key.IsExpression(null, 0, requestFlag) : | |
523 _key.IsExpression(expression, level + 1, requestFlag); | |
524 } | |
525 } | |
526 } | |
527 | |
528 return IsExpressionResult.False; | |
529 } | |
530 | |
531 public override int ConvertToParentIndex(int index, IBuildContext context) | |
532 { | |
533 var expr = SqlQuery.Select.Columns[index].Expression; | |
534 | |
535 if (!SqlQuery.GroupBy.Items.Exists(_ => _ == expr || (expr is SqlQuery.Column && _ == ((SqlQuery.Column)expr).Expression))) | |
536 SqlQuery.GroupBy.Items.Add(expr); | |
537 | |
538 return base.ConvertToParentIndex(index, this); | |
539 } | |
540 | |
541 interface IContextHelper | |
542 { | |
543 Expression GetContext(Expression sequence, ParameterExpression param, Expression expr1, Expression expr2); | |
544 } | |
545 | |
546 class ContextHelper<T> : IContextHelper | |
547 { | |
548 public Expression GetContext(Expression sequence, ParameterExpression param, Expression expr1, Expression expr2) | |
549 { | |
550 // ReSharper disable AssignNullToNotNullAttribute | |
551 //ReflectionHelper.Expressor<object>.MethodExpressor(_ => Queryable.Where(null, (Expression<Func<T,bool>>)null)), | |
552 var mi = ReflectionHelper.Expressor<object>.MethodExpressor(_ => Enumerable.Where(null, (Func<T,bool>)null)); | |
553 // ReSharper restore AssignNullToNotNullAttribute | |
554 var arg2 = Expression.Lambda<Func<T,bool>>(Expression.Equal(expr1, expr2), new[] { param }); | |
555 | |
556 return Expression.Call(null, mi, sequence, arg2); | |
557 } | |
558 } | |
559 | |
560 public override IBuildContext GetContext(Expression expression, int level, BuildInfo buildInfo) | |
561 { | |
562 if (expression == null && buildInfo != null) | |
563 { | |
564 if (buildInfo.Parent is SelectManyBuilder.SelectManyContext) | |
565 { | |
566 var sm = (SelectManyBuilder.SelectManyContext)buildInfo.Parent; | |
567 var ctype = typeof(ContextHelper<>).MakeGenericType(_key.Lambda.Parameters[0].Type); | |
568 var helper = (IContextHelper)Activator.CreateInstance(ctype); | |
569 var expr = helper.GetContext( | |
570 Sequence.Expression, | |
571 _key.Lambda.Parameters[0], | |
572 Expression.PropertyOrField(sm.Lambda.Parameters[0], "Key"), | |
573 _key.Lambda.Body); | |
574 | |
575 return Builder.BuildSequence(new BuildInfo(buildInfo, expr)); | |
576 } | |
577 | |
578 //if (buildInfo.Parent == this) | |
579 { | |
580 var ctype = typeof(ContextHelper<>).MakeGenericType(_key.Lambda.Parameters[0].Type); | |
581 var helper = (IContextHelper)Activator.CreateInstance(ctype); | |
582 var expr = helper.GetContext( | |
583 _sequenceExpr, | |
584 _key.Lambda.Parameters[0], | |
585 Expression.PropertyOrField(buildInfo.Expression, "Key"), | |
586 _key.Lambda.Body); | |
587 | |
588 var ctx = Builder.BuildSequence(new BuildInfo(buildInfo, expr)); | |
589 | |
590 ctx.SqlQuery.Properties.Add(Tuple.Create("from_group_by", SqlQuery)); | |
591 | |
592 return ctx; | |
593 } | |
594 | |
595 //return this; | |
596 } | |
597 | |
598 if (level != 0) | |
599 { | |
600 var levelExpression = expression.GetLevelExpression(level); | |
601 | |
602 if (levelExpression.NodeType == ExpressionType.MemberAccess) | |
603 { | |
604 var ma = (MemberExpression)levelExpression; | |
605 | |
606 if (ma.Member.Name == "Key" && ma.Member.DeclaringType == _groupingType) | |
607 { | |
608 return levelExpression == expression ? | |
609 _key.GetContext(null, 0, buildInfo) : | |
610 _key.GetContext(expression, level + 1, buildInfo); | |
611 } | |
612 } | |
613 } | |
614 | |
615 throw new InvalidOperationException(); | |
616 } | |
617 } | |
618 | |
619 #endregion | |
620 } | |
621 } |