Entity Framework 导航 属性 preload/reuse
Entity Framework Navigation Property preload/reuse
当我期望可以从 EF 缓存中获取对象时,为什么 Entity Framework 正在执行查询?
有了这些简单的模型类:
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Content { get; set; }
public virtual Blog Blog { get; set; }
}
public class BlogDbContext : DbContext
{
public BlogDbContext() : base("BlogDbContext") {}
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
我分析了以下操作的查询
public class HomeController : Controller
{
public ActionResult Index()
{
var ctx = new BlogDbContext();
// expecting posts are retrieved and cached by EF
var posts = ctx.Posts.ToList();
var blogs = ctx.Blogs.ToList();
var wholeContent = "";
foreach (var blog in blogs)
foreach (var post in blog.Posts) // <- query is executed
wholeContent += post.Content;
return Content(wholeContent);
}
}
为什么 EF 不重新使用我已经通过 var posts = ctx.Posts.ToList();
语句获取的 Post
实体?
进一步说明:
一个现有的应用程序有一个 Excel 导出报告。数据是通过一个主要的 Linq2Sql 查询和一个包含树(~20)来获取的。然后它通过自动映射器映射,并添加来自手动缓存的附加数据(如果添加到主查询,以前会减慢执行速度)。
现在数据增长了,SQL服务器在尝试执行错误查询时崩溃:
The query processor ran out of internal resources and could not produce a query plan.
延迟加载会导致超过 100.000 个查询。所以我想我可以通过几个简单的查询预加载所有需要的数据,并让 EF 在延迟加载期间自动使用缓存中的对象。
我最初在 TSQL IN()
子句的限制方面遇到了其他问题,我用 MoreLinq 的 Batch 扩展解决了这些问题。
启用延迟加载后,EF 仍会重新加载集合导航属性。可能是因为 EF 不知道你是否真的加载了所有 Post。 EG代码如
var post = db.Posts.First();
var relatedPosts = post.Blog.Posts.ToList();
会很棘手,因为博客已经加载了一个 Post,但显然需要获取其他的。
在任何情况下,当依靠更改跟踪器来修复您的导航属性时,您都应该禁用延迟加载。 EG
using (var db = new BlogDbContext())
{
db.Configuration.LazyLoadingEnabled = false;
. . .
鉴于您拥有导航属性,请考虑在您的查询中利用它们来为 Automapper 提供一个动态对象以映射到您的 ViewModel/DTO 而不是您将依赖预加载的顶级实体或等待延迟加载。
这是通过对您的查询发出 .Select() 来完成的。使用一个简单的示例来提取订单详细信息,包括客户名称、订单行中的产品名称和数量列表,以及订单引用客户的交货地址,并且该客户有一个交货地址,订单行的集合, 每个都有一个产品...
var orderDetails = dbContext.Orders
.Where(o => /* Insert criteria */)
.Select(o => new
{
o.OrderId,
o.OrderNumber,
o.Customer.CustomerId,
CustomerName = x.Customer.FullName,
o.Customer.DeliveryAddress, // Address entity if no further dependencies, or extract fields/relations from the Address.
o.OrderLines.Select( ol = > new
{
ol.OrderLineId,
ProductName = ol.Product.Name,
ol.Quantity
}
}).ToList(); // Ready to feed into Automapper.
有了 ~20,您的 Select 无疑会涉及更多,但我们的想法是向 SQL 服务器提供一个查询以检索您想要的数据,然后您可以将其输入Automapper 导航任何子关系可以被 EF 展平或简化的位置,并 returned 以便您的映射器充实到生成的模型中。
随着系统的增长,您还需要考虑利用分页 /w Skip and Take 而不是 ToList,或者至少利用 Take 来确保 return 的数据量有上限。 ToList 是我在 EF 代码中寻找的主要性能巨魔,因为它的误用会杀死应用程序。
当我期望可以从 EF 缓存中获取对象时,为什么 Entity Framework 正在执行查询?
有了这些简单的模型类:
public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Content { get; set; }
public virtual Blog Blog { get; set; }
}
public class BlogDbContext : DbContext
{
public BlogDbContext() : base("BlogDbContext") {}
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
}
我分析了以下操作的查询
public class HomeController : Controller
{
public ActionResult Index()
{
var ctx = new BlogDbContext();
// expecting posts are retrieved and cached by EF
var posts = ctx.Posts.ToList();
var blogs = ctx.Blogs.ToList();
var wholeContent = "";
foreach (var blog in blogs)
foreach (var post in blog.Posts) // <- query is executed
wholeContent += post.Content;
return Content(wholeContent);
}
}
为什么 EF 不重新使用我已经通过 var posts = ctx.Posts.ToList();
语句获取的 Post
实体?
进一步说明:
一个现有的应用程序有一个 Excel 导出报告。数据是通过一个主要的 Linq2Sql 查询和一个包含树(~20)来获取的。然后它通过自动映射器映射,并添加来自手动缓存的附加数据(如果添加到主查询,以前会减慢执行速度)。
现在数据增长了,SQL服务器在尝试执行错误查询时崩溃:
The query processor ran out of internal resources and could not produce a query plan.
延迟加载会导致超过 100.000 个查询。所以我想我可以通过几个简单的查询预加载所有需要的数据,并让 EF 在延迟加载期间自动使用缓存中的对象。
我最初在 TSQL IN()
子句的限制方面遇到了其他问题,我用 MoreLinq 的 Batch 扩展解决了这些问题。
启用延迟加载后,EF 仍会重新加载集合导航属性。可能是因为 EF 不知道你是否真的加载了所有 Post。 EG代码如
var post = db.Posts.First();
var relatedPosts = post.Blog.Posts.ToList();
会很棘手,因为博客已经加载了一个 Post,但显然需要获取其他的。
在任何情况下,当依靠更改跟踪器来修复您的导航属性时,您都应该禁用延迟加载。 EG
using (var db = new BlogDbContext())
{
db.Configuration.LazyLoadingEnabled = false;
. . .
鉴于您拥有导航属性,请考虑在您的查询中利用它们来为 Automapper 提供一个动态对象以映射到您的 ViewModel/DTO 而不是您将依赖预加载的顶级实体或等待延迟加载。
这是通过对您的查询发出 .Select() 来完成的。使用一个简单的示例来提取订单详细信息,包括客户名称、订单行中的产品名称和数量列表,以及订单引用客户的交货地址,并且该客户有一个交货地址,订单行的集合, 每个都有一个产品...
var orderDetails = dbContext.Orders
.Where(o => /* Insert criteria */)
.Select(o => new
{
o.OrderId,
o.OrderNumber,
o.Customer.CustomerId,
CustomerName = x.Customer.FullName,
o.Customer.DeliveryAddress, // Address entity if no further dependencies, or extract fields/relations from the Address.
o.OrderLines.Select( ol = > new
{
ol.OrderLineId,
ProductName = ol.Product.Name,
ol.Quantity
}
}).ToList(); // Ready to feed into Automapper.
有了 ~20,您的 Select 无疑会涉及更多,但我们的想法是向 SQL 服务器提供一个查询以检索您想要的数据,然后您可以将其输入Automapper 导航任何子关系可以被 EF 展平或简化的位置,并 returned 以便您的映射器充实到生成的模型中。
随着系统的增长,您还需要考虑利用分页 /w Skip and Take 而不是 ToList,或者至少利用 Take 来确保 return 的数据量有上限。 ToList 是我在 EF 代码中寻找的主要性能巨魔,因为它的误用会杀死应用程序。