我有一个模型优先的实体框架设计(4.4版本)
当我使用这样的代码加载它时:
PriceSnapshotSummary snapshot = db.PriceSnapshotSummaries.FirstOrDefault(pss => pss.Id == snapshotId);
快照已经加载了所有内容(即SnapshotPart,Quote,QuoteType),但DataInfo除外。现在查看SQL似乎是因为由于0..1关系,Quote对DataInfo没有FK。 但是,我原本预计Quote上的导航属性'DataInfo'仍会转到数据库来获取它。
我目前的工作是:
foreach (var quote in snapshot.ComponentQuotes)
{
var dataInfo = db.DataInfoes.FirstOrDefault(di => di.Quote.Id == quote.InstrumentQuote.Id);
quote.InstrumentQuote.DataInfo = dataInfo;
}
有没有更好的方法来实现这一目标?我以为EF会自动加载引用吗?
答案 0 :(得分:2)
此问题与基本linq构建块如何与Entity Framework交互有关。
采用以下(伪)代码:
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并且只在它需要之前执行数据库查询。这样可以大大加快速度,并且每次执行Where()
或Select()
时都可以进行表达式链接而无需访问数据库。副作用是如果对象在db范围之外使用,则在处理db之后执行sql语句。
要强制执行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关系),我们会在它运行之前遇到另一个碰撞:懒加载。
默认情况下是实体框架lazy loads实体,因此在需要之前不会从数据库中提取任何内容。同样,这会大大加快速度,因为没有它,每次调用数据库都可能将整个数据库带入内存;但是存在依赖于可用环境的问题。
您可以将EF设置为eager load(在您的模型中,转到属性并将'Lazy Loading Enabled'设置为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。在构建sql语句时,Include将使IQueryable包括所有指定的关系路径:
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之间是一个很好的折衷方案。
你可以做的另一件事是地图multiple tables to a single entity。在你的情况下,由于关系是1-0..1,你应该没有问题。