Mercurial > pub > bltoolkit
comparison Source/Data/Linq/Builder/JoinBuilder.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.Generic; | |
3 using System.Linq; | |
4 using System.Linq.Expressions; | |
5 | |
6 namespace BLToolkit.Data.Linq.Builder | |
7 { | |
8 using BLToolkit.Linq; | |
9 using Data.Sql; | |
10 | |
11 class JoinBuilder : MethodCallBuilder | |
12 { | |
13 protected override bool CanBuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo) | |
14 { | |
15 if (!methodCall.IsQueryable("Join", "GroupJoin") || methodCall.Arguments.Count != 5) | |
16 return false; | |
17 | |
18 var body = ((LambdaExpression)methodCall.Arguments[2].Unwrap()).Body.Unwrap(); | |
19 | |
20 if (body.NodeType == ExpressionType .MemberInit) | |
21 { | |
22 var mi = (MemberInitExpression)body; | |
23 bool throwExpr; | |
24 | |
25 if (mi.NewExpression.Arguments.Count > 0 || mi.Bindings.Count == 0) | |
26 throwExpr = true; | |
27 else | |
28 throwExpr = mi.Bindings.Any(b => b.BindingType != MemberBindingType.Assignment); | |
29 | |
30 if (throwExpr) | |
31 throw new NotSupportedException(string.Format("Explicit construction of entity type '{0}' in join is not allowed.", body.Type)); | |
32 } | |
33 | |
34 return true; | |
35 } | |
36 | |
37 protected override IBuildContext BuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo) | |
38 { | |
39 var isGroup = methodCall.Method.Name == "GroupJoin"; | |
40 var outerContext = builder.BuildSequence(new BuildInfo(buildInfo, methodCall.Arguments[0], buildInfo.SqlQuery)); | |
41 var innerContext = builder.BuildSequence(new BuildInfo(buildInfo, methodCall.Arguments[1], new SqlQuery())); | |
42 var countContext = builder.BuildSequence(new BuildInfo(buildInfo, methodCall.Arguments[1], new SqlQuery())); | |
43 | |
44 var context = new SubQueryContext(outerContext); | |
45 innerContext = isGroup ? new GroupJoinSubQueryContext(innerContext, methodCall) : new SubQueryContext(innerContext); | |
46 countContext = new SubQueryContext(countContext); | |
47 | |
48 var join = isGroup ? innerContext.SqlQuery.WeakLeftJoin() : innerContext.SqlQuery.InnerJoin(); | |
49 var sql = context.SqlQuery; | |
50 | |
51 sql.From.Tables[0].Joins.Add(join.JoinedTable); | |
52 | |
53 var selector = (LambdaExpression)methodCall.Arguments[4].Unwrap(); | |
54 | |
55 context.SetAlias(selector.Parameters[0].Name); | |
56 innerContext.SetAlias(selector.Parameters[1].Name); | |
57 | |
58 var outerKeyLambda = ((LambdaExpression)methodCall.Arguments[2].Unwrap()); | |
59 var innerKeyLambda = ((LambdaExpression)methodCall.Arguments[3].Unwrap()); | |
60 | |
61 var outerKeySelector = outerKeyLambda.Body.Unwrap(); | |
62 var innerKeySelector = innerKeyLambda.Body.Unwrap(); | |
63 | |
64 var outerParent = context. Parent; | |
65 var innerParent = innerContext.Parent; | |
66 var countParent = countContext.Parent; | |
67 | |
68 var outerKeyContext = new ExpressionContext(buildInfo.Parent, context, outerKeyLambda); | |
69 var innerKeyContext = new InnerKeyContext (buildInfo.Parent, innerContext, innerKeyLambda); | |
70 var countKeyContext = new ExpressionContext(buildInfo.Parent, countContext, innerKeyLambda); | |
71 | |
72 // Process counter. | |
73 // | |
74 var counterSql = ((SubQueryContext)countContext).SqlQuery; | |
75 | |
76 // Make join and where for the counter. | |
77 // | |
78 if (outerKeySelector.NodeType == ExpressionType.New) | |
79 { | |
80 var new1 = (NewExpression)outerKeySelector; | |
81 var new2 = (NewExpression)innerKeySelector; | |
82 | |
83 for (var i = 0; i < new1.Arguments.Count; i++) | |
84 { | |
85 var arg1 = new1.Arguments[i]; | |
86 var arg2 = new2.Arguments[i]; | |
87 | |
88 BuildJoin(builder, join, outerKeyContext, arg1, innerKeyContext, arg2, countKeyContext, counterSql); | |
89 } | |
90 } | |
91 else if (outerKeySelector.NodeType == ExpressionType.MemberInit) | |
92 { | |
93 var mi1 = (MemberInitExpression)outerKeySelector; | |
94 var mi2 = (MemberInitExpression)innerKeySelector; | |
95 | |
96 for (var i = 0; i < mi1.Bindings.Count; i++) | |
97 { | |
98 if (mi1.Bindings[i].Member != mi2.Bindings[i].Member) | |
99 throw new LinqException(string.Format("List of member inits does not match for entity type '{0}'.", outerKeySelector.Type)); | |
100 | |
101 var arg1 = ((MemberAssignment)mi1.Bindings[i]).Expression; | |
102 var arg2 = ((MemberAssignment)mi2.Bindings[i]).Expression; | |
103 | |
104 BuildJoin(builder, join, outerKeyContext, arg1, innerKeyContext, arg2, countKeyContext, counterSql); | |
105 } | |
106 } | |
107 else | |
108 { | |
109 BuildJoin(builder, join, outerKeyContext, outerKeySelector, innerKeyContext, innerKeySelector, countKeyContext, counterSql); | |
110 } | |
111 | |
112 builder.ReplaceParent(outerKeyContext, outerParent); | |
113 builder.ReplaceParent(innerKeyContext, innerParent); | |
114 builder.ReplaceParent(countKeyContext, countParent); | |
115 | |
116 if (isGroup) | |
117 { | |
118 counterSql.ParentSql = sql; | |
119 counterSql.Select.Columns.Clear(); | |
120 | |
121 var inner = (GroupJoinSubQueryContext)innerContext; | |
122 | |
123 inner.Join = join.JoinedTable; | |
124 inner.CounterSql = counterSql; | |
125 return new GroupJoinContext( | |
126 buildInfo.Parent, selector, context, inner, methodCall.Arguments[1], outerKeyLambda, innerKeyLambda); | |
127 } | |
128 | |
129 return new JoinContext(buildInfo.Parent, selector, context, innerContext) | |
130 #if DEBUG | |
131 { | |
132 MethodCall = methodCall | |
133 } | |
134 #endif | |
135 ; | |
136 } | |
137 | |
138 protected override SequenceConvertInfo Convert( | |
139 ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo, ParameterExpression param) | |
140 { | |
141 return null; | |
142 } | |
143 | |
144 static void BuildJoin( | |
145 ExpressionBuilder builder, | |
146 SqlQuery.FromClause.Join join, | |
147 IBuildContext outerKeyContext, Expression outerKeySelector, | |
148 IBuildContext innerKeyContext, Expression innerKeySelector, | |
149 IBuildContext countKeyContext, SqlQuery countSql) | |
150 { | |
151 var predicate = builder.ConvertObjectComparison( | |
152 ExpressionType.Equal, | |
153 outerKeyContext, outerKeySelector, | |
154 innerKeyContext, innerKeySelector); | |
155 | |
156 if (predicate != null) | |
157 join.JoinedTable.Condition.Conditions.Add(new SqlQuery.Condition(false, predicate)); | |
158 else | |
159 join | |
160 .Expr(builder.ConvertToSql(outerKeyContext, outerKeySelector, false)).Equal | |
161 .Expr(builder.ConvertToSql(innerKeyContext, innerKeySelector, false)); | |
162 | |
163 predicate = builder.ConvertObjectComparison( | |
164 ExpressionType.Equal, | |
165 outerKeyContext, outerKeySelector, | |
166 countKeyContext, innerKeySelector); | |
167 | |
168 if (predicate != null) | |
169 countSql.Where.SearchCondition.Conditions.Add(new SqlQuery.Condition(false, predicate)); | |
170 else | |
171 countSql.Where | |
172 .Expr(builder.ConvertToSql(outerKeyContext, outerKeySelector, false)).Equal | |
173 .Expr(builder.ConvertToSql(countKeyContext, innerKeySelector, false)); | |
174 } | |
175 | |
176 class InnerKeyContext : ExpressionContext | |
177 { | |
178 public InnerKeyContext(IBuildContext parent, IBuildContext sequence, LambdaExpression lambda) | |
179 : base(parent, sequence, lambda) | |
180 { | |
181 } | |
182 | |
183 public override SqlInfo[] ConvertToSql(Expression expression, int level, ConvertFlags flags) | |
184 { | |
185 return base | |
186 .ConvertToSql(expression, level, flags) | |
187 .Select(idx => | |
188 { | |
189 var n = SqlQuery.Select.Add(idx.Sql); | |
190 | |
191 return new SqlInfo(idx.Members) | |
192 { | |
193 Sql = SqlQuery.Select.Columns[n], | |
194 Index = n | |
195 }; | |
196 }) | |
197 .ToArray(); | |
198 } | |
199 } | |
200 | |
201 internal class JoinContext : SelectContext | |
202 { | |
203 public JoinContext(IBuildContext parent, LambdaExpression lambda, IBuildContext outerContext, IBuildContext innerContext) | |
204 : base(parent, lambda, outerContext, innerContext) | |
205 { | |
206 } | |
207 } | |
208 | |
209 internal class GroupJoinContext : JoinContext | |
210 { | |
211 public GroupJoinContext( | |
212 IBuildContext parent, | |
213 LambdaExpression lambda, | |
214 IBuildContext outerContext, | |
215 GroupJoinSubQueryContext innerContext, | |
216 Expression innerExpression, | |
217 LambdaExpression outerKeyLambda, | |
218 LambdaExpression innerKeyLambda) | |
219 : base(parent, lambda, outerContext, innerContext) | |
220 { | |
221 _innerExpression = innerExpression; | |
222 _outerKeyLambda = outerKeyLambda; | |
223 _innerKeyLambda = innerKeyLambda; | |
224 | |
225 innerContext.GroupJoin = this; | |
226 } | |
227 | |
228 readonly Expression _innerExpression; | |
229 readonly LambdaExpression _outerKeyLambda; | |
230 readonly LambdaExpression _innerKeyLambda; | |
231 private Expression _groupExpression; | |
232 | |
233 interface IGroupJoinHelper | |
234 { | |
235 Expression GetGroupJoin(GroupJoinContext context); | |
236 } | |
237 | |
238 class GroupJoinHelper<TKey,TElement> : IGroupJoinHelper | |
239 { | |
240 public Expression GetGroupJoin(GroupJoinContext context) | |
241 { | |
242 // Convert outer condition. | |
243 // | |
244 var outerParam = Expression.Parameter(context._outerKeyLambda.Body.Type, "o"); | |
245 var outerKey = context._outerKeyLambda.Body.Convert( | |
246 e => e == context._outerKeyLambda.Parameters[0] ? context.Lambda.Parameters[0] : e); | |
247 | |
248 outerKey = context.Builder.BuildExpression(context, outerKey); | |
249 | |
250 // Convert inner condition. | |
251 // | |
252 var parameters = context.Builder.CurrentSqlParameters | |
253 .Select((p,i) => new { p, i }) | |
254 .ToDictionary(_ => _.p.Expression, _ => _.i); | |
255 var paramArray = Expression.Parameter(typeof(object[]), "ps"); | |
256 | |
257 var innerKey = context._innerKeyLambda.Body.Convert(e => | |
258 { | |
259 int idx; | |
260 | |
261 if (parameters.TryGetValue(e, out idx)) | |
262 { | |
263 return | |
264 Expression.Convert( | |
265 Expression.ArrayIndex(paramArray, Expression.Constant(idx)), | |
266 e.Type); | |
267 } | |
268 | |
269 return e; | |
270 }); | |
271 | |
272 // Item reader. | |
273 // | |
274 // ReSharper disable AssignNullToNotNullAttribute | |
275 | |
276 var expr = Expression.Call( | |
277 null, | |
278 ReflectionHelper.Expressor<object>.MethodExpressor(_ => Queryable.Where(null, (Expression<Func<TElement,bool>>)null)), | |
279 context._innerExpression, | |
280 Expression.Lambda<Func<TElement,bool>>( | |
281 Expression.Equal(innerKey, outerParam), | |
282 new[] { context._innerKeyLambda.Parameters[0] })); | |
283 | |
284 // ReSharper restore AssignNullToNotNullAttribute | |
285 | |
286 var lambda = Expression.Lambda<Func<IDataContext,TKey,object[],IQueryable<TElement>>>( | |
287 Expression.Convert(expr, typeof(IQueryable<TElement>)), | |
288 Expression.Parameter(typeof(IDataContext), "ctx"), | |
289 outerParam, | |
290 paramArray); | |
291 | |
292 var itemReader = CompiledQuery.Compile(lambda); | |
293 | |
294 return Expression.Call( | |
295 null, | |
296 ReflectionHelper.Expressor<object>.MethodExpressor(_ => GetGrouping(null, null, default(TKey), null)), | |
297 new[] | |
298 { | |
299 ExpressionBuilder.ContextParam, | |
300 Expression.Constant(context.Builder.CurrentSqlParameters), | |
301 outerKey, | |
302 Expression.Constant(itemReader), | |
303 }); | |
304 } | |
305 | |
306 static IEnumerable<TElement> GetGrouping( | |
307 QueryContext context, | |
308 List<ParameterAccessor> parameterAccessor, | |
309 TKey key, | |
310 Func<IDataContext,TKey,object[],IQueryable<TElement>> itemReader) | |
311 { | |
312 return new GroupByBuilder.GroupByContext.Grouping<TKey,TElement>(key, context, parameterAccessor, itemReader); | |
313 } | |
314 } | |
315 | |
316 public override Expression BuildExpression(Expression expression, int level) | |
317 { | |
318 if (expression == Lambda.Parameters[1]) | |
319 { | |
320 if (_groupExpression == null) | |
321 { | |
322 var gtype = typeof(GroupJoinHelper<,>).MakeGenericType( | |
323 _innerKeyLambda.Body.Type, | |
324 _innerKeyLambda.Parameters[0].Type); | |
325 | |
326 var helper = (IGroupJoinHelper)Activator.CreateInstance(gtype); | |
327 | |
328 _groupExpression = helper.GetGroupJoin(this); | |
329 } | |
330 | |
331 return _groupExpression; | |
332 } | |
333 | |
334 return base.BuildExpression(expression, level); | |
335 } | |
336 } | |
337 | |
338 internal class GroupJoinSubQueryContext : SubQueryContext | |
339 { | |
340 //readonly MethodCallExpression _methodCall; | |
341 | |
342 public SqlQuery.JoinedTable Join; | |
343 public SqlQuery CounterSql; | |
344 public GroupJoinContext GroupJoin; | |
345 | |
346 public GroupJoinSubQueryContext(IBuildContext subQuery, MethodCallExpression methodCall) | |
347 : base(subQuery) | |
348 { | |
349 //_methodCall = methodCall; | |
350 } | |
351 | |
352 public override IBuildContext GetContext(Expression expression, int level, BuildInfo buildInfo) | |
353 { | |
354 if (expression == null) | |
355 return this; | |
356 | |
357 return base.GetContext(expression, level, buildInfo); | |
358 } | |
359 | |
360 Expression _counterExpression; | |
361 SqlInfo[] _counterInfo; | |
362 | |
363 public override SqlInfo[] ConvertToIndex(Expression expression, int level, ConvertFlags flags) | |
364 { | |
365 if (expression != null && expression == _counterExpression) | |
366 return _counterInfo ?? (_counterInfo = new[] | |
367 { | |
368 new SqlInfo | |
369 { | |
370 Query = CounterSql.ParentSql, | |
371 Index = CounterSql.ParentSql.Select.Add(CounterSql), | |
372 Sql = CounterSql | |
373 } | |
374 }); | |
375 | |
376 return base.ConvertToIndex(expression, level, flags); | |
377 } | |
378 | |
379 public override IsExpressionResult IsExpression(Expression expression, int level, RequestFor testFlag) | |
380 { | |
381 if (testFlag == RequestFor.GroupJoin && expression == null) | |
382 return IsExpressionResult.True; | |
383 | |
384 return base.IsExpression(expression, level, testFlag); | |
385 } | |
386 | |
387 public SqlQuery GetCounter(Expression expr) | |
388 { | |
389 Join.IsWeak = true; | |
390 | |
391 _counterExpression = expr; | |
392 | |
393 return CounterSql; | |
394 } | |
395 } | |
396 } | |
397 } |