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 }