首页 > 解决方案 > IQueryable 上的选择器表达式动态

问题描述

我想从一些 lambda 动态生成一个选择器表达式。

我想像这样声明一个 lambda 表达式列表

Expression<Func<MyEntity, object>> select1 = myentity => myentity.Label;
Expression<Func<MyEntity, object>> select2 = myentity => myentity.User.Name;
Expression<Func<MyEntity, object>> select3 = myentity => myentity.Fields.Where(1 == 1).Select(f => f.Code).FirstOrDefault();

假设我有一堂课:

class MyClass 
{
   public string Label { get; set; }
   public string UserName { get; set; }
   public string CodeField { get; set; }
}

我想使用声明的表达式动态组合选择器表达式。目标是我想选择要恢复的数据,而不是全部一起。

Expression.Lambda<Func<MyEntity, MyClass>> selectExpression = ?? 

req.Select(selectExpression).ToList();

我想生成一个选择器表达式来拥有这样的东西

return req.Select(myentity => new MyClass {
    Label = myentity.Label,
    UserName = myentity.User.Name,
    CodeField = myentity.Fields.Where(1 == 1).Select(f => f.Code).FirstOrDefault()
}).ToList();

我可以这样做吗?


例如,我成功了,但这不是我要寻找的方式

var entityT = Expression.Parameter(typeof(MyEntity), "entity");

var propertyA = Expression.Property(entityT, typeof(MyEntity).GetProperty("Label"));

var propertyB = Expression.Property(entityT, typeof(MyEntity).GetProperty("User"));
var propertyC = Expression.Property(propertyB, typeof(UserEntity).GetProperty("Name"));


var binding = Expression.MemberInit(Expression.New(typeof(MyClass)),
   new[]
   {
       Expression.Bind(typeof(MyClass).GetProperty("Label"), propertyA),
       Expression.Bind(typeof(MyClass).GetProperty("UserName"), propertyC),   
   });

var selectExpression = Expression.Lambda<Func<Benef, MyClass>>(binding, entityT);

return req.Select(selectExpression).ToList();

同样的想法,我很想这样做,它编译但不起作用:

var binding = Expression.MemberInit(Expression.New(typeof(T)),
    new[]
    {
        Expression.Bind(typeof(T).GetProperty("Label"), select1.Body),
        Expression.Bind(typeof(T).GetProperty("UserName"), select2.Body),   
    });

我有这个错误:“从范围''引用'MyEntity'类型的变量'myentity',但它没有定义”

谢谢您的回答。

标签: c#.netselectlambdaef-core-2.2

解决方案


基本上,您需要从每个 lambda 中提取表达式并将其与参数 from 连接起来MyClass

类似的东西:Expression.Bind(typeof(MyClass).GetParameter("x"), selectX.Body)

唯一的难点是 all 都selectX.Body需要指向同一个参数,所以每个body 的表情都需要调整。

这是示例代码:

class Program
{
    static void Main(string[] args)
    {
        var mapped = entities
            .Select(MakeExpression<MyEntity, MyClass>(select1, select2, select3))
            .ToList();
    }

    // Create lambda expression 
    private static Expression<Func<TEntity, TModel>> MakeExpression<TEntity, TModel>(params Expression<Func<TEntity, object>>[] select)
    {
        var param = Expression.Parameter(typeof(TEntity));

        // Map expressions [select1, ..., selectN] with properties
        // For keeping things simple I map nth expression with nth property
        // eg. select1 with first property from MyClass
        var body = Expression.MemberInit(
            Expression.New(typeof(TModel)),
            typeof(TModel)
                .GetProperties()
                .Select((p, i) => Expression.Bind(p, MakeParam(param, select[i])))
                .ToArray()
        );  

        return Expression.Lambda<Func<TEntity, TModel>>(body, param);
    }

    // Replace parameter from given expression with param
    // All expressions must have same MyEntity parameter
    private static Expression MakeParam<TEntity>(ParameterExpression param, Expression<Func<TEntity, object>> select)
    {
        Expression body = select.Body;

        return new ParamVisitor<TEntity>(param).Visit(body);
    }
}

class ParamVisitor<TEntity> : ExpressionVisitor
{
    private readonly ParameterExpression _param;

    public ParamVisitor(ParameterExpression param)
    {
        this._param = param;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (node.Type == typeof(TEntity))
        {
            return this._param;
        }

        return base.VisitParameter(node);
    }
}

推荐阅读