将lambda参数传递给include语句 [英] Pass a lambda parameter to an include statement

查看:651
本文介绍了将lambda参数传递给include语句的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用System.Data.Entity命名空间,因此我可以将lambda表达式传递给Linq Include方法.

I am using the System.Data.Entity namespace, so I can pass lambda expressions to the Linq Include method.

public ICollection<MyEntity> FindAll()
    {
        using (var ctx = new MyEntityContext())
        {
            return ctx.MyEntity.Include(x => x.SomeLazyLoadedValue).ToList();
        }
    }

当我在其他方法中使用Where语句时,可以像这样将参数传递给它:

When I'm using a Where statement in a different method, I can pass a parameter to it like so:

public ICollection<MyEntity> FindAllBy(Func<MyEntity, bool> criteria)
    {
        using (var ctx = new MyEntityContext())
        {
            return ctx.MyEntity.Where(criteria).ToList();
        }
    }

但是,在Include中尝试相同的操作不起作用:

However, trying the same thing in an Include does not work:

public ICollection<MyEntity> FindAll(Func<MyEntity, bool> criteria)
    {
        using (var ctx = new MyEntityContext())
        {
            return ctx.MyEntity.Include(criteria).ToList();
        }
    }

如果您尝试这样做,Visual Studio会抱怨它

If you try this, Visual Studio will complain that it

Cannot convert from 'System.Func<MyEntity, bool>' to 'string'

如何将lambda传递给Include方法?

How do I pass a lambda to the Include method?

推荐答案

您的代码存在一些问题.例如,您的FindAllBy不执行sql WHERE查询,而是加载数据库中的所有条目,然后根据criteria过滤内存中的内容.要了解为什么会这样,请看以下内容:

There are a few problems with your code. For instance, your FindAllBy does not do a sql WHERE query, instead it loads all the entries in your database, and then filter in-memory based on your criteria. To understand why this is like so take a look at the following:

int a = 5;
long b = 5;

现在,很明显这里发生了什么,但这仍然很重要.编译器读取以下代码并产生两个变量.一个整数和一个长整数,两者的值均设置为数字5.但是,即使这两个数字的值(在源代码中)设置为相同的值,它们的值也不同.一个是32位,另一个是64位.

Now, it's quite obvious what's happening here, but it's still quite important. The compiler reads the following code and produces two variables. One integer and one long integer, both with values set to the number 5. However, the values of these two numbers are different, even though they are set (in the source code) to the same thing. One is 32-bit, and the other is 64-bit.

现在,让我们看一下以下代码:

Now, let's take a look at the following code:

Func<int, string> a = num => num.ToString();
Expr<Func<int, string>> b = num => num.ToString();

同一件事(或多或少)正在发生.在第一种情况下,C#编译器会看到您需要一个谓词(一个Func<int, string>谓词),而第二个值是一个Expr<Func<int, string>>,即使这些值的写法相同.但是,与第一个示例相反,此处的最终结果有很大不同.

Here the same thing (more or less) is happening. In the first case, the C# compiler sees you want a predicate (a Func<int, string> predicate), whereas the second value is a Expr<Func<int, string>> even though the values are written the same. However, as opposed to the first example, the end result here is vastly different.

将谓词作为编译器生成的类上的方法进行编译.它像任何其他代码一样被编译,并且仅允许您删除一堆样板文件.另一方面,表达式是所写实际代码的内存表示形式.例如,在这种情况下,表达式可能看起来类似于Call(int.ToString, $1).可以通过其他代码读取此信息,并将其翻译为例如SQL,然后将其用于查询数据库.

A predicate is compiled as a method on a compiler-generated class. It's compiled just as any other code, and simply allows you to remove a bunch of boilerplate. A expression on the other hand is a in-memory representation of the actual code written. In this case, for instance, the expression might look something akin to Call(int.ToString, $1). This can be read by other code and translated to for instance SQL which is then used to query your database.

现在,回到您的问题. EntityFramework向您提供IQueryable<T>实例,这些实例又继承了IEnumerable<T>.每当枚举枚举时,它都会查询数据库.

Now, back to your problem. EntityFramework hands you IQueryable<T> instances, which in turn inherit IEnumerable<T>. Whenever you enumerate over the enumerable, it queries the database.

所有接受委托的扩展方法都是在IEnumerable上定义的,因此可以在运行谓词之前 来查询数据库.这就是为什么您需要确保选择正确的方法重载的原因.

All the extension-methods that accept delegates are defined on IEnumerable and thus query your database before running the predicate. This is why you need to make sure to select the right method-overloads.

编辑(以回答评论)]
为了澄清更多,我将举一些例子.假设我们有一个User类,它包含FirstNameLastNameAge,而db集合简称为db.

Edit (to answer comment)]
To clarify a bit more I'm going to make a few examples. Say for instance that we have a User class that cointains FirstName, LastName and Age, and the db collection is simply called db.

Expr<Func<User, bool>> olderThan10 = u => u.Age > 10;
Func<User, bool> youngerThan90 = u => u.Age < 90;
var users = db.Where(olderThan10).Where(youngerThan90);

这将导致SQL查找所有10岁以上的用户,之后它将在内存中过滤掉所有90岁以上的用户.

This would result in SQL that finds all users that are older than 10, after which it would in-memory filter away everyone that was older than or equal to 90.

因此,传递Func并不一定意味着它查询整个数据库.这只是意味着它会在此时停止在查询上构建,并执行它.

So passing a Func doesn't necessarily mean it queries the whole database. It just means it stops building on the query at that point, and executes it.

关于下一个问题,Expression<Func<T,bool>>不是通用答案.它的意思是一个带有T并返回布尔值的表达式".在某些情况下,例如.Include引发了整个问题,您不想返回布尔值.您想返回想要包含的任何内容.因此,例如,如果我们回到用户示例,并在引用另一个用户的用户类上修改Father属性,并希望将其包含在常规代码中,我们会这样做

As for the next question, Expression<Func<T,bool>> is not a universal answer. It means "a expression that takes a T and returns a bool". In some cases, like .Include which started this whole question, you don't want to return a bool. You want to return whatever you want to include. So for instance, if we go back to our example of users, and amend a Father property on the user class which references another user, and we want to include it, in regular code we'd do

db.Include(u => u.Father);

现在.在这里,u是用户,返回值u.Father也是用户,因此在这种情况下,u => u.FatherExpr<Func<User, User>>Expr<Func<User, object>>(我不知道entry-framework .Include是否接受通用值或简称object.

Now. Here, u is a User, and the return value u.Father is also a user, so in this case u => u.Father is Expr<Func<User, User>> or Expr<Func<User, object>> (I don't know if entity-framework .Include accepts generic values or simply objects).

所以您的FindAll函数应该看起来像这样:

So your FindAll function should probably look like this:

public ICollection<TData> FindAll<TInclude>(Expr<Func<TData, TInclude>> include) {
    using (var ctx = new TContext()) {
        return ctx.T.Include(include).ToList();
    }
}

尽管,说实话,这看起来很奇怪,并且鉴于您已经(例如)将它们分别命名为TTContext,所以您对模型所做的其他事情很奇怪.我的猜测是,您需要了解泛型在C#中的工作原理.

Though, to be honest, this is pretty weird looking code, and it's likely that you're doing something else weird with your models given that you've (for instance) named them T and TContext. My guess is that you need to read up a bit on how generics works in C#.

这篇关于将lambda参数传递给include语句的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆