簡體   English   中英

如何在將 LINQ 用於實體和輔助方法時保持干燥?

[英]How to stay DRY whilst using LINQ to Entities and helper methods?

假設我有一種特殊的方式來確定某些字符串是否“匹配”,如下所示:

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)));

這在邏輯上是等價的,然后一切正常。 問題是代碼不那么可讀,我必須為我想要匹配的每個不同實體重新編寫它。

據我從這樣的問題中可以看出,目前我想做的事情是不可能的,但我希望我錯過了一些東西,是嗎?

如果您要過濾的所有“blahs”(類)都具有相同的結構,則可以使用這樣的簡單方法。 主要區別在於它返回 Linq 應該能夠解析的表達式,它引入了整個實例並在 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; }
    }

如果你想過濾不同的屬性,我認為你不能用這樣的簡單代碼來做這件事。 我相信您需要自己通過反射(或在使用反射的庫的幫助下)構建表達式 - 這仍然是可能的,但要困難得多。

編輯:聽起來你需要動態行為,所以我從dtb這個問題的回答中借用了一些邏輯並想出了這個:

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的免費可用庫(如 @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() 的使用,它添加了一個允許它們工作的包裝層。

AsExpandable() 位由原作者在這里詳細解釋。

請注意,我對表達式的某些細節仍然有些模糊,因此如果可以使其更好/更短/更清晰,請添加評論/編輯此答案。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM