diff Source/Data/Linq/Builder/ExpressionBuilder.QueryBuilder.cs @ 0:f990fcb411a9

Копия текущей версии из github
author cin
date Thu, 27 Mar 2014 21:46:09 +0400
parents
children 1ef98bd70424
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Source/Data/Linq/Builder/ExpressionBuilder.QueryBuilder.cs	Thu Mar 27 21:46:09 2014 +0400
@@ -0,0 +1,543 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.Data;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace BLToolkit.Data.Linq.Builder
+{
+	using BLToolkit.Linq;
+	using Reflection;
+
+	partial class ExpressionBuilder
+	{
+		#region BuildExpression
+
+		readonly HashSet<Expression> _skippedExpressions = new HashSet<Expression>();
+
+		public Expression BuildExpression(IBuildContext context, Expression expression)
+		{
+			var newExpr = expression.Convert2(expr =>
+			{
+				if (_skippedExpressions.Contains(expr))
+					return new ExpressionHelper.ConvertInfo(expr, true);
+
+				if (expr.Find(IsNoneSqlMember) != null)
+					return new ExpressionHelper.ConvertInfo(expr);
+
+				switch (expr.NodeType)
+				{
+					case ExpressionType.MemberAccess:
+						{
+							if (IsServerSideOnly(expr) || PreferServerSide(expr))
+								return new ExpressionHelper.ConvertInfo(BuildSql(context, expr));
+
+							var ma = (MemberExpression)expr;
+
+							if (SqlProvider.ConvertMember(ma.Member) != null)
+								break;
+
+							var ctx = GetContext(context, expr);
+
+							if (ctx != null)
+								return new ExpressionHelper.ConvertInfo(ctx.BuildExpression(expr, 0));
+
+							var ex = ma.Expression;
+
+							if (ex != null && ex.NodeType == ExpressionType.Constant)
+							{
+								// field = localVariable
+								//
+								var c = _expressionAccessors[ex];
+								return new ExpressionHelper.ConvertInfo(
+									Expression.MakeMemberAccess(Expression.Convert(c, ex.Type), ma.Member));
+							}
+
+							break;
+						}
+
+					case ExpressionType.Parameter:
+						{
+							if (expr == ParametersParam)
+								break;
+
+							var ctx = GetContext(context, expr);
+
+							if (ctx != null)
+								return new ExpressionHelper.ConvertInfo(ctx.BuildExpression(expr, 0));
+
+							break;
+						}
+
+					case ExpressionType.Constant:
+						{
+							if (ExpressionHelper.IsConstant(expr.Type))
+								break;
+
+							if (_expressionAccessors.ContainsKey(expr))
+								return new ExpressionHelper.ConvertInfo(Expression.Convert(_expressionAccessors[expr], expr.Type));
+
+							break;
+						}
+
+					case ExpressionType.Coalesce:
+
+						if (expr.Type == typeof(string) && MappingSchema.GetDefaultNullValue<string>() != null)
+							return new ExpressionHelper.ConvertInfo(BuildSql(context, expr));
+
+						if (CanBeTranslatedToSql(context, ConvertExpression(expr), true))
+							return new ExpressionHelper.ConvertInfo(BuildSql(context, expr));
+
+						break;
+
+					case ExpressionType.Conditional:
+
+						if (CanBeTranslatedToSql(context, ConvertExpression(expr), true))
+							return new ExpressionHelper.ConvertInfo(BuildSql(context, expr));
+						break;
+
+					case ExpressionType.Call:
+						{
+							var ce = (MethodCallExpression)expr;
+
+							if (IsGroupJoinSource(context, ce))
+							{
+								foreach (var arg in ce.Arguments.Skip(1))
+									if (!_skippedExpressions.Contains(arg))
+										_skippedExpressions.Add(arg);
+
+								break;
+							}
+
+							if (IsSubQuery(context, ce))
+							{
+								if (TypeHelper.IsSameOrParent(typeof(IEnumerable), expr.Type) && expr.Type != typeof(string) && !expr.Type.IsArray)
+									return new ExpressionHelper.ConvertInfo(BuildMultipleQuery(context, expr));
+
+								return new ExpressionHelper.ConvertInfo(GetSubQuery(context, ce).BuildExpression(null, 0));
+							}
+
+							if (IsServerSideOnly(expr) || PreferServerSide(expr))
+								return new ExpressionHelper.ConvertInfo(BuildSql(context, expr));
+						}
+
+						break;
+				}
+
+				if (EnforceServerSide(context))
+				{
+					switch (expr.NodeType)
+					{
+						case ExpressionType.MemberInit :
+						case ExpressionType.New        :
+						case ExpressionType.Convert    :
+							break;
+
+						default                        :
+							if (CanBeCompiled(expr))
+								break;
+							return new ExpressionHelper.ConvertInfo(BuildSql(context, expr));
+					}
+				}
+
+				return new ExpressionHelper.ConvertInfo(expr);
+			});
+
+			return newExpr;
+		}
+
+		static bool EnforceServerSide(IBuildContext context)
+		{
+			return context.SqlQuery.Select.IsDistinct;
+		}
+
+		#endregion
+
+		#region BuildSql
+
+		Expression BuildSql(IBuildContext context, Expression expression)
+		{
+			var sqlex = ConvertToSqlExpression(context, expression, true);
+			var idx   = context.SqlQuery.Select.Add(sqlex);
+
+			idx = context.ConvertToParentIndex(idx, context);
+
+			var field = BuildSql(expression.Type, idx);
+
+			return field;
+		}
+
+		public Expression BuildSql(MemberAccessor ma, int idx, MethodInfo checkNullFunction, Expression context)
+		{
+			var expr = Expression.Call(DataReaderParam, ReflectionHelper.DataReader.GetValue, Expression.Constant(idx));
+
+			if (checkNullFunction != null)
+				expr = Expression.Call(null, checkNullFunction, expr, context);
+
+			Expression mapper;
+
+			if (TypeHelper.IsEnumOrNullableEnum(ma.Type))
+			{
+				var type = TypeHelper.ToNullable(ma.Type);
+				mapper =
+					Expression.Convert(
+						Expression.Call(
+							Expression.Constant(MappingSchema),
+							ReflectionHelper.MapSchema.MapValueToEnumWithMemberAccessor,
+								expr,
+								Expression.Constant(ma)),
+						type);
+			}
+			else
+			{
+				MethodInfo mi;
+
+				if (!ReflectionHelper.MapSchema.Converters.TryGetValue(ma.Type, out mi))
+				{
+					mapper =
+						Expression.Convert(
+							Expression.Call(
+								Expression.Constant(MappingSchema),
+								ReflectionHelper.MapSchema.ChangeType,
+									expr,
+									Expression.Constant(ma.Type)),
+							ma.Type);
+				}
+				else
+				{
+					mapper = Expression.Call(Expression.Constant(MappingSchema), mi, expr);
+				}
+			}
+
+			return mapper;
+		}
+
+		public Expression BuildSql(Type type, int idx, MethodInfo checkNullFunction, Expression context)
+		{
+			var expr = Expression.Call(DataReaderParam, ReflectionHelper.DataReader.GetValue, Expression.Constant(idx));
+
+			if (checkNullFunction != null)
+				expr = Expression.Call(null, checkNullFunction, expr, context);
+
+			Expression mapper;
+
+			if (type.IsEnum)
+			{
+				mapper =
+					Expression.Convert(
+						Expression.Call(
+							Expression.Constant(MappingSchema),
+							ReflectionHelper.MapSchema.MapValueToEnum,
+								expr,
+								Expression.Constant(type)),
+						type);
+			}
+			else
+			{
+				MethodInfo mi;
+
+				if (!ReflectionHelper.MapSchema.Converters.TryGetValue(type, out mi))
+				{
+					mapper =
+						Expression.Convert(
+							Expression.Call(
+								Expression.Constant(MappingSchema),
+								ReflectionHelper.MapSchema.ChangeType,
+									expr,
+									Expression.Constant(type)),
+							type);
+				}
+				else
+				{
+					mapper = Expression.Call(Expression.Constant(MappingSchema), mi, expr);
+				}
+			}
+
+			return mapper;
+		}
+
+		public Expression BuildSql(Type type, int idx)
+		{
+			return BuildSql(type, idx, null, null);
+		}
+
+		public Expression BuildSql(MemberAccessor ma, int idx)
+		{
+			return BuildSql(ma, idx, null, null);
+		}
+
+		#endregion
+
+		#region IsNonSqlMember
+
+		bool IsNoneSqlMember(Expression expr)
+		{
+			switch (expr.NodeType)
+			{
+				case ExpressionType.MemberAccess:
+					{
+						var me = (MemberExpression)expr;
+
+						var om = (
+							from c in Contexts.OfType<TableBuilder.TableContext>()
+							where c.ObjectType == me.Member.DeclaringType
+							select c.ObjectMapper
+						).FirstOrDefault();
+
+						return om != null && om.Associations.All(a => !TypeHelper.Equals(a.MemberAccessor.MemberInfo, me.Member)) && om[me.Member.Name, true] == null;
+					}
+			}
+
+			return false;
+		}
+
+		#endregion
+
+		#region PreferServerSide
+
+		bool PreferServerSide(Expression expr)
+		{
+			switch (expr.NodeType)
+			{
+				case ExpressionType.MemberAccess:
+					{
+						var pi = (MemberExpression)expr;
+						var l  = SqlProvider.ConvertMember(pi.Member);
+
+						if (l != null)
+						{
+							var info = l.Body.Unwrap();
+
+							if (l.Parameters.Count == 1 && pi.Expression != null)
+								info = info.Convert(wpi => wpi == l.Parameters[0] ? pi.Expression : wpi);
+
+							return info.Find(PreferServerSide) != null;
+						}
+
+						var attr = GetFunctionAttribute(pi.Member);
+						return attr != null && attr.PreferServerSide && !CanBeCompiled(expr);
+					}
+
+				case ExpressionType.Call:
+					{
+						var pi = (MethodCallExpression)expr;
+						var e  = pi;
+						var l  = SqlProvider.ConvertMember(e.Method);
+
+						if (l != null)
+							return l.Body.Unwrap().Find(PreferServerSide) != null;
+
+						var attr = GetFunctionAttribute(e.Method);
+						return attr != null && attr.PreferServerSide && !CanBeCompiled(expr);
+					}
+			}
+
+			return false;
+		}
+
+		#endregion
+
+		#region Build Mapper
+
+		public Expression BuildBlock(Expression expression)
+		{
+#if FW4 || SILVERLIGHT
+
+			if (IsBlockDisable || BlockExpressions.Count == 0)
+				return expression;
+
+			BlockExpressions.Add(expression);
+
+			expression = Expression.Block(BlockVariables, BlockExpressions);
+
+			BlockVariables.  Clear();
+			BlockExpressions.Clear();
+
+#endif
+
+			return expression;
+		}
+
+		public Expression<Func<QueryContext,IDataContext,IDataReader,Expression,object[],T>> BuildMapper<T>(Expression expr)
+		{
+			var type = typeof(T);
+
+			if (expr.Type != type)
+				expr = Expression.Convert(expr, type);
+
+			var mapper = Expression.Lambda<Func<QueryContext,IDataContext,IDataReader,Expression,object[],T>>(
+				BuildBlock(expr), new []
+				{
+					ContextParam,
+					DataContextParam,
+					DataReaderParam,
+					ExpressionParam,
+					ParametersParam,
+				});
+
+			return mapper;
+		}
+
+		#endregion
+
+		#region BuildMultipleQuery
+
+		interface IMultipleQueryHelper
+		{
+			Expression GetSubquery(
+				ExpressionBuilder       builder,
+				Expression              expression,
+				ParameterExpression     paramArray,
+				IEnumerable<Expression> parameters);
+		}
+
+		class MultipleQueryHelper<TRet> : IMultipleQueryHelper
+		{
+			public Expression GetSubquery(
+				ExpressionBuilder       builder,
+				Expression              expression,
+				ParameterExpression     paramArray,
+				IEnumerable<Expression> parameters)
+			{
+				var lambda      = Expression.Lambda<Func<IDataContext,object[],TRet>>(
+					expression,
+					Expression.Parameter(typeof(IDataContext), "ctx"),
+					paramArray);
+				var queryReader = CompiledQuery.Compile(lambda);
+
+				return Expression.Call(
+					null,
+					ReflectionHelper.Expressor<object>.MethodExpressor(_ => ExecuteSubQuery(null, null, null)),
+						ContextParam,
+						Expression.NewArrayInit(typeof(object), parameters),
+						Expression.Constant(queryReader)
+					);
+			}
+
+			static TRet ExecuteSubQuery(
+				QueryContext                     queryContext,
+				object[]                         parameters,
+				Func<IDataContext,object[],TRet> queryReader)
+			{
+				var db = queryContext.GetDataContext();
+
+				try
+				{
+					return queryReader(db.DataContextInfo.DataContext, parameters);
+				}
+				finally
+				{
+					queryContext.ReleaseDataContext(db);
+				}
+			}
+		}
+
+		public Expression BuildMultipleQuery(IBuildContext context, Expression expression)
+		{
+			if (!Common.Configuration.Linq.AllowMultipleQuery)
+				throw new LinqException("Multiple queries are not allowed. Set the 'BLToolkit.Common.Configuration.Linq.AllowMultipleQuery' flag to 'true' to allow multiple queries.");
+
+			var parameters = new HashSet<ParameterExpression>();
+
+			expression.Visit(e =>
+			{
+				if (e.NodeType == ExpressionType.Lambda)
+					foreach (var p in ((LambdaExpression)e).Parameters)
+						parameters.Add(p);
+			});
+
+			// Convert associations.
+			//
+			expression = expression.Convert(e =>
+			{
+				switch (e.NodeType)
+				{
+					case ExpressionType.MemberAccess :
+						{
+							var root = e.GetRootObject();
+
+							if (root != null &&
+								root.NodeType == ExpressionType.Parameter &&
+								!parameters.Contains((ParameterExpression)root))
+							{
+								var res = context.IsExpression(e, 0, RequestFor.Association);
+
+								if (res.Result)
+								{
+									var table = (TableBuilder.AssociatedTableContext)res.Context;
+
+									if (table.IsList)
+									{
+										var ttype  = typeof(Table<>).MakeGenericType(table.ObjectType);
+										var tbl    = Activator.CreateInstance(ttype);
+										var method = typeof(LinqExtensions)
+											.GetMethod("Where", BindingFlags.NonPublic | BindingFlags.Static)
+											.MakeGenericMethod(e.Type, table.ObjectType, ttype);
+
+										var me = (MemberExpression)e;
+										var op = Expression.Parameter(table.ObjectType, "t");
+
+										parameters.Add(op);
+
+										Expression ex = null;
+
+										for (var i = 0; i < table.Association.ThisKey.Length; i++)
+										{
+											var field1 = table.ParentAssociation.SqlTable.Fields[table.Association.ThisKey [i]];
+											var field2 = table.                  SqlTable.Fields[table.Association.OtherKey[i]];
+
+											var ee = Expression.Equal(
+												Expression.MakeMemberAccess(op,            field2.MemberMapper.MemberAccessor.MemberInfo),
+												Expression.MakeMemberAccess(me.Expression, field1.MemberMapper.MemberAccessor.MemberInfo));
+
+											ex = ex == null ? ee : Expression.AndAlso(ex, ee);
+										}
+
+										return Expression.Call(null, method, Expression.Constant(tbl), Expression.Lambda(ex, op));
+									}
+								}
+							}
+
+							break;
+						}
+				}
+
+				return e;
+			});
+
+			var paramex = Expression.Parameter(typeof(object[]), "ps");
+			var parms   = new List<Expression>();
+
+			// Convert parameters.
+			//
+			expression = expression.Convert(e =>
+			{
+				var root = e.GetRootObject();
+
+				if (root != null &&
+					root.NodeType == ExpressionType.Parameter &&
+					!parameters.Contains((ParameterExpression)root))
+				{
+					var ex = Expression.Convert(BuildExpression(context, e), typeof(object));
+
+					parms.Add(ex);
+
+					return Expression.Convert(
+						Expression.ArrayIndex(paramex, Expression.Constant(parms.Count - 1)),
+						e.Type);
+				}
+
+				return e;
+			});
+
+			var sqtype = typeof(MultipleQueryHelper<>).MakeGenericType(expression.Type);
+			var helper = (IMultipleQueryHelper)Activator.CreateInstance(sqtype);
+
+			return helper.GetSubquery(this, expression, paramex, parms);
+		}
+
+		#endregion
+	}
+}