组合多个表达式以创建Linq选择器表达式

时间:2017-08-17 20:01:53

标签: c# linq expression

我试图动态构建Linq查询的select语句。 我有这样的功能:

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类:(我的代码我每个字段都有额外的属性)

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功能?可能吗? (不要太复杂) 这就是我想用它的方式:

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

1 个答案:

答案 0 :(得分:3)

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

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

顺便说一句,正如您在上面的代码中看到的那样,当您尝试检索具有超过1级嵌套class1->class2->class3->Propery_1的属性时,当前解决方案不起作用。解决它并不困难。

编辑:以上案例已修复。现在您可以检索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,则无法检索其属性。

顺便说一句,您的public Task<List<T>> RunQuery<T>无法编译,因为field.GetSelector()会返回Func<T, object>,当您调用return {{}时,您将获得Task<List<object>> 1}}

希望,这很有帮助。