0
|
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 Reflection;
|
|
13
|
|
14 partial class ExpressionBuilder
|
|
15 {
|
|
16 #region BuildExpression
|
|
17
|
|
18 readonly HashSet<Expression> _skippedExpressions = new HashSet<Expression>();
|
|
19
|
|
20 public Expression BuildExpression(IBuildContext context, Expression expression)
|
|
21 {
|
|
22 var newExpr = expression.Convert2(expr =>
|
|
23 {
|
|
24 if (_skippedExpressions.Contains(expr))
|
|
25 return new ExpressionHelper.ConvertInfo(expr, true);
|
|
26
|
|
27 if (expr.Find(IsNoneSqlMember) != null)
|
|
28 return new ExpressionHelper.ConvertInfo(expr);
|
|
29
|
|
30 switch (expr.NodeType)
|
|
31 {
|
|
32 case ExpressionType.MemberAccess:
|
|
33 {
|
|
34 if (IsServerSideOnly(expr) || PreferServerSide(expr))
|
|
35 return new ExpressionHelper.ConvertInfo(BuildSql(context, expr));
|
|
36
|
|
37 var ma = (MemberExpression)expr;
|
|
38
|
|
39 if (SqlProvider.ConvertMember(ma.Member) != null)
|
|
40 break;
|
|
41
|
|
42 var ctx = GetContext(context, expr);
|
|
43
|
|
44 if (ctx != null)
|
|
45 return new ExpressionHelper.ConvertInfo(ctx.BuildExpression(expr, 0));
|
|
46
|
|
47 var ex = ma.Expression;
|
|
48
|
|
49 if (ex != null && ex.NodeType == ExpressionType.Constant)
|
|
50 {
|
|
51 // field = localVariable
|
|
52 //
|
|
53 var c = _expressionAccessors[ex];
|
|
54 return new ExpressionHelper.ConvertInfo(
|
|
55 Expression.MakeMemberAccess(Expression.Convert(c, ex.Type), ma.Member));
|
|
56 }
|
|
57
|
|
58 break;
|
|
59 }
|
|
60
|
|
61 case ExpressionType.Parameter:
|
|
62 {
|
|
63 if (expr == ParametersParam)
|
|
64 break;
|
|
65
|
|
66 var ctx = GetContext(context, expr);
|
|
67
|
|
68 if (ctx != null)
|
|
69 return new ExpressionHelper.ConvertInfo(ctx.BuildExpression(expr, 0));
|
|
70
|
|
71 break;
|
|
72 }
|
|
73
|
|
74 case ExpressionType.Constant:
|
|
75 {
|
|
76 if (ExpressionHelper.IsConstant(expr.Type))
|
|
77 break;
|
|
78
|
|
79 if (_expressionAccessors.ContainsKey(expr))
|
|
80 return new ExpressionHelper.ConvertInfo(Expression.Convert(_expressionAccessors[expr], expr.Type));
|
|
81
|
|
82 break;
|
|
83 }
|
|
84
|
|
85 case ExpressionType.Coalesce:
|
|
86
|
|
87 if (expr.Type == typeof(string) && MappingSchema.GetDefaultNullValue<string>() != null)
|
|
88 return new ExpressionHelper.ConvertInfo(BuildSql(context, expr));
|
|
89
|
|
90 if (CanBeTranslatedToSql(context, ConvertExpression(expr), true))
|
|
91 return new ExpressionHelper.ConvertInfo(BuildSql(context, expr));
|
|
92
|
|
93 break;
|
|
94
|
|
95 case ExpressionType.Conditional:
|
|
96
|
|
97 if (CanBeTranslatedToSql(context, ConvertExpression(expr), true))
|
|
98 return new ExpressionHelper.ConvertInfo(BuildSql(context, expr));
|
|
99 break;
|
|
100
|
|
101 case ExpressionType.Call:
|
|
102 {
|
|
103 var ce = (MethodCallExpression)expr;
|
|
104
|
|
105 if (IsGroupJoinSource(context, ce))
|
|
106 {
|
|
107 foreach (var arg in ce.Arguments.Skip(1))
|
|
108 if (!_skippedExpressions.Contains(arg))
|
|
109 _skippedExpressions.Add(arg);
|
|
110
|
|
111 break;
|
|
112 }
|
|
113
|
|
114 if (IsSubQuery(context, ce))
|
|
115 {
|
|
116 if (TypeHelper.IsSameOrParent(typeof(IEnumerable), expr.Type) && expr.Type != typeof(string) && !expr.Type.IsArray)
|
|
117 return new ExpressionHelper.ConvertInfo(BuildMultipleQuery(context, expr));
|
|
118
|
|
119 return new ExpressionHelper.ConvertInfo(GetSubQuery(context, ce).BuildExpression(null, 0));
|
|
120 }
|
|
121
|
|
122 if (IsServerSideOnly(expr) || PreferServerSide(expr))
|
|
123 return new ExpressionHelper.ConvertInfo(BuildSql(context, expr));
|
|
124 }
|
|
125
|
|
126 break;
|
|
127 }
|
|
128
|
|
129 if (EnforceServerSide(context))
|
|
130 {
|
|
131 switch (expr.NodeType)
|
|
132 {
|
|
133 case ExpressionType.MemberInit :
|
|
134 case ExpressionType.New :
|
|
135 case ExpressionType.Convert :
|
|
136 break;
|
|
137
|
|
138 default :
|
|
139 if (CanBeCompiled(expr))
|
|
140 break;
|
|
141 return new ExpressionHelper.ConvertInfo(BuildSql(context, expr));
|
|
142 }
|
|
143 }
|
|
144
|
|
145 return new ExpressionHelper.ConvertInfo(expr);
|
|
146 });
|
|
147
|
|
148 return newExpr;
|
|
149 }
|
|
150
|
|
151 static bool EnforceServerSide(IBuildContext context)
|
|
152 {
|
|
153 return context.SqlQuery.Select.IsDistinct;
|
|
154 }
|
|
155
|
|
156 #endregion
|
|
157
|
|
158 #region BuildSql
|
|
159
|
|
160 Expression BuildSql(IBuildContext context, Expression expression)
|
|
161 {
|
|
162 var sqlex = ConvertToSqlExpression(context, expression, true);
|
|
163 var idx = context.SqlQuery.Select.Add(sqlex);
|
|
164
|
|
165 idx = context.ConvertToParentIndex(idx, context);
|
|
166
|
|
167 var field = BuildSql(expression.Type, idx);
|
|
168
|
|
169 return field;
|
|
170 }
|
|
171
|
|
172 public Expression BuildSql(MemberAccessor ma, int idx, MethodInfo checkNullFunction, Expression context)
|
|
173 {
|
|
174 var expr = Expression.Call(DataReaderParam, ReflectionHelper.DataReader.GetValue, Expression.Constant(idx));
|
|
175
|
|
176 if (checkNullFunction != null)
|
|
177 expr = Expression.Call(null, checkNullFunction, expr, context);
|
|
178
|
|
179 Expression mapper;
|
|
180
|
|
181 if (TypeHelper.IsEnumOrNullableEnum(ma.Type))
|
|
182 {
|
|
183 var type = TypeHelper.ToNullable(ma.Type);
|
|
184 mapper =
|
|
185 Expression.Convert(
|
|
186 Expression.Call(
|
|
187 Expression.Constant(MappingSchema),
|
|
188 ReflectionHelper.MapSchema.MapValueToEnumWithMemberAccessor,
|
|
189 expr,
|
|
190 Expression.Constant(ma)),
|
|
191 type);
|
|
192 }
|
|
193 else
|
|
194 {
|
|
195 MethodInfo mi;
|
|
196
|
|
197 if (!ReflectionHelper.MapSchema.Converters.TryGetValue(ma.Type, out mi))
|
|
198 {
|
|
199 mapper =
|
|
200 Expression.Convert(
|
|
201 Expression.Call(
|
|
202 Expression.Constant(MappingSchema),
|
|
203 ReflectionHelper.MapSchema.ChangeType,
|
|
204 expr,
|
|
205 Expression.Constant(ma.Type)),
|
|
206 ma.Type);
|
|
207 }
|
|
208 else
|
|
209 {
|
|
210 mapper = Expression.Call(Expression.Constant(MappingSchema), mi, expr);
|
|
211 }
|
|
212 }
|
|
213
|
|
214 return mapper;
|
|
215 }
|
|
216
|
|
217 public Expression BuildSql(Type type, int idx, MethodInfo checkNullFunction, Expression context)
|
|
218 {
|
|
219 var expr = Expression.Call(DataReaderParam, ReflectionHelper.DataReader.GetValue, Expression.Constant(idx));
|
|
220
|
|
221 if (checkNullFunction != null)
|
|
222 expr = Expression.Call(null, checkNullFunction, expr, context);
|
|
223
|
|
224 Expression mapper;
|
|
225
|
|
226 if (type.IsEnum)
|
|
227 {
|
|
228 mapper =
|
|
229 Expression.Convert(
|
|
230 Expression.Call(
|
|
231 Expression.Constant(MappingSchema),
|
|
232 ReflectionHelper.MapSchema.MapValueToEnum,
|
|
233 expr,
|
|
234 Expression.Constant(type)),
|
|
235 type);
|
|
236 }
|
|
237 else
|
|
238 {
|
|
239 MethodInfo mi;
|
|
240
|
|
241 if (!ReflectionHelper.MapSchema.Converters.TryGetValue(type, out mi))
|
|
242 {
|
|
243 mapper =
|
|
244 Expression.Convert(
|
|
245 Expression.Call(
|
|
246 Expression.Constant(MappingSchema),
|
|
247 ReflectionHelper.MapSchema.ChangeType,
|
|
248 expr,
|
|
249 Expression.Constant(type)),
|
|
250 type);
|
|
251 }
|
|
252 else
|
|
253 {
|
|
254 mapper = Expression.Call(Expression.Constant(MappingSchema), mi, expr);
|
|
255 }
|
|
256 }
|
|
257
|
|
258 return mapper;
|
|
259 }
|
|
260
|
|
261 public Expression BuildSql(Type type, int idx)
|
|
262 {
|
|
263 return BuildSql(type, idx, null, null);
|
|
264 }
|
|
265
|
|
266 public Expression BuildSql(MemberAccessor ma, int idx)
|
|
267 {
|
|
268 return BuildSql(ma, idx, null, null);
|
|
269 }
|
|
270
|
|
271 #endregion
|
|
272
|
|
273 #region IsNonSqlMember
|
|
274
|
|
275 bool IsNoneSqlMember(Expression expr)
|
|
276 {
|
|
277 switch (expr.NodeType)
|
|
278 {
|
|
279 case ExpressionType.MemberAccess:
|
|
280 {
|
|
281 var me = (MemberExpression)expr;
|
|
282
|
|
283 var om = (
|
|
284 from c in Contexts.OfType<TableBuilder.TableContext>()
|
|
285 where c.ObjectType == me.Member.DeclaringType
|
|
286 select c.ObjectMapper
|
|
287 ).FirstOrDefault();
|
|
288
|
|
289 return om != null && om.Associations.All(a => !TypeHelper.Equals(a.MemberAccessor.MemberInfo, me.Member)) && om[me.Member.Name, true] == null;
|
|
290 }
|
|
291 }
|
|
292
|
|
293 return false;
|
|
294 }
|
|
295
|
|
296 #endregion
|
|
297
|
|
298 #region PreferServerSide
|
|
299
|
|
300 bool PreferServerSide(Expression expr)
|
|
301 {
|
|
302 switch (expr.NodeType)
|
|
303 {
|
|
304 case ExpressionType.MemberAccess:
|
|
305 {
|
|
306 var pi = (MemberExpression)expr;
|
|
307 var l = SqlProvider.ConvertMember(pi.Member);
|
|
308
|
|
309 if (l != null)
|
|
310 {
|
|
311 var info = l.Body.Unwrap();
|
|
312
|
|
313 if (l.Parameters.Count == 1 && pi.Expression != null)
|
|
314 info = info.Convert(wpi => wpi == l.Parameters[0] ? pi.Expression : wpi);
|
|
315
|
|
316 return info.Find(PreferServerSide) != null;
|
|
317 }
|
|
318
|
|
319 var attr = GetFunctionAttribute(pi.Member);
|
|
320 return attr != null && attr.PreferServerSide && !CanBeCompiled(expr);
|
|
321 }
|
|
322
|
|
323 case ExpressionType.Call:
|
|
324 {
|
|
325 var pi = (MethodCallExpression)expr;
|
|
326 var e = pi;
|
3
|
327 var l = SqlProvider.ConvertMember(ReflectionHelper.ExtractMethodInfo(e));
|
0
|
328
|
|
329 if (l != null)
|
|
330 return l.Body.Unwrap().Find(PreferServerSide) != null;
|
|
331
|
|
332 var attr = GetFunctionAttribute(e.Method);
|
|
333 return attr != null && attr.PreferServerSide && !CanBeCompiled(expr);
|
|
334 }
|
|
335 }
|
|
336
|
|
337 return false;
|
|
338 }
|
|
339
|
|
340 #endregion
|
|
341
|
|
342 #region Build Mapper
|
|
343
|
|
344 public Expression BuildBlock(Expression expression)
|
|
345 {
|
|
346 #if FW4 || SILVERLIGHT
|
|
347
|
|
348 if (IsBlockDisable || BlockExpressions.Count == 0)
|
|
349 return expression;
|
|
350
|
|
351 BlockExpressions.Add(expression);
|
|
352
|
|
353 expression = Expression.Block(BlockVariables, BlockExpressions);
|
|
354
|
|
355 BlockVariables. Clear();
|
|
356 BlockExpressions.Clear();
|
|
357
|
|
358 #endif
|
|
359
|
|
360 return expression;
|
|
361 }
|
|
362
|
|
363 public Expression<Func<QueryContext,IDataContext,IDataReader,Expression,object[],T>> BuildMapper<T>(Expression expr)
|
|
364 {
|
|
365 var type = typeof(T);
|
|
366
|
|
367 if (expr.Type != type)
|
|
368 expr = Expression.Convert(expr, type);
|
|
369
|
|
370 var mapper = Expression.Lambda<Func<QueryContext,IDataContext,IDataReader,Expression,object[],T>>(
|
|
371 BuildBlock(expr), new []
|
|
372 {
|
|
373 ContextParam,
|
|
374 DataContextParam,
|
|
375 DataReaderParam,
|
|
376 ExpressionParam,
|
|
377 ParametersParam,
|
|
378 });
|
|
379
|
|
380 return mapper;
|
|
381 }
|
|
382
|
|
383 #endregion
|
|
384
|
|
385 #region BuildMultipleQuery
|
|
386
|
|
387 interface IMultipleQueryHelper
|
|
388 {
|
|
389 Expression GetSubquery(
|
|
390 ExpressionBuilder builder,
|
|
391 Expression expression,
|
|
392 ParameterExpression paramArray,
|
|
393 IEnumerable<Expression> parameters);
|
|
394 }
|
|
395
|
|
396 class MultipleQueryHelper<TRet> : IMultipleQueryHelper
|
|
397 {
|
|
398 public Expression GetSubquery(
|
|
399 ExpressionBuilder builder,
|
|
400 Expression expression,
|
|
401 ParameterExpression paramArray,
|
|
402 IEnumerable<Expression> parameters)
|
|
403 {
|
|
404 var lambda = Expression.Lambda<Func<IDataContext,object[],TRet>>(
|
|
405 expression,
|
|
406 Expression.Parameter(typeof(IDataContext), "ctx"),
|
|
407 paramArray);
|
|
408 var queryReader = CompiledQuery.Compile(lambda);
|
|
409
|
|
410 return Expression.Call(
|
|
411 null,
|
|
412 ReflectionHelper.Expressor<object>.MethodExpressor(_ => ExecuteSubQuery(null, null, null)),
|
|
413 ContextParam,
|
|
414 Expression.NewArrayInit(typeof(object), parameters),
|
|
415 Expression.Constant(queryReader)
|
|
416 );
|
|
417 }
|
|
418
|
|
419 static TRet ExecuteSubQuery(
|
|
420 QueryContext queryContext,
|
|
421 object[] parameters,
|
|
422 Func<IDataContext,object[],TRet> queryReader)
|
|
423 {
|
|
424 var db = queryContext.GetDataContext();
|
|
425
|
|
426 try
|
|
427 {
|
|
428 return queryReader(db.DataContextInfo.DataContext, parameters);
|
|
429 }
|
|
430 finally
|
|
431 {
|
|
432 queryContext.ReleaseDataContext(db);
|
|
433 }
|
|
434 }
|
|
435 }
|
|
436
|
|
437 public Expression BuildMultipleQuery(IBuildContext context, Expression expression)
|
|
438 {
|
|
439 if (!Common.Configuration.Linq.AllowMultipleQuery)
|
|
440 throw new LinqException("Multiple queries are not allowed. Set the 'BLToolkit.Common.Configuration.Linq.AllowMultipleQuery' flag to 'true' to allow multiple queries.");
|
|
441
|
|
442 var parameters = new HashSet<ParameterExpression>();
|
|
443
|
|
444 expression.Visit(e =>
|
|
445 {
|
|
446 if (e.NodeType == ExpressionType.Lambda)
|
|
447 foreach (var p in ((LambdaExpression)e).Parameters)
|
|
448 parameters.Add(p);
|
|
449 });
|
|
450
|
|
451 // Convert associations.
|
|
452 //
|
|
453 expression = expression.Convert(e =>
|
|
454 {
|
|
455 switch (e.NodeType)
|
|
456 {
|
|
457 case ExpressionType.MemberAccess :
|
|
458 {
|
|
459 var root = e.GetRootObject();
|
|
460
|
|
461 if (root != null &&
|
|
462 root.NodeType == ExpressionType.Parameter &&
|
|
463 !parameters.Contains((ParameterExpression)root))
|
|
464 {
|
|
465 var res = context.IsExpression(e, 0, RequestFor.Association);
|
|
466
|
|
467 if (res.Result)
|
|
468 {
|
|
469 var table = (TableBuilder.AssociatedTableContext)res.Context;
|
|
470
|
|
471 if (table.IsList)
|
|
472 {
|
|
473 var ttype = typeof(Table<>).MakeGenericType(table.ObjectType);
|
|
474 var tbl = Activator.CreateInstance(ttype);
|
|
475 var method = typeof(LinqExtensions)
|
|
476 .GetMethod("Where", BindingFlags.NonPublic | BindingFlags.Static)
|
|
477 .MakeGenericMethod(e.Type, table.ObjectType, ttype);
|
|
478
|
|
479 var me = (MemberExpression)e;
|
|
480 var op = Expression.Parameter(table.ObjectType, "t");
|
|
481
|
|
482 parameters.Add(op);
|
|
483
|
|
484 Expression ex = null;
|
|
485
|
|
486 for (var i = 0; i < table.Association.ThisKey.Length; i++)
|
|
487 {
|
|
488 var field1 = table.ParentAssociation.SqlTable.Fields[table.Association.ThisKey [i]];
|
|
489 var field2 = table. SqlTable.Fields[table.Association.OtherKey[i]];
|
|
490
|
|
491 var ee = Expression.Equal(
|
|
492 Expression.MakeMemberAccess(op, field2.MemberMapper.MemberAccessor.MemberInfo),
|
|
493 Expression.MakeMemberAccess(me.Expression, field1.MemberMapper.MemberAccessor.MemberInfo));
|
|
494
|
|
495 ex = ex == null ? ee : Expression.AndAlso(ex, ee);
|
|
496 }
|
|
497
|
|
498 return Expression.Call(null, method, Expression.Constant(tbl), Expression.Lambda(ex, op));
|
|
499 }
|
|
500 }
|
|
501 }
|
|
502
|
|
503 break;
|
|
504 }
|
|
505 }
|
|
506
|
|
507 return e;
|
|
508 });
|
|
509
|
|
510 var paramex = Expression.Parameter(typeof(object[]), "ps");
|
|
511 var parms = new List<Expression>();
|
|
512
|
|
513 // Convert parameters.
|
|
514 //
|
|
515 expression = expression.Convert(e =>
|
|
516 {
|
|
517 var root = e.GetRootObject();
|
|
518
|
|
519 if (root != null &&
|
|
520 root.NodeType == ExpressionType.Parameter &&
|
|
521 !parameters.Contains((ParameterExpression)root))
|
|
522 {
|
|
523 var ex = Expression.Convert(BuildExpression(context, e), typeof(object));
|
|
524
|
|
525 parms.Add(ex);
|
|
526
|
|
527 return Expression.Convert(
|
|
528 Expression.ArrayIndex(paramex, Expression.Constant(parms.Count - 1)),
|
|
529 e.Type);
|
|
530 }
|
|
531
|
|
532 return e;
|
|
533 });
|
|
534
|
|
535 var sqtype = typeof(MultipleQueryHelper<>).MakeGenericType(expression.Type);
|
|
536 var helper = (IMultipleQueryHelper)Activator.CreateInstance(sqtype);
|
|
537
|
|
538 return helper.GetSubquery(this, expression, paramex, parms);
|
|
539 }
|
|
540
|
|
541 #endregion
|
|
542 }
|
|
543 }
|