Mercurial > pub > bltoolkit
diff Source/Data/Linq/Builder/SelectBuilder.cs @ 0:f990fcb411a9
Копия текущей версии из github
author | cin |
---|---|
date | Thu, 27 Mar 2014 21:46:09 +0400 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Source/Data/Linq/Builder/SelectBuilder.cs Thu Mar 27 21:46:09 2014 +0400 @@ -0,0 +1,315 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Linq.Expressions; + +namespace BLToolkit.Data.Linq.Builder +{ + using BLToolkit.Linq; + using Reflection; + + class SelectBuilder : MethodCallBuilder + { + #region SelectBuilder + + protected override bool CanBuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo) + { + if (methodCall.IsQueryable("Select")) + { + switch (((LambdaExpression)methodCall.Arguments[1].Unwrap()).Parameters.Count) + { + case 1 : + case 2 : return true; + default: break; + } + } + + return false; + } + + protected override IBuildContext BuildMethodCall(ExpressionBuilder builder, MethodCallExpression methodCall, BuildInfo buildInfo) + { + var selector = (LambdaExpression)methodCall.Arguments[1].Unwrap(); + var sequence = builder.BuildSequence(new BuildInfo(buildInfo, methodCall.Arguments[0])); + + sequence.SetAlias(selector.Parameters[0].Name); + + var body = selector.Body.Unwrap(); + + switch (body.NodeType) + { + case ExpressionType.Parameter : break; + default : + sequence = CheckSubQueryForSelect(sequence); + break; + } + + var context = selector.Parameters.Count == 1 ? + new SelectContext (buildInfo.Parent, selector, sequence) : + new SelectContext2(buildInfo.Parent, selector, sequence); + +#if DEBUG + context.MethodCall = methodCall; +#endif + + return context; + } + + static IBuildContext CheckSubQueryForSelect(IBuildContext context) + { + return context.SqlQuery.Select.IsDistinct ? new SubQueryContext(context) : context; + } + + #endregion + + #region SelectContext2 + + class SelectContext2 : SelectContext + { + public SelectContext2(IBuildContext parent, LambdaExpression lambda, IBuildContext sequence) + : base(parent, lambda, sequence) + { + } + + static readonly ParameterExpression _counterParam = Expression.Parameter(typeof(int), "counter"); + + public override void BuildQuery<T>(Query<T> query, ParameterExpression queryParameter) + { + var expr = BuildExpression(null, 0); + + if (expr.Type != typeof(T)) + expr = Expression.Convert(expr, typeof(T)); + + var mapper = Expression.Lambda<Func<int,QueryContext,IDataContext,IDataReader,Expression,object[],T>>( + Builder.BuildBlock(expr), new [] + { + _counterParam, + ExpressionBuilder.ContextParam, + ExpressionBuilder.DataContextParam, + ExpressionBuilder.DataReaderParam, + ExpressionBuilder.ExpressionParam, + ExpressionBuilder.ParametersParam, + }); + + var func = mapper.Compile(); + + Func<QueryContext,IDataContext,IDataReader,Expression,object[],int,T> map = (ctx,db,rd,e,ps,n) => func(n, ctx, db, rd, e, ps); + + query.SetQuery(map); + } + + public override IsExpressionResult IsExpression(Expression expression, int level, RequestFor requestFlag) + { + switch (requestFlag) + { + case RequestFor.Expression : + case RequestFor.Root : + if (expression == Lambda.Parameters[1]) + return IsExpressionResult.True; + break; + } + + return base.IsExpression(expression, level, requestFlag); + } + + public override Expression BuildExpression(Expression expression, int level) + { + if (expression == Lambda.Parameters[1]) + return _counterParam; + + return base.BuildExpression(expression, level); + } + } + + #endregion + + #region Convert + + protected override SequenceConvertInfo Convert( + ExpressionBuilder builder, MethodCallExpression originalMethodCall, BuildInfo buildInfo, ParameterExpression param) + { + var methodCall = originalMethodCall; + var selector = (LambdaExpression)methodCall.Arguments[1].Unwrap(); + var info = builder.ConvertSequence(new BuildInfo(buildInfo, methodCall.Arguments[0]), selector.Parameters[0]); + + if (info != null) + { + methodCall = (MethodCallExpression)methodCall.Convert( + ex => ConvertMethod(methodCall, 0, info, selector.Parameters[0], ex)); + selector = (LambdaExpression)methodCall.Arguments[1].Unwrap(); + } + + if (param != null && param != builder.SequenceParameter) + { + var list = + ( + from path in GetExpressions(selector.Parameters[0], param, 0, selector.Body.Unwrap()) + orderby path.Level descending + select path + ).ToList(); + + if (list.Count > 0) + { + var plist = list.Where(e => e.Expr == selector.Parameters[0]).ToList(); + + if (plist.Count > 1) + list = list.Except(plist.Skip(1)).ToList(); + + var p = plist.FirstOrDefault(); + + if (p == null) + { + var types = methodCall.Method.GetGenericArguments(); + var mgen = methodCall.Method.GetGenericMethodDefinition(); + var btype = typeof(ExpressionHoder<,>).MakeGenericType(types[0], selector.Body.Type); + var fields = btype.GetFields(); + var pold = selector.Parameters[0]; + var psel = Expression.Parameter(types[0], pold.Name); + + methodCall = Expression.Call( + methodCall.Object, + mgen.MakeGenericMethod(types[0], btype), + methodCall.Arguments[0], + Expression.Lambda( + Expression.MemberInit( + Expression.New(btype), + Expression.Bind(fields[0], psel), + Expression.Bind(fields[1], selector.Body.Convert(e => e == pold ? psel : e))), + psel)); + + selector = (LambdaExpression)methodCall.Arguments[1].Unwrap(); + param = Expression.Parameter(selector.Body.Type, param.Name); + + list.Add(new SequenceConvertPath { Path = param, Expr = Expression.MakeMemberAccess(param, fields[1]), Level = 1 }); + + var expr = Expression.MakeMemberAccess(param, fields[0]); + + foreach (var t in list) + t.Expr = t.Expr.Convert(ex => ex == pold ? expr : ex); + + return new SequenceConvertInfo + { + Parameter = param, + Expression = methodCall, + ExpressionsToReplace = list + }; + } + + if (info != null) + { + if (info.ExpressionsToReplace != null) + { + foreach (var path in info.ExpressionsToReplace) + { + path.Path = path.Path.Convert(e => e == info.Parameter ? p.Path : e); + path.Expr = path.Expr.Convert(e => e == info.Parameter ? p.Path : e); + path.Level += p.Level; + + list.Add(path); + } + + list = list.OrderByDescending(path => path.Level).ToList(); + } + } + + if (list.Count > 1) + { + return new SequenceConvertInfo + { + Parameter = param, + Expression = methodCall, + ExpressionsToReplace = list + .Where (e => e != p) + .Select(ei => + { + ei.Expr = ei.Expr.Convert(e => e == p.Expr ? p.Path : e); + return ei; + }) + .ToList() + }; + } + } + } + + if (methodCall != originalMethodCall) + return new SequenceConvertInfo + { + Parameter = param, + Expression = methodCall, + }; + + return null; + } + + static IEnumerable<SequenceConvertPath> GetExpressions(ParameterExpression param, Expression path, int level, Expression expression) + { + switch (expression.NodeType) + { + // new { ... } + // + case ExpressionType.New : + { + var expr = (NewExpression)expression; + + if (expr.Members != null) for (var i = 0; i < expr.Members.Count; i++) + { + var q = GetExpressions(param, Expression.MakeMemberAccess(path, expr.Members[i]), level + 1, expr.Arguments[i]); + foreach (var e in q) + yield return e; + } + + break; + } + + // new MyObject { ... } + // + case ExpressionType.MemberInit : + { + var expr = (MemberInitExpression)expression; + var dic = TypeAccessor.GetAccessor(expr.Type) + .Select((m,i) => new { m, i }) + .ToDictionary(_ => _.m.MemberInfo.Name, _ => _.i); + + foreach (var binding in expr.Bindings.Cast<MemberAssignment>().OrderBy(b => dic[b.Member.Name])) + { + var q = GetExpressions(param, Expression.MakeMemberAccess(path, binding.Member), level + 1, binding.Expression); + foreach (var e in q) + yield return e; + } + + break; + } + + // parameter + // + case ExpressionType.Parameter : + if (expression == param) + yield return new SequenceConvertPath { Path = path, Expr = expression, Level = level }; + break; + + case ExpressionType.TypeAs : + yield return new SequenceConvertPath { Path = path, Expr = expression, Level = level }; + break; + + // Queriable method. + // + case ExpressionType.Call : + { + var call = (MethodCallExpression)expression; + + if (call.IsQueryable()) + if (TypeHelper.IsSameOrParent(typeof(IEnumerable), call.Type) || + TypeHelper.IsSameOrParent(typeof(IQueryable), call.Type) || + FirstSingleBuilder.MethodNames.Contains(call.Method.Name)) + yield return new SequenceConvertPath { Path = path, Expr = expression, Level = level }; + + break; + } + } + } + + #endregion + } +}