Dan*_*Dan 8 c# linq-to-entities entity-framework
让我们说我有一种特殊的方式来决定某些字符串是否"匹配",如下所示:
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));
}
Run Code Online (Sandbox Code Playgroud)
我想使用Linq To Entities和这个帮助器从数据库中提取匹配项.但是,当我尝试这个:
IQueryable<Blah> blahs = query.Where(b => stringsMatch(searchText, b.Name);
Run Code Online (Sandbox Code Playgroud)
我得到"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)));
Run Code Online (Sandbox Code Playgroud)
这在逻辑上是等价的,那么事情就可以了.问题是代码不是可读的,我必须为我想要匹配的每个不同实体重新编写代码.
至于我从这个问题中可以看出,我现在想做的事情是不可能的,但我希望我错过了什么,是吗?
如果您要过滤的所有'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)));
}
Run Code Online (Sandbox Code Playgroud)
您可以像这样使用该方法:
IQueryable<Blah> blahs = query.Where(BuildStringMatch<Blah>(searchText));
Run Code Online (Sandbox Code Playgroud)
假设您要过滤的所有类都实现了一些接口,例如:
public interface IHasName
{
string Name { get; }
}
Run Code Online (Sandbox Code Playgroud)
如果你想过滤不同的属性,我不认为你可以用这样的简单代码做些什么.我相信你需要自己用反射构建表达式(或者在使用反射的库的帮助下) - 它仍然可能但更难.
编辑:听起来你需要动态行为,所以我从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
);
}
Run Code Online (Sandbox Code Playgroud)
您会像以下一样使用它:
IQueryable<Blah> blahs2 = query.Where(BuildStringMatch<Blah>(b => b.Name, searchText));
Run Code Online (Sandbox Code Playgroud)
它冗长而冗长,但你可以看到它与用直接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)));
}
Run Code Online (Sandbox Code Playgroud)
并且需要像这样调用(注意 AsExpandable() 调用)
IQueryable<Blah> blahs = query().AsExpandable().Where(StringsMatch(searchText, b => b.Name));
Run Code Online (Sandbox Code Playgroud)
神奇的部分是 searchIn.Invoke(e) 调用和 AsExpandable() 的使用,它添加了一个允许它们工作的包装层。
原作者在这里详细解释了 AsExpandable() 位。
请注意,我对表达式的一些细节仍然有点模糊,所以如果可以做得更好/更短/更清晰,请添加评论/编辑这个答案。
| 归档时间: |
|
| 查看次数: |
1004 次 |
| 最近记录: |