将强类型表达式转换为匿名类型返回

时间:2016-09-29 20:22:26

标签: c# linq expression

我试图动态转换此类表达式:

myDbSet.Select(x => new MyClass
            {
                IsSelected = x.ChildList.Any()
            })

进入:

myDbSet.Select(x => new 
            {
                Item = x
                IsSelected = x.ChildList.Any()
            })

我会写一个扩展方法,它将获取强类型版本并转换为匿名,然后创建一个新表达式,以便执行以下操作:

myDbSet.Select(anonymousTransformedExpression).ToList().
       .Select(newGeneratedExpression)

我希望这个newGeneratedExpression为:

myDbSet.Select(x => new 
            {
                x.Item.IsSelected = x.IsSelected
                return x.Item;
            })

所以基本上将返回转换回强类型但是使用" IsSelected"应用价值。

问题是我真的找不到如何做的起点......

修改

好吧,我意识到这个问题并不是那么清楚,到目前为止,我已经有了一个主要问题,因此我已经接近解决方案了。这就是我得到的:

public static IEnumerable<TModel> SelectWithUnmapped<TModel> (this IQueryable<TModel> source, Expression<Func<TModel, object>> assigner) where TModel : class, new()
    {
        var anonymousType = typeof(AnonymousType<TModel>);
        var expression = Expression.New(anonymousType);
        var parameter = Expression.Parameter(typeof(TModel), "x");


        //this part is hard coded to only take binding at position 0.. eventually will become dynamic
        var originalBinding = ((MemberAssignment) ((MemberInitExpression) assigner.Body).Bindings[0]);
        var originalExpression = originalBinding.Expression;
        Expression conversion = Expression.Convert(originalExpression, typeof(object));

        var bindings = new[]
        {
            Expression.Bind(anonymousType.GetProperty("Item"), parameter),
            //this is hardcoded test
            Expression.Bind(anonymousType.GetProperty("Property1"), conversion)
        };

        var body = Expression.MemberInit(expression, bindings);
        var lambda = Expression.Lambda<Func<TModel, AnonymousType<TModel>>>(body, parameter);




        var test = source.Select(lambda).ToList();

        return source;
    }

    class AnonymousType<TModel>
    {
        public TModel Item { get; set; }
        public object Property1 { get; set; }
        public object Property2 { get; set; }
        public object Property3 { get; set; }
        public object Property4 { get; set; }
        public object Property5 { get; set; }
        public object Property6 { get; set; }
        public object Property7 { get; set; }
        public object Property8 { get; set; }
        public object Property9 { get; set; }
        public object Property10 { get; set; }
        public object Property11 { get; set; }
        public object Property12 { get; set; }
        public object Property13 { get; set; }
        public object Property14 { get; set; }
        public object Property15 { get; set; }
        public object Property16 { get; set; }
        public object Property17 { get; set; }
        public object Property18 { get; set; }
        public object Property19 { get; set; }
        public object Property20 { get; set; }
    } 
}

当我尝试分配测试时,出现以下错误:参数&#39; x&#39;未绑定在指定的LINQ to Entities查询表达式中。

这是由于我的第二次绑定使用了原始表达式的绑定表达式。但是两者都使用相同的参数名称&#34; x&#34;。我怎样才能确保我的新绑定实际上知道x是新表达式中的参数?

所以基本上到目前为止,我试图接受一个看起来像的表达式:

x => new Test
{
   PropertyTest = x.Blah.Count()
}

进入:

x => new AnonymousType<Test>(){
   Item = x,
   Property1 = x.Blah.Count()
}

1 个答案:

答案 0 :(得分:2)

我终于完成了它。我想这只需要耐心和反复试验。帮助任何想做同样事情的人。我不得不将自己限制在一定的财产数量......可以轻松添加。

以下是代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;


public static class IQueryableExtensions
{
    public static IEnumerable<TModel> SelectAndAssign<TModel>(this IQueryable<TModel> source, Expression<Func<TModel, object>> assigner) where TModel : class, new()
    {
        //get typed body of original expression
        var originalBody = (MemberInitExpression)assigner.Body;

        //list to store the new bindings we're creating for new expression
        var newExpressionBindings = new List<MemberBinding>();
        var newExpressionReturnType = typeof(AnonymousType<TModel>);

        //new param
        var parameter = Expression.Parameter(typeof(TModel), "x");

        //base binding
        newExpressionBindings.Add(Expression.Bind(newExpressionReturnType.GetProperty("Item"), parameter));

        //go through all the original expression's bindings
        for (var i = 0; i < originalBody.Bindings.Count; i++)
        {
            var originalBinding = (MemberAssignment)originalBody.Bindings[i];
            var originalExpression = originalBinding.Expression;

            var memberType = originalBinding.Expression.Type;

            //create delegate based on the member type
            var originalLambdaDelegate = typeof(Func<,>).MakeGenericType(typeof(TModel), memberType);

            //create lambda from that delegate
            var originalLambda = Expression.Lambda(originalLambdaDelegate, originalExpression, assigner.Parameters[0]);

            //create a AnonymousVar<MemberType> from the type of the member ( to please EF unable to assign bool to object directly ) 
            //start with getting the generic type
            var genericMemberType = typeof(AnonymousVar<>).MakeGenericType(memberType);
            //then create teh delegate
            var genericMemberTypeDelegate = typeof(Func<>).MakeGenericType(genericMemberType);
            //Now create an expression with a binding for that object to assign its Property ( strongly typed now from the generic declaration )
            var genericInstantiationExpression = Expression.New(genericMemberType);
            //the binding.. using the original expression expression
            var genericInstantiationBinding = Expression.Bind(genericMemberType.GetProperty("Property"), originalLambda.Body);
            // create the body
            var genericInstantiationBody = Expression.MemberInit(genericInstantiationExpression, genericInstantiationBinding);

            //now we need to recreate a lambda for this
            var newBindingExpression = Expression.Lambda(genericMemberTypeDelegate, genericInstantiationBody);

            //Create the binding and add it to the new expression bindings
            newExpressionBindings.Add(Expression.Bind(newExpressionReturnType.GetProperty("Property" + (i + 1)), newBindingExpression.Body));
        }

        //start creating the new expression
        var expression = Expression.New(newExpressionReturnType);

        //create new expression body with bindings
        var body = Expression.MemberInit(expression, newExpressionBindings);

        //The actual new expression lambda
        var newLambda = Expression.Lambda<Func<TModel, AnonymousType<TModel>>>(body, parameter);

        // replace old lambda param with new one
        var replacer = new ParameterReplacer(assigner.Parameters[0], newLambda.Parameters[0]); // replace old lambda param with new

        //new lambda with fixed params
        newLambda = Expression.Lambda<Func<TModel, AnonymousType<TModel>>>(replacer.Visit(newLambda.Body), newLambda.Parameters[0]);

        //now that we have all we need form the server, we materialize the list
        var materialized = source.Select(newLambda).ToList();

        //typed return parameter
        var typedReturnParameter = Expression.Parameter(typeof(AnonymousType<TModel>), "x");

        //Lets assign all those custom properties back into the original object type
        var expressionLines = new List<Expression>();
        for (var i = 0; i < originalBody.Bindings.Count; i++)
        {
            var originalBinding = (MemberAssignment)originalBody.Bindings[i];
            var itemPropertyExpression = Expression.Property(typedReturnParameter, "Item");
            var bindingPropertyExpression = Expression.Property(itemPropertyExpression, originalBinding.Member.Name);

            var memberType = originalBinding.Expression.Type;
            var valuePropertyExpression = Expression.Convert(Expression.Property(typedReturnParameter, "Property" + (i + 1)), typeof(AnonymousVar<>).MakeGenericType(memberType));
            var memberValuePropertyExpression = Expression.Property(valuePropertyExpression, "Property");

            var equalExpression = Expression.Assign(bindingPropertyExpression, memberValuePropertyExpression);
            expressionLines.Add(equalExpression);
        }
        var returnTarget = Expression.Label(typeof(TModel));
        expressionLines.Add(Expression.Return(returnTarget, Expression.Property(typedReturnParameter, "Item")));
        expressionLines.Add(Expression.Label(returnTarget, Expression.Constant(null, typeof(TModel))));
        var finalExpression = Expression.Block(expressionLines);

        var typedReturnLambda = Expression.Lambda<Func<AnonymousType<TModel>, TModel>>(finalExpression, typedReturnParameter).Compile();

        return materialized.Select(typedReturnLambda);
    }

    class AnonymousVar<TModel>
    {
        public TModel Property { get; set; }
    }

    class AnonymousType<TModel>
    {
        public TModel Item { get; set; }

        public object Property1 { get; set; }
        public object Property2 { get; set; }
        public object Property3 { get; set; }
        public object Property4 { get; set; }
        public object Property5 { get; set; }
        public object Property6 { get; set; }
        public object Property7 { get; set; }
        public object Property8 { get; set; }
        public object Property9 { get; set; }
        public object Property10 { get; set; }
        public object Property11 { get; set; }
        public object Property12 { get; set; }
        public object Property13 { get; set; }
        public object Property14 { get; set; }
        public object Property15 { get; set; }
        public object Property16 { get; set; }
        public object Property17 { get; set; }
        public object Property18 { get; set; }
        public object Property19 { get; set; }
        public object Property20 { get; set; }
    }


    class ParameterReplacer : ExpressionVisitor
    {
        private ParameterExpression from;
        private ParameterExpression to;

        public ParameterReplacer(ParameterExpression from, ParameterExpression to)
        {
            this.from = from;
            this.to = to;
        }
        protected override Expression VisitParameter(ParameterExpression node)
        {
            return base.VisitParameter(node == this.from ? this.to : node);
        }
    }
}