【问题标题】:How can I specify a predicate's type, which I won't know until runtime?如何指定谓词的类型,直到运行时我才知道?
【发布时间】:2016-11-11 09:00:00
【问题描述】:

我的存储库方法从数据库中获取内容。它接受排序顺序作为参数:

IEnumerable<Car> getCars<TSortKey>(Expression<Func<Car, TSortKey>> sort);

我使用TSortKey,因为直到运行时我才知道将使用哪个属性,它可以是x =&gt; x.Namex =&gt; x.Make,它们是字符串,但也可以是x =&gt; x.History.Age,它是一个整数。

用户选择排序顺序,然后我在开关中设置排序谓词并调用该方法。

  Expression<Func<Car, object>> sortPredicate;
  switch (sortOption) {
    case SortOption.Name: sortPredicate = s => s.Name; break;
    case SortOption.Make: sortPredicate = s => s.Make; break;
    case SortOption.Age:  sortPredicate = s => s.History.Age; break;
    default:              sortPredicate = s => s.Name; break;
    }
  var cars = repo.getCars(sortPredicate);

我在谓词中使用object,因为直到运行时我才知道类型。但这会生成错误的 SQL,然后抛出。

那么我该如何解决这个问题?

【问题讨论】:

  • 可能的解决方案:我可以将方法调用移动到每个 switch 案例中,但这会变得非常混乱。例如case SortOption.Name: cars = repo.getCars(x=&gt;x.Name); break;
  • 我很确定您可能的解决方案是最好的解决方案。另一种可能性是将您的 SortOption 枚举传递到 repo 并在适当的位置构建正确的 lamda ..
  • @Jamiec 是的,我现在正在做“混乱”的方式,但它变得非常混乱,因为这只是我真实代码的一小部分摘录。我希望我能以某种方式重写 lambda 以采用泛型类型。
  • 您可以考虑使用反射来创建您的 Expression-Function 并创建 getCars 的通用方法并调用它。如果您需要更多帮助,我可以写一个答案。

标签: c# entity-framework linq lambda


【解决方案1】:

问题是Expression&lt;Func&lt;T, object&gt;&gt; 为值类型属性生成额外的Convert,EF 不喜欢并抛出NotSupportedException

除了OrderBy,您可以在存储库类中使用以下帮助方法。它的作用是在需要时剥离Convert 表达式并动态调用Queryable.OrderBy 方法:

public static partial class EFExtensions
{
    public static IOrderedQueryable<T> SortBy<T>(this IQueryable<T> source, Expression<Func<T, object>> keySelector)
    {
        var body = keySelector.Body;
        if (body.NodeType == ExpressionType.Convert)
            body = ((UnaryExpression)keySelector.Body).Operand;
        var selector = Expression.Lambda(body, keySelector.Parameters);
        var orderByCall = Expression.Call(
            typeof(Queryable), "OrderBy", new[] { typeof(T), body.Type },
            source.Expression, Expression.Quote(selector));
        return (IOrderedQueryable<T>)source.Provider.CreateQuery(orderByCall);
    }
}

【讨论】:

  • 这真的很棒。我还添加了一个SortByDescending 方法。
  • 重写这样的表达式是否像反射一样“慢”?我假设不是,因为您不是在查询类型和元数据,而是在创建新的东西。对吗?
  • 我确信你能做到!虽然您也可以在上述方法中添加 bool descending 参数并在内部使用 "OrderBy" + (descending ? "Descending" : "") :)
  • 在这种情况下重写 lambda 不像反射,因为您正在重用已经构建的表达式(body 或 body.Operand)。 Expression.Call 呢,这正是 Queryable 方法所做的 - source
  • 是的,这是有道理的。再次感谢。
猜你喜欢
  • 1970-01-01
  • 2011-08-18
  • 1970-01-01
  • 2019-01-21
  • 2012-09-19
  • 2010-09-30
  • 1970-01-01
  • 2011-12-09
相关资源
最近更新 更多