我正在尝试编写一个搜索函数,我可以传入一个Func,允许我指定要搜索的字符串属性。实体框架6抱怨我试图调用一个不允许的函数。是否有重写以下内容以便我可以在EF中使用它?
public List<Person> SearchPeople(string searchTerm, Func<Person, string> selector)
{
return myDbContext.Persons.Where(person => selector(person).Contains(searchTerm)).ToList();
}
LINQ to Entities中不支持LINQ表达式节点类型“Invoke”。
答案 0 :(得分:3)
您可以使用LinqKit库来解决您的问题(查看此link和this了解详情)。
public List<Person> SearchPeople(string searchTerm, Expression<Func<Person, string>> selector)
{
return myDbContext.Persons
.AsExpandable()
.Where(person => selector.Invoke(person).Contains(searchTerm))
.ToList();
}
AsExpandable
方法在DLINQ Table对象周围创建一个瘦包装器。感谢这个包装器,您可以使用名为Invoke
的第二个方法扩展Expression
类以调用lambda表达式,同时仍然可以将查询转换为T-SQL。这是有效的,因为在转换为表达式树时,包装器将所有出现的Invoke
方法替换为调用的lambda表达式的表达式树,并将这些表达式传递给能够将扩展查询转换为T-的DLINQ。 SQ L,
因此,您唯一需要做的就是将SearchPeople
方法的第二个参数类型更改为Expression<Func<Person,string>>
,然后您可以调用您的方法,如下所示:
Expression<Func<Person,string>> exp= person=>person.Name;
//...
var people=SearchPeople("Albert",exp);
答案 1 :(得分:3)
您尝试做的事情实际上比看起来更复杂,因为它涉及手动将Person
属性表达式与表示对string.Contains(searchTerm)
的调用的表达式组合。
何时可以直接编写完整的lambda表达式,如
p => p.FirstName.Contains(searchTerm)
......它并不难,因为编译器会为你完成所有繁重的工作。但是当你必须手动组合表达式片段时,就像在这种情况下一样,它很快就会变得混乱。
我没有尝试过octavioccl
的答案,但如果它像宣传的那样有效,那么这是一个非常优雅的解决方案,如果可能,我肯定会使用它。
然而,如果有什么,为了获得对编译器为我们所做的所有工作的一些赞赏,这里是你如何在没有任何外部库的情况下解决你的问题(使用一些C#6功能,但它可以很容易地调整为较旧的版本):
public List<Person> SearchPeople(string searchTerm, Expression<Func<Person, string>> personProperty)
{
// Note: Explanatory comments below assume that the "personProperty" lambda expression is:
// p => p.FirstName
// Get MethodInfo for "string.Contains(string)" method.
var stringContainsMethod = typeof(string).GetMethod(
nameof(string.Contains),
new Type[] { typeof(string) });
// Create a closure class around searchTerm.
// Using this technique allows EF to use parameter binding
// when building the SQL query.
// In contrast, simply using "Expression.Constant(searchTerm)",
// makes EF hard-code the string in the SQL, which is not usually desirable.
var closure = new { SearchTerm = searchTerm };
var searchTermProperty = Expression.Property(
Expression.Constant(closure), // closure
nameof(closure.SearchTerm)); // SearchTerm
// This forms the complete statement: p.FirstName.Contains(closure.SearchTerm)
var completeStatement = Expression.Call(
personProperty.Body, // p.FirstName
stringContainsMethod, // .Contains()
searchTermProperty); // closure.SearchTerm
// This forms the complete lambda: p => p.FirstName.Contains(closure.SearchTerm)
var whereClauseLambda = Expression.Lambda<Func<Person, bool>>(
completeStatement, // p.FirstName.Contains(closure.SearchTerm)
personProperty.Parameters[0]); // p
// Execute query using constructed lambda.
return myDbContext.Persons.Where(whereClauseLambda).ToList();
}
然后您可以调用这样的方法:
foreach (var person in SearchPeople("joe", p => p.FirstName))
{
Console.WriteLine($"First Name: {person.FirstName}, Last Name: {person.LastName}");
}