Combine multiple expression to create a Linq selector expression

夙愿已清 提交于 2020-01-16 09:10:08

问题


I'm trying to dynamically build the select statement of a Linq query. I have a function like this:

public Task<List<T>> RunQuery<T>(
    IQueryable<T> query, 
    FieldSelector<T> fields, 
    int pageNumber, int pageSize)
{
    var skip = (pageNumber-1) * pageSize;
    var query = query.Skip(skip).Take(pageSize);
    var selector = fields.GetSelector();
    return query.Select(selector).ToListAsync();
}

Here is the FieldSelector class: (I my code I have extra properties per field)

public class FieldSelector<T>
{
    private List<LambdaExpression> expressions;

    public FieldSelector()
    {
        expressions = new List<LambdaExpression>();
    }

    public void Add(Expression<Func<T, object>> expr)
    {
        expressions.Add(expr);
    }

    public Expression<Func<T, object>> GetSelector()
    {
        //Build an expression like e => new {e.Name, e.Street}
    }
}

How to implement the GetSelector function? Is it possible? (without getting too complex) .
This is how I would like to use it:

var fields = new FieldSelector<Employee>();
fields.Add(e => e.Name);
fields.Add(e => e.Street);
RunQuery<Employee>(query, fields, 1, 100);

回答1:


You need to generate a custom type how that is doing the compiler for Anonymous Type, because you cannot generate the really Anonymous Type with dynamic properties count. After generation this type you can easy set the assignments of expressions that you passed to FieldSelector and combine it to custom type.

    public class FieldSelector<T>
    {
        private List<LambdaExpression> expressions;

        public FieldSelector()
        {
            expressions = new List<LambdaExpression>();
        }

        public void Add(Expression<Func<T, object>> expr)
        {
            expressions.Add(expr);
        }

        public Expression<Func<T, object>> GetSelector()
        {
            // We will create a new type in runtime that looks like a AnonymousType
            var str = $"<>f__AnonymousType0`{expressions.Count}";

            // Create type builder
            var assemblyName = Assembly.GetExecutingAssembly().GetName();
            var modelBuilder = AppDomain.CurrentDomain
                                        .DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.RunAndCollect)
                                        .DefineDynamicModule("module");
            var typeBuilder = modelBuilder.DefineType(str, TypeAttributes.Public | TypeAttributes.Class);

            var types = new Type[expressions.Count];
            var names = new List<string>[expressions.Count];

            for (int i = 0; i < expressions.Count; i++)
            {
                // Retrive passed properties
                var unExpr = expressions[i].Body as UnaryExpression;
                var exp = unExpr == null ? expressions[i].Body as MemberExpression : unExpr.Operand as MemberExpression;
                types[i] = exp.Type;
                // Retrive a nested properties
                names[i] = GetAllNestedMembersName(exp);
            }

            // Defined generic parameters for custom type
            var genericParams = typeBuilder.DefineGenericParameters(types.Select((_, i) => $"PropType{i}").ToArray());
            for (int i = 0; i < types.Length; i++)
            {
                typeBuilder.DefineField($"{string.Join("_", names[i])}", genericParams[i], FieldAttributes.Public);
            }

            // Create generic type by passed properties
            var type = typeBuilder.CreateType();
            var genericType = type.MakeGenericType(types);

            ParameterExpression parameter = Expression.Parameter(typeof(T), "MyItem");

            // Create nested properties
            var assignments = genericType.GetFields().Select((prop, i) => Expression.Bind(prop, GetAllNestedMembers(parameter, names[i])));
            return Expression.Lambda<Func<T, object>>(Expression.MemberInit(Expression.New(genericType.GetConstructors()[0]), assignments), parameter);
        }

        private Expression GetAllNestedMembers(Expression parameter, List<string> properties)
        {
            Expression expression = parameter;
            for (int i = 0; i < properties.Count; ++i)
            {
                expression = Expression.Property(expression, properties[i]);
            }
            return expression;
        }

        private List<string> GetAllNestedMembersName(Expression arg)
        {
            var result = new List<string>();
            var expression = arg as MemberExpression;
            while (expression != null && expression.NodeType != ExpressionType.Parameter)
            {
                result.Insert(0, expression.Member.Name);
                expression = expression.Expression as MemberExpression;
            }
            return result;
        }
    }

By the way, as you saw in the code above current solution doesn't work for cases when you try to retrieve property with more than 1 level nesting class1->class2->class3->Propery_1. It's not hard to fix it.

EDIT: Case above was fixed. Now you can retrive class1->class2->class3->Propery_1

And it's usage of it:

private class TestClass
{
    public string Arg2 { get; set; }

    public TestClass Nested { get; set; }

    public int Id { get; set; }
}

var field = new FieldSelector<TestClass>();
field.Add(e => e.Arg2);
field.Add(e => e.Id);
field.Add(e => e.Nested.Id);
dynamic cusObj = field.GetSelector().Compile()(new TestClass { Arg2 = "asd", Id = 6, Nested = new TestClass { Id = 79 } });
Console.WriteLine(cusObj.Arg2);
Console.WriteLine(cusObj.Id);
Console.WriteLine(cusObj.Nested_Id);

Unfortunately, cusObj will be object at compile type and you cannot retrieve his propertis if you doesn't mark it as dynamic.

By the way, your public Task<List<T>> RunQuery<T> wouldn't be compile, because field.GetSelector() returns Func<T, object> and you will get a Task<List<object>> when you will invoke return return query.Select(field.GetSelector()).ToListAsync()

Hope, this is helpful.



来源:https://stackoverflow.com/questions/45744046/combine-multiple-expression-to-create-a-linq-selector-expression

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!