我正在使用System.Data.Entity
命名空间,因此我可以将lambda表达式传递给Linq Include方法。
public ICollection<MyEntity> FindAll()
{
using (var ctx = new MyEntityContext())
{
return ctx.MyEntity.Include(x => x.SomeLazyLoadedValue).ToList();
}
}
当我在其他方法中使用Where语句时,我可以像这样传递一个参数:
public ICollection<MyEntity> FindAllBy(Func<MyEntity, bool> criteria)
{
using (var ctx = new MyEntityContext())
{
return ctx.MyEntity.Where(criteria).ToList();
}
}
但是,在Include中尝试相同的操作不起作用:
public ICollection<MyEntity> FindAll(Func<MyEntity, bool> criteria)
{
using (var ctx = new MyEntityContext())
{
return ctx.MyEntity.Include(criteria).ToList();
}
}
如果您尝试这样做,Visual Studio会抱怨它
Cannot convert from 'System.Func<MyEntity, bool>' to 'string'
如何将lambda传递给Include方法?
答案 0 :(得分:7)
您的代码存在一些问题。例如,您的FindAllBy
不执行sql WHERE
查询,而是加载数据库中的所有条目,然后根据您的criteria
过滤内存。要理解为什么会这样,请看看以下内容:
int a = 5;
long b = 5;
现在,很明显这里发生了什么,但它仍然非常重要。编译器读取以下代码并生成两个变量。一个整数和一个长整数,两者的值都设置为数字5.但是,这两个数字的值是不同的,即使它们(在源代码中)设置为相同的东西。一个是32位,另一个是64位。
现在,我们来看看以下代码:
Func<int, string> a = num => num.ToString();
Expr<Func<int, string>> b = num => num.ToString();
这里发生了同样的事情(或多或少)。在第一种情况下,C#编译器看到您需要谓词(Func<int, string>
谓词),而第二个值是Expr<Func<int, string>>
,即使值写得相同。但是,与第一个例子相反,这里的最终结果大不相同。
谓词被编译为编译器生成的类的方法。它的编译方式与其他任何代码一样,只是允许您删除一堆样板。另一方面,表达式是写入的实际代码的内存表示。例如,在这种情况下,表达式可能看起来类似于Call(int.ToString, $1)
。这可以被其他代码读取并转换为例如SQL,然后用于查询数据库。
现在,回到你的问题。 EntityFramework向您发送IQueryable<T>
个实例,而这些实例又继承IEnumerable<T>
。无论何时枚举可枚举,它都会查询数据库。
接受委托的所有扩展方法都在IEnumerable
上定义,因此在运行谓词之前查询数据库。这就是为什么你需要确保选择正确的方法 - 重载。
编辑(回答评论)]
为了澄清一点,我将举几个例子。比如说我们有一个User
类,其中包含FirstName
,LastName
和Age
,而db集合简称为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的人。
因此传递Func
并不一定意味着它会查询整个数据库。它只是意味着它在该点停止构建查询并执行它。
关于下一个问题,Expression<Func<T,bool>>
不是一个普遍的答案。它意味着“一个带有T并返回bool的表达式”。在某些情况下,比如启动整个问题的.Include
,你不想返回一个bool。您想要返回任何想要包含的内容。因此,例如,如果我们回到我们的用户示例,并修改引用其他用户的用户类的Father
属性,并且我们想要包含它,那么我们会在常规代码中执行
db.Include(u => u.Father);
现在。在此处,u
是用户,返回值u.Father
也是用户,因此在这种情况下,u => u.Father
为Expr<Func<User, User>>
或Expr<Func<User, object>>
(我不知道)知道实体框架.Include
是接受通用值还是object
s)。
所以你的FindAll
函数应该是这样的:
public ICollection<TData> FindAll<TInclude>(Expr<Func<TData, TInclude>> include) {
using (var ctx = new TContext()) {
return ctx.T.Include(include).ToList();
}
}
虽然,老实说,这看起来非常怪异的代码,并且你可能会对你的模型做一些奇怪的事情,因为你已经(例如)将它们命名为T
和{{1 }}。我的猜测是你需要仔细阅读泛型在C#中的工作原理。