2011-08-15 26 views
8

比方說,我有決定是否某些字符串「匹配」,像這樣的一個特殊的方式:如何在使用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實體和這個輔助數據庫。然而,當我試試這個:

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

我得到「LINQ到實體無法識別方法......」

如果我重新寫代碼:

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這樣的問題,我想要做的事現在是不可能的,但我希望我失去了一些東西,是嗎?

+0

嘗試[Predicate Builder](http://www.albahari.com/nutshell/predicatebuilder.aspx) – Eranga

回答

4

使用一個名爲LINQKit的免費庫(如@E​​ranga所述),這個任務變得合理。使用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的。調用(e)調用並使用AsExpandable(),它添加了一個允許它們工作的包裝器層。

AsExpandable()位由原作者here詳細解釋。

請注意,對於表達式的某些細節,我還是有點朦朧,所以請添加評論/編輯此答案,如果它可以變得更好/更短/更清晰。

5

如果您要篩選的所有'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的回答有些邏輯this 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#代碼編寫的原始方法類似。注意:我沒有測試這個代碼,所以可能會有一些小問題 - 但這是一般的想法。

+0

感謝您的回答,我確實考慮過這樣的事情,但缺乏靈活性是錯誤的(不是我所有的想要匹配被稱爲「名稱」)。從更復雜的方式開始的任何提示? – Dan

+0

我編輯了我的答案以包含一些示例代碼。它看起來更復雜,但如果你只需要寫一次,也許它不會那麼糟糕。 –

+0

謝謝,這真的很有幫助(我只能讚揚一次)。不過,我在代碼庫中發現了一些其他代碼,它使用一個名爲LINQKit的庫來做一些非常相似的事情(我認爲)是一種更好的方式。我會添加一個新的答案與全部細節。 – Dan