使用 Func 属性 选择器和 Entity Framework 6
Using Func property selector with Entity Framework 6
我正在尝试编写一个搜索函数,我可以在其中传递一个允许我指定要搜索的字符串 属性 的 Func。 Entity Framework 6 抱怨我正在尝试调用不允许的函数。是否有重写以下内容以便我可以将其与 EF 一起使用?
public List<Person> SearchPeople(string searchTerm, Func<Person, string> selector)
{
return myDbContext.Persons.Where(person => selector(person).Contains(searchTerm)).ToList();
}
The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
您可以使用 LinqKit library to solve your issue (Check this link and 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
class 来调用 lambda 表达式,同时仍然可以将查询转换为 T-SQL。这是有效的,因为当转换为表达式树时,包装器用调用的 lambda 表达式的表达式树替换所有出现的 Invoke
方法,并将这些表达式传递给能够将扩展查询转换为的 DLINQ T-SQL.
所以您唯一需要做的就是将 SearchPeople
方法的第二个参数类型更改为 Expression<Func<Person,string>>
,然后您可以调用您的方法,如下所示:
Expression<Func<Person,string>> exp= person=>person.Name;
//...
var people=SearchPeople("Albert",exp);
您尝试做的事情实际上比看起来更复杂,因为它涉及手动组合 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}");
}
我正在尝试编写一个搜索函数,我可以在其中传递一个允许我指定要搜索的字符串 属性 的 Func。 Entity Framework 6 抱怨我正在尝试调用不允许的函数。是否有重写以下内容以便我可以将其与 EF 一起使用?
public List<Person> SearchPeople(string searchTerm, Func<Person, string> selector)
{
return myDbContext.Persons.Where(person => selector(person).Contains(searchTerm)).ToList();
}
The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.
您可以使用 LinqKit library to solve your issue (Check this link and 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
class 来调用 lambda 表达式,同时仍然可以将查询转换为 T-SQL。这是有效的,因为当转换为表达式树时,包装器用调用的 lambda 表达式的表达式树替换所有出现的 Invoke
方法,并将这些表达式传递给能够将扩展查询转换为的 DLINQ T-SQL.
所以您唯一需要做的就是将 SearchPeople
方法的第二个参数类型更改为 Expression<Func<Person,string>>
,然后您可以调用您的方法,如下所示:
Expression<Func<Person,string>> exp= person=>person.Name;
//...
var people=SearchPeople("Albert",exp);
您尝试做的事情实际上比看起来更复杂,因为它涉及手动组合 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}");
}