Mercurial > pub > bltoolkit
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 + } +}