简体   繁体   中英

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:

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.

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.

This is due to my second binding using the original expression's binding expression. However both use the same parameter name of "x". How could I make sure my new binding actually know that x is the parameter from the new expression?

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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