【问题标题】:How to build a simple property selector expression in ef6如何在 ef6 中构建一个简单的属性选择器表达式
【发布时间】:2015-07-10 14:39:00
【问题描述】:

如何为这样的实体框架创建属性选择器?

public static List<T> StandardSearchAlgorithm<T>(this IQueryable<T> queryable, Func<T, string> property, string query)
{

    return queryable.Where(e => property(e).ToLower().IndexOf(query) > -1).ToList();

}

我希望调用代码能够像这样干净简单:

var usernameResults = _db.Users.StandardSearchAlgorithm(u => u.Username, query);

我收到“LINQ to Entities 不支持 LINQ 表达式节点类型 'Invoke'。”错误。我无法弄清楚如何构建表达式。

更新:

根据 MBoros 的回答,这是我最终得到的代码。效果很好。

表达式树的关键是理解表达式树就是将你通常在代码中编写的内容(如“e => e.Username.IndexOf(query)”)分解为一系列对象:“e”得到它自己的对象,“用户名”它自己的对象,“IndexOf()”它自己的对象,“查询”常量它自己的对象,等等。第二个关键是要知道你可以使用Expression类上的一系列静态方法来创建各种这些对象,如下所示。

    PropertyInfo pinfo = (PropertyInfo)((MemberExpression)property.Body).Member;
    ParameterExpression parameter = Expression.Parameter(typeof(T), "e");
    MemberExpression accessor = Expression.Property(parameter, pinfo);
    ConstantExpression queryString = Expression.Constant(query, typeof(string));
    ConstantExpression minusOne = Expression.Constant(-1, typeof(int));
    MethodInfo indexOfInfo = typeof(string).GetMethod("IndexOf", new[] { typeof(string) }); // easiest way to do this
    Expression indexOf = Expression.Call(accessor, indexOfInfo, queryString);
    Expression expression = Expression.GreaterThan(indexOf, minusOne);
    Expression<Func<T, bool>> predicate = Expression.Lambda<Func<T, bool>>(expression, parameter);
    //return predicate.Body.ToString(); // returns "e => e.Username.IndexOf(query) > -1" which is exactly what we want.

    var results = queryable.Where(predicate).ToList();
    return results;

现在我有一个真正的问题,但我会在一个单独的问题中提出。我的真实查询如下所示:

public static List<T> StandardSearchAlgorithm<T>(this IQueryable<T> queryable, Func<T, string> property, string query)
{

    return queryable.Where(e => property(e).IndexOf(query) > -1).Select(e=> new { Priority = property(e).IndexOf(query), Entity = e } ).ToList();

}

所以我需要构建一个返回匿名类型的表达式!!或者即使我创建了一个类来提供帮助,我也需要编写一个返回新对象的表达式。但我会将其包含在一个单独的问题中。

【问题讨论】:

  • 您可能需要查看PredicateBuilder 来帮助您完成此操作
  • 之前发布答案的人。那行得通!感谢神秘人删除了你的帖子。
  • 我删除了我的答案,因为它实际上并没有编译为 SQL,它只是在内存中。我正在研究如何使其正确编译为 sql。回来看看,希望我能得到一个有效的答案。
  • 我还没有找到一种能够在多个属性上重用ToLower().Contains() 逻辑的好方法。我确信有一种方法可以通过构建表达式来做到这一点,我还没有足够的使用表达式的经验来弄清楚它。我会谨慎使用已删除的答案,因为它不会编译为 SQL(据我所知),这意味着您将把整个表拉下来,然后过滤它,这不是您想要的去做。

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


【解决方案1】:

您不能在 sql 中如此简单地调用 CLR 委托。但是您可以将属性选择器作为表达式树传递,因此您的签名将是:

public static List<T> StandardSearchAlgorithm<T>(this IQueryable<T> queryable, Expression<Func<T, string>> property, string query)

调用看起来是一样的。但是现在你手里有了一个表情,你可以看看这个答案: Pass expression parameter as argument to another expression 它为您提供了简单地将表达式树放入另一个表达式树的工具。在您的情况下,它看起来像:

Expression<Func<T, bool>> predicate = e => property.AsQuote()(e).Contains(query);
predicate = predicate.ResolveQuotes();
return queryable.Where(predicate).ToList();

一旦你在那里,你仍然有 .ToLower().Contains() 调用(使用 .Contains 而不是 .IndexOf()> 1)。这实际上很棘手。通常数据库使用其默认排序规则,因此如果它设置为 CI(不区分大小写),那么它将以这种方式进行比较。如果您没有任何限制,并且可以调整数据库排序规则,我会这样做。在这种情况下,您可以省略 .ToLower() 调用。 否则请查看此分析器:https://stackoverflow.com/a/2433217/280562

【讨论】:

  • 感谢您提及数据库排序规则。事实证明,数据库设置为 CI,所以在这种情况下我可以完全忽略 ToLower()。这真的很有帮助。使用 Contains() 的问题在于,尽管我在问题中显示的代码可以根据 Contains() 来考虑,但实际查询有另一个需要 IndexOf() 的子句,所以即使我在查询中使用 Contains()以上我最终将需要制定 IndexOf() 解决方案。
  • 对于 linq 查询中的 MSSql,您可以尝试 SqlFunctions.CharIndex (msdn.microsoft.com/en-us/library/dd487160%28v=vs.110%29.aspx)。请注意,这只适用于理解这一点的提供者,例如,您将无法在内存中编译和执行表达式树!
猜你喜欢
  • 2019-03-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-11
  • 2015-01-03
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多