【问题标题】:How to stay DRY whilst using LINQ to Entities and helper methods?如何在使用 LINQ to Entities 和辅助方法时保持 DRY?
【发布时间】:2011-08-19 00:04:40
【问题描述】:

假设我有一种特殊的方式来确定某些字符串是否“匹配”,如下所示:

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<Blah> blahs = query.Where(b => stringsMatch(searchText, b.Name);

我收到“LINQ to Entities 无法识别该方法...”

如果我将代码重写为:

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

这在逻辑上是等价的,然后一切正常。问题是代码不那么可读,我必须为每个我想匹配的不同实体重新编写它。

据我从this one 之类的问题中可以看出,目前我想做的事情是不可能的,但我希望我错过了什么,不是吗?

【问题讨论】:

标签: c# entity-framework linq-to-entities


【解决方案1】:

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

    public static Expression<Func<T, bool>> BuildStringMatch<T>(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<Blah> blahs = query.Where(BuildStringMatch<Blah>(searchText));

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

    public interface IHasName
    {
        string Name { get; }
    }

如果你想过滤不同的属性,我认为你不能用这样的简单代码来做这件事。我相信您需要自己使用反射(或借助使用反射的库)构建表达式 - 这仍然是可能的,但要困难得多。

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

public static Expression<Func<T, bool>> BuildStringMatch<T>(Expression<Func<T, string>> property, string searchFor)
{
    var searchForExpression = Expression.Constant(searchFor, typeof(string));
    return
        Expression.Lambda<Func<T, bool>>(
            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<Blah> blahs2 = query.Where(BuildStringMatch<Blah>(b => b.Name, searchText));

它很长而且很冗长,但您可以看到它与直接用 C# 代码编写的原始方法有多么相似。注意:我没有测试这段代码,所以可能会有一些小问题 - 但这是一般的想法。

【讨论】:

  • 感谢您的回答,我确实考虑过这样的事情,但缺乏灵活性令人反感(并非我想要匹配的所有内容都称为“名称”)。关于从更复杂的方式开始的任何提示?
  • 我已经编辑了我的答案以包含一些示例代码。它当然看起来更复杂,但如果你只需要写一次......也许它不会那么糟糕。
  • 谢谢,这真的很有帮助(可惜我只能投票一次)。但是,我在我们的代码库中发现了一些其他代码,它们使用名为 LINQKit 的库以(我认为)更简洁的方式执行非常相似的操作。我将添加一个包含完整详细信息的新答案。
【解决方案2】:

使用名为LINQKit(@Eranga 提到)的免费可用库,这项任务变得合理。使用 LINQKit,我现在的代码如下所示:

protected Expression<Func<T, bool>> stringsMatch(string searchFor, Expression<Func<T, string>> 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<Blah> blahs = query().AsExpandable().Where(StringsMatch(searchText, b => b.Name));

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多