[英]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.