将Func属性选择器与Entity Framework 6一起使用

时间:2015-08-28 15:09:28

标签: c# linq entity-framework linq-to-entities

我正在尝试编写一个搜索函数,我可以传入一个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”。

2 个答案:

答案 0 :(得分:3)

您可以使用LinqKit库来解决您的问题(查看此linkthis了解详情)。

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}");
}