【问题标题】:Entity Framework Navigation Property preload/reuse实体框架导航属性预加载/重用
【发布时间】:2018-07-12 16:27:55
【问题描述】:

当我期望可以从 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 Server 崩溃并出现错误:

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

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

我最初在 TSQL IN() 子句的限制方面遇到了其他问题,我通过 MoreLinq 的 Batch 扩展解决了这些问题。

【问题讨论】:

    标签: entity-framework linq eager-loading


    【解决方案1】:

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

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

    这会很棘手,因为博客已经加载了一个帖子,但显然需要获取其他帖子。

    在任何情况下,当依靠更改跟踪器来修复您的导航属性时,您都应该禁用延迟加载。 EG

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

    【讨论】:

    • 好的,首先 sn-p 显示 EF 存在的问题。但我们知道我们加载了所有必需的数据,禁用 LazyLoadingEnabled 可以启用 EF 的“神奇”绑定。
    【解决方案2】:

    鉴于您拥有导航属性,请考虑在查询中利用它们为 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 Skip and Take 而不是 ToList,或者至少利用 Take 来确保您返回的数据量有一个上限。 ToList 是我在 EF 代码中寻找的主要性能巨魔,因为它的滥用会杀死应用程序。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多