简体   繁体   English

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

[英]Convert strongly typed Expression to anonymous type return

I'm trying to dynamically convert this type of expression :我正在尝试动态转换这种类型的表达式:

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

into :进入 :

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

I would write an extension method that would take the strongly typed version and transform into the anonymous then create a new expression in order to do something like this :我会编写一个扩展方法,该方法将采用强类型版本并转换为匿名版本,然后创建一个新表达式以执行以下操作:

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

I would want this newGeneratedExpression to be:我希望这个 newGeneratedExpression 是:

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

So basically tranform the return back into a strongly typed but with the "IsSelected" value applied.所以基本上将返回转换回强类型但应用了“IsSelected”值。

Problem is I cant really find a starting point on how to do so..问题是我真的找不到如何做的起点..

EDIT编辑

Alright I realised that the question wasn't so clear and I've come a little closer to the solution with one main problem so far.好吧,我意识到问题不是那么清楚,到目前为止,我已经接近解决一个主要问题的解决方案。 Here's what I got :这是我得到的:

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; }
    } 
}

When I try to assign test, I get the following error : The parameter 'x' was not bound in the specified LINQ to Entities query expression.当我尝试分配测试时,出现以下错误:在指定的 LINQ to Entities 查询表达式中未绑定参数“x”。

This is due to my second binding using the original expression's binding expression.这是由于我使用原始表达式的绑定表达式进行了第二次绑定。 However both use the same parameter name of "x".然而,两者都使用相同的参数名称“x”。 How could I make sure my new binding actually know that x is the parameter from the new expression?我如何确保我的新绑定确实知道 x 是来自新表达式的参数?

So basically so far I'm trying to take in an expression that looks like :所以基本上到目前为止,我正在尝试采用一个看起来像这样的表达式:

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

into :进入 :

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

I finally finished it up.我终于完成了。 I guess it just took patience and trial and error a lot.我想这只是需要耐心和反复试验。 To help anyone who would like to do the same.帮助任何想做同样事情的人。 I had to limit myself to a certain property count.. which can be added easily.我不得不将自己限制在特定的财产数量上……这很容易添加。

Here's the code :这是代码:

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);
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM