这个问题与基本 linq 构建块如何与实体框架交互有关。
采取以下(伪)代码:
IQueryable<Address> addresses;
Using (var db = new ObjectContext()) {
addresses = db.Users.Addresses.Where(addr => addr.Number > 1000);
}
addresses.Select(addr => Console.WriteLine(addr.City.Name));
这看起来不错,但会抛出运行时错误,因为有一个名为 IQueryable 的接口。
IQueryable实现IEnumerable并为表达式和提供程序添加信息。这基本上允许它针对数据库构建和执行 sql 语句,而不必像在 IEnumerable 上那样在获取数据和迭代它们时加载整个表。
由于 linq 将表达式的执行推迟到使用之前,它会将 IQueryable 表达式编译为 SQL,并仅在需要之前执行数据库查询。这大大加快了速度,并允许表达式链接,而无需每次执行 a Where()
or时都进入数据库Select()
。副作用是如果对象在db的范围之外使用,那么sql语句在db被处理掉后执行。
要强制执行 linq,可以使用 ToList,如下所示:
IQueryable<Address> addresses;
Using (var db = new ObjectContext()) {
addresses = db.Users.Addresses.Where(addr => addr.Number > 1000).ToList();
}
addresses.Select(addr => Console.WriteLine(addr.City.Name));
这将强制 linq 对 db 执行表达式并获取所有大于一千的地址。如果您需要访问地址表中的字段,这一切都很好,但是由于我们想要获取城市的名称(类似于您的 1..1 关系),我们将在它运行之前遇到另一个问题:延迟加载。
实体框架默认延迟加载实体,因此在需要之前不会从数据库中获取任何内容。同样,这大大加快了速度,因为如果没有它,对数据库的每次调用都可能将整个数据库带入内存;但存在取决于可用上下文的问题。
您可以将 EF 设置为预加载(在您的模型中,转到属性并将“启用延迟加载”设置为 False),但这会带来很多您可能不使用的信息。
解决此问题的最佳方法是执行 db 范围内的所有内容:
IQueryable<Address> addresses;
Using (var db = new ObjectContext()) {
addresses = db.Users.Addresses.Where(addr => addr.Number > 1000);
addresses.Select(addr => Console.WriteLine(addr.City.Name));
}
我知道这是一个非常简单的示例,但在现实世界中,您可以使用像 ninject 这样的 DI 容器来处理您的依赖项,并在应用程序的整个执行过程中让您的数据库可供您使用。
这给我们留下了Include。Include 将使 IQueryable 在构建 sql 语句时包含所有指定的关系路径:
IQueryable<Address> addresses;
Using (var db = new ObjectContext()) {
addresses = db.Users.Addresses.Include("City").Where(addr => addr.Number > 1000).ToList;
}
addresses.Select(addr => Console.WriteLine(addr.City.Name));
这将起作用,这是在加载整个数据库和必须重构整个项目以支持 DI 之间的一个很好的折衷。
您可以做的另一件事是将多个表映射到单个实体。在您的情况下,由于关系是 1-0..1,因此您应该没有问题。