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 }