实体框架 - 参考未加载

时间:2013-10-02 04:47:45

标签: c# entity-framework ef-model-first

我有一个模型优先的实体框架设计(4.4版本)

Entity ER diagram 当我使用这样的代码加载它时:

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会自动加载引用吗?

1 个答案:

答案 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,你应该没有问题。