转换表达式<func <tentity,ienumerable <tproperty =“”>&gt;&gt; valueSelector,TValue []的值为表达式<func <telement,bool =“”>&gt;

时间:2016-02-27 21:55:42

标签: c# entity-framework linq expression-trees

我正在尝试编写已解析的text-to-EF查询表达式库。想象一个文本框,其中用户键入以逗号分隔的整数列表。我将分裂&amp;解析它,并从中创建一个int数组。但是我需要为它创建一个表达式我的一个要求是取ctx => ctx.User.Receipts.Select(x => x.ReceiptID)和一个Receipt ID(int)列表并生成一个Expression,它做的是这样的:< / p>

ctx.User.Where(x => x.Receipts.Any(y => listOfIds.Contains(y.ReceiptId));

我认为它的签名是这样的:

Expression<Func<TEntity, bool>> CreateListExpression(Expression<Func<TEntity, IEnumerable<TProperty>>> accessor, TProperty[] constantValues)

,示例用法如下:

ctx => ctx.User.Where(CreateListExpression(x => x.Receipts.Select(y => y.ReceiptId), new int[] { 1, 2, 3, 4, 5 });

...但我有1个约束的灵活性。在SQL中,我正在寻找与此类似的东西(就我检索数据的效率和确保工作在SQL方面的工作而言):

select u.* 
from Users u 
join Receipts on r.UserID = u.UserID 
where r.ReceiptID in (1, 2, 3, 4, 5)

3 个答案:

答案 0 :(得分:0)

您是否需要动态生成这些表达式,而不是仅在DAL中为每个潜在查询提供一个函数?

您的描述听起来像用户只提供查询的输入值,但不是查询本身的整体结构,在这种情况下,我不会过度复杂化。您的模型还可以传递IQueryable并根据用户选择(添加任意数量的约束,订购内容等)根据需要进行修改,而不是实际创建表单。

如果你确实需要采用动态表达的方式,你能否更多地指出你的问题?我不确定当时的实际问题是什么。 你当然可以自己构建表达式。 Imho,这是一个充满痛苦的世界,用手创造非平凡的表达方式......并不是特别有趣。所以,如果有另一种方式,我通常建议选择另一种方式。

答案 1 :(得分:0)

我也不确定你打算如何动态定义这些属性,但你提出的方式似乎让它变得困难。我只想采用一种很好的旧占位技术。尝试类似常见的参数:

public class ParameterReplacer : ExpressionVisitor
{
    private readonly ParameterExpression m_parameter;
    private readonly Expression m_replacement;
    public ParameterReplacer(ParameterExpression parameter, Expression replacement)
    {
        this.m_parameter = parameter;
        this.m_replacement = replacement;
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (object.ReferenceEquals(node, m_parameter))
            return m_replacement;
        return node;
    }

}

然后定义一个扩展方法,该方法应该替换表达式中的参数。

public static class ExpressionExtensions
{
    public static Expression<Func<T1, TResult>> FixParam<T1, T2, TResult>(this Expression<Func<T1, T2, TResult>> expression, T2 parameterValue)
    {
        var parameterToRemove = expression.Parameters.ElementAt(1);
        var replacer = new ParameterReplacer(parameterToRemove, Expression.Constant(parameterValue, typeof(T2)));
        return Expression.Lambda<Func<T1, TResult>>(replacer.Visit(expression.Body), expression.Parameters.Where(p => p != parameterToRemove));
    }
}

这是第二个参数,但您当然可以轻松更改它。 现在,我将使用额外的占位符直接定义我的最终谓词,例如:

Expression<Func<User, int[], bool>> queryDefinition = (user, receiptIds) => user.Receipts.Any(r => receiptIds.Contains(r.Id));

然后在有值

时替换第二个参数
var ids = new []{3, 6};
var finalquery = queryDefinition.FixParam(ids);

这使您无需担心任何表达式树构建,并且您可以完全控制代码中的谓词。 PS:https://dotnetfiddle.net/Widget/T3oj5U

答案 2 :(得分:0)

实际上,一旦你意识到这一点,你所要求的就很容易了

(Parent p) => p.Children.Any(c => filter(c.Property))

相当于

(Parent p) => p.Children.Select(c => c.Property).Any(v => filter(v))

所以功能可以像这样

Expression<Func<TEntity, bool>> CreateListExpression<TEntity, TProperty>(Expression<Func<TEntity, IEnumerable<TProperty>>> accessor, TProperty[] constantValues)
{
    Expression<Func<TProperty, bool>> predicate = v => constantValues.Contains(v);
    var body = Expression.Call(typeof(Enumerable), "Any", 
        new[] { typeof(TProperty) }, accessor.Body, predicate);
    return Expression.Lambda<Func<TEntity, bool>>(body, accessor.Parameters);
}
相关问题