组合多个表达式以创建Linq选择器表达式 [英] Combine multiple expression to create a Linq selector expression

查看:72
本文介绍了组合多个表达式以创建Linq选择器表达式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试动态构建Linq查询的select语句. 我有这样的功能:

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

这是FieldSelector类:(我的代码每个字段都具有额外的属性)

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

如何实现GetSelector函数?是否有可能? (不会太复杂).
这就是我想使用它的方式:

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

推荐答案

您需要生成一个自定义类型,以供Anonymous Type的编译器使用,因为您无法生成具有动态属性计数的真正的Anonymous Type.生成此类型后,您可以轻松设置传递给FieldSelector的表达式的分配并将其组合为自定义类型.

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

顺便说一句,正如您在上面的代码中所看到的那样,当您尝试检索具有多于一层嵌套的属性class1->class2->class3->Propery_1时,当前解决方案不起作用.修复它并不难.

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.

编辑:上述情况已解决.现在您可以取回class1->class2->class3->Propery_1

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

它的用法:

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

不幸的是,cusObj的编译类型为object,如果您未将其标记为dynamic,则无法检索其属性.

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

顺便说一句,您的public Task<List<T>> RunQuery<T>不会被编译,因为field.GetSelector()返回Func<T, object>,当您调用return return query.Select(field.GetSelector()).ToListAsync()

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.

这篇关于组合多个表达式以创建Linq选择器表达式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆