如何在使用LINQ to Entities和帮助方法时保持DRY?

让我们说我有一种特殊的方法来决定某些字符串是否“匹配”,如下所示:

public bool stringsMatch(string searchFor, string searchIn) { if (string.IsNullOrEmpty(searchFor)) { return true; } return searchIn != null && (searchIn.Trim().ToLower().StartsWith(searchFor.Trim().ToLower()) || searchIn.Contains(" " + searchFor)); } 

我想使用Linq To Entities和这个帮助器从数据库中提取匹配项。 但是,当我尝试这个:

 IQueryable blahs = query.Where(b => stringsMatch(searchText, b.Name); 

我得到“LINQ to Entities无法识别方法……”

如果我重新编写代码:

 IQueryable blahs = query.Where(b => string.IsNullOrEmpty(searchText) || (b.Name != null && (b.Name.Trim().ToLower().StartsWith(searchText.Trim().ToLower()) || b.Name.Contains(" " + searchText))); 

这在逻辑上是等价的,那么事情就可以了。 问题是代码不是可读的,我必须为我想要匹配的每个不同实体重写它。

至于我从这个问题中可以看出,我现在想做的事情是不可能的,但我希望我错过了什么,是吗?

如果您要过滤的所有’blahs’(类)具有相同的结构,则可以使用这样的简单方法。 主要区别在于它返回一个Linq应该能够解析的表达式,它会引入整个实例并过滤Name而不是仅引入字符串名称。

  public static Expression> BuildStringMatch(string searchFor) where T : IHasName { return b => string.IsNullOrEmpty(searchFor) || (b.Name != null && (b.Name.Trim().ToLower().StartsWith(searchFor.Trim().ToLower()) || b.Name.Contains(" " + searchFor))); } 

您可以像这样使用该方法:

  IQueryable blahs = query.Where(BuildStringMatch(searchText)); 

假设您要过滤的所有类都实现了一些接口,例如:

  public interface IHasName { string Name { get; } } 

如果你想过滤不同的属性,我不认为你可以用这样的简单代码做些什么。 我相信你需要自己用reflection构建表达式(或者在使用reflection的库的帮助下) – 它仍然可能但更难。

编辑:听起来你需要动态行为,所以我从dtb对这个问题的回答中借用了一些逻辑并得出了这个:

 public static Expression> BuildStringMatch(Expression> property, string searchFor) { var searchForExpression = Expression.Constant(searchFor, typeof(string)); return Expression.Lambda>( Expression.OrElse( Expression.Call(typeof(string), "IsNullOrEmpty", null, searchForExpression), Expression.AndAlso( Expression.NotEqual(property.Body, Expression.Constant(null, typeof(string))), Expression.OrElse( Expression.Call(Expression.Call(Expression.Call(property.Body, "Trim", null), "ToLower", null), "StartsWith", null, Expression.Call(Expression.Call(searchForExpression, "Trim", null), "ToLower", null)), Expression.Call(property.Body, "Contains", null, Expression.Call(typeof(string), "Concat", null, Expression.Constant(" "), searchForExpression)) ) ) ), property.Parameters ); } 

您会像以下一样使用它:

  IQueryable blahs2 = query.Where(BuildStringMatch(b => b.Name, searchText)); 

它冗长而冗长,但你可以看到它与用直接C#代码编写的原始方法类似。 注意:我没有测试这个代码,所以可能会有一些小问题 – 但这是一般的想法。

使用名为LINQKit的免费库(如@E​​ranga所述),此任务变得合理。 使用LINQKit我现在的代码如下:

 protected Expression> stringsMatch(string searchFor, Expression> searchIn) { if (string.IsNullOrEmpty(searchFor)) { return e => true; } return e => (searchIn.Invoke(e) != null && (searchIn.Invoke(e).Trim().ToLower().StartsWith(searchFor.Trim().ToLower()) || searchIn.Invoke(e).Contains(" " + searchFor))); } 

并且需要像这样调用(注意AsExpandable()调用)

 IQueryable blahs = query().AsExpandable().Where(StringsMatch(searchText, b => b.Name)); 

神奇的部分是searchIn.Invoke(e)调用和AsExpandable()的使用,它添加了一个允许它们工作的包装层。

原作者在此详细解释了AsExpandable()位。

请注意,我对表达式的某些细节仍然有些模糊,所以如果可以做得更好/更短/更清楚,请添加评论/编辑此答案。