Entity Framework - 为集合中的成员加载特定的导航 属性
Entity Framework - Loading specific Navigational Property for members in collection
我发现了与使用 .Include
等加载相关的各种问题,但似乎 SQL 形状查询变成了一个巨大的连接,这意味着如果我正在获取客户信息,并且该客户拥有 1000 件商品,而我拥有
context.Customers.Include(c=> c.Inventory).Where(c=>c.ID == id);
//side note, WHY can't I use .Find() after Include?
我将获得 1000 行相同的客户信息以及商品信息,而不是 1 条客户记录和多 table 集中的 1000 件商品。这看起来效率很低,并且会导致一些非常慢的查询。
所以如果我有一组客户:
//get all customers in TX
var texasCustomers = context.Customers.Where(c=> c.State == "TX");
我想在导出到 XLSX 时循环遍历它们:
foreach (var c in texasCustomers) {
//compile row per item SKU
foreach(var sku in c.Inventory.GroupBy(i=>i.SKU)) {
xlsx.SetCellValue(row, col, c.Name);
//output more customer info here
xlsx.SetCellValue(row, col, sku.Key);
xlsx.SetCellValue(row, col, sku.Sum(i=>i.Quantity));
}
}
这会生成对 'Inventory' table PER 客户的查询。这是一个快速查询,但是当您执行 SAME 查询 1000 次时,它会变得非常慢。
所以我做了这样的事情:
//get customer key
var customerids = texasCustomers.Select(c=> c.ID).ToArray();
var inventories = context.Inventories.Where(i=> customerids.Contains(i.CustomerID)).ToList();
...现在我的导出循环看起来更像这样...第一个示例中在导航 属性 上运行的内部循环变成了针对预建清单对象列表的内存中 linq 过滤器:
foreach (var c in texasCustomers) {
//compile row per item SKU
foreach(var sku in inventories.Where(i=> i.CustomerID == c.ID)) {
xlsx.SetCellValue(row, col, c.Name);
//output more customer info and then the sku info
xlsx.SetCellValue(row, col, sku.Key);
xlsx.SetCellValue(row, col, sku.Sum(i=>i.Quantity));
}
}
这成功地解决了 'query per loop' 问题,但有明显的缺点……感觉不对。
那么,我错过了什么?让我执行类似操作的秘密 EF 功能在哪里:
texasCustomers.LoadAll(c=> c.Inventories);
一次 到 "populate" 集合成员的所有导航属性?还是我从错误的角度接近问题?
有没有一种方法可以构造查询以使 EF 生成 SQL 而不会变成单个巨大的非规范化 table?
其实我觉得你应该把它分开。
一个 table 给客户,另一个给订单,另一个给 OrderItens。
Orders 是头部,OrderItens 是 body(像这样)
并建立它之间的关系。
之后,您应该将整个 children 加载到内存中并对其应用 First() 函数。
没有秘密的 EF 功能可以让您完全按照自己的意愿行事,但是有一个接近于 navigation 属性 fix up 的东西可以填充导航如果相关实体已经加载到上下文中,即使没有 Include
实体化实体的属性。
因此您可以先加载相关库存如下:
texasCustomers.SelectMany(c => c.Inventories).Load();
然后执行并迭代主查询:
foreach (var c in texasCustomers)
{
var inventories = c.Inventories; // must be there
// ...
}
但是为了避免在访问导航时延迟加载 属性,请确保在执行上述所有操作之前禁用延迟加载,方法是在开头插入以下行:
context.Configuration.ProxyCreationEnabled = false;
我忘记提及的一个重要细节是,使用上述技术,如果没有相关实体,导航 属性 将保持 null
而不是像正常用法那样返回空列表,因此请确保检查以包括 null
检查或在访问时使用 ?? Enumerable.Empty<Inventory>()
。
我发现了与使用 .Include
等加载相关的各种问题,但似乎 SQL 形状查询变成了一个巨大的连接,这意味着如果我正在获取客户信息,并且该客户拥有 1000 件商品,而我拥有
context.Customers.Include(c=> c.Inventory).Where(c=>c.ID == id);
//side note, WHY can't I use .Find() after Include?
我将获得 1000 行相同的客户信息以及商品信息,而不是 1 条客户记录和多 table 集中的 1000 件商品。这看起来效率很低,并且会导致一些非常慢的查询。
所以如果我有一组客户:
//get all customers in TX
var texasCustomers = context.Customers.Where(c=> c.State == "TX");
我想在导出到 XLSX 时循环遍历它们:
foreach (var c in texasCustomers) {
//compile row per item SKU
foreach(var sku in c.Inventory.GroupBy(i=>i.SKU)) {
xlsx.SetCellValue(row, col, c.Name);
//output more customer info here
xlsx.SetCellValue(row, col, sku.Key);
xlsx.SetCellValue(row, col, sku.Sum(i=>i.Quantity));
}
}
这会生成对 'Inventory' table PER 客户的查询。这是一个快速查询,但是当您执行 SAME 查询 1000 次时,它会变得非常慢。
所以我做了这样的事情:
//get customer key
var customerids = texasCustomers.Select(c=> c.ID).ToArray();
var inventories = context.Inventories.Where(i=> customerids.Contains(i.CustomerID)).ToList();
...现在我的导出循环看起来更像这样...第一个示例中在导航 属性 上运行的内部循环变成了针对预建清单对象列表的内存中 linq 过滤器:
foreach (var c in texasCustomers) {
//compile row per item SKU
foreach(var sku in inventories.Where(i=> i.CustomerID == c.ID)) {
xlsx.SetCellValue(row, col, c.Name);
//output more customer info and then the sku info
xlsx.SetCellValue(row, col, sku.Key);
xlsx.SetCellValue(row, col, sku.Sum(i=>i.Quantity));
}
}
这成功地解决了 'query per loop' 问题,但有明显的缺点……感觉不对。
那么,我错过了什么?让我执行类似操作的秘密 EF 功能在哪里:
texasCustomers.LoadAll(c=> c.Inventories);
一次 到 "populate" 集合成员的所有导航属性?还是我从错误的角度接近问题?
有没有一种方法可以构造查询以使 EF 生成 SQL 而不会变成单个巨大的非规范化 table?
其实我觉得你应该把它分开。
一个 table 给客户,另一个给订单,另一个给 OrderItens。
Orders 是头部,OrderItens 是 body(像这样)
并建立它之间的关系。
之后,您应该将整个 children 加载到内存中并对其应用 First() 函数。
没有秘密的 EF 功能可以让您完全按照自己的意愿行事,但是有一个接近于 navigation 属性 fix up 的东西可以填充导航如果相关实体已经加载到上下文中,即使没有 Include
实体化实体的属性。
因此您可以先加载相关库存如下:
texasCustomers.SelectMany(c => c.Inventories).Load();
然后执行并迭代主查询:
foreach (var c in texasCustomers)
{
var inventories = c.Inventories; // must be there
// ...
}
但是为了避免在访问导航时延迟加载 属性,请确保在执行上述所有操作之前禁用延迟加载,方法是在开头插入以下行:
context.Configuration.ProxyCreationEnabled = false;
我忘记提及的一个重要细节是,使用上述技术,如果没有相关实体,导航 属性 将保持 null
而不是像正常用法那样返回空列表,因此请确保检查以包括 null
检查或在访问时使用 ?? Enumerable.Empty<Inventory>()
。