实体框架导航属性预加载/重用

时间:2018-07-12 16:27:55

标签: entity-framework linq eager-loading

当我期望可以从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不重用我在Post语句中抓住的var posts = ctx.Posts.ToList();实体?

进一步的解释:

现有应用程序具有Excel导出报告。数据是通过主Linq2Sql查询获取的,该查询具有包含(〜20)的树。然后通过自动映射器进行映射,并添加来自手动缓存的附加数据(如果将其添加到主查询中,以前会减慢执行速度)。

现在,数据增长了,并且尝试执行错误查询时SQL Server崩溃:

  

查询处理器用尽了内部资源,无法生成查询计划。

延迟加载将导致> 100.000个查询。因此,我想我可以通过一些简单的查询来预加载所有必需的数据,并让EF在延迟加载期间自动使用缓存中的对象。

最初,我对TSQL IN()子句的限制还有其他问题,我通过MoreLinq的Batch扩展解决了该问题。

2 个答案:

答案 0 :(得分:0)

启用延迟加载后,EF仍将重新加载集合导航属性。可能是因为EF不知道您是否真的加载了所有帖子。像这样的EG代码

   var post = db.Posts.First();
   var relatedPosts = post.Blog.Posts.ToList();

会很棘手,因为Blog会已经加载了一个帖子,但是显然需要提取其他帖子。

无论如何,当依靠变更跟踪器修正导航属性时,无论如何都应该禁用延迟加载。 EG

using (var db = new BlogDbContext())
{
    db.Configuration.LazyLoadingEnabled = false;
    . . .

答案 1 :(得分:0)

鉴于您具有导航属性,请看一下如何在查询中利用它们来为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 Server提供查询以仅检索您想要的数据,然后您可以将其输入到Automapper中以导航任何子关系可以由EF展平或简化,然后返回给您的映射器以充实到生成的模型中。

随着系统的不断发展,您还需要考虑使用分页/ w跳过并获取而不是ToList,或者至少要利用获取来确保返回的数据量没有上限。 ToList是我在EF代码中寻找的主要性能巨魔,因为它的滥用会杀死应用程序。