Entity Framework Core在转换时使用惰性加载。

3

我在使用实体框架核心(v2.0.1)将实体模型转换为DTO时遇到了问题。基本上,它正在进行懒加载,而我不想要它。这里是一个简单的.NET Core控制台应用程序(使用Microsoft.EntityFrameworkCore.SqlServer(2.0.1)包)。

using Microsoft.EntityFrameworkCore;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;

namespace EfCoreIssue
{
    class Program
    {
        static void Main(string[] args)
        {
            var dbOptions = new DbContextOptionsBuilder<ReportDbContext>()
                .UseSqlServer("Server=.;Database=EfCoreIssue;Trusted_Connection=True;")
                .Options;

            // Create and seed database if it doesn't already exist.
            using (var dbContext = new ReportDbContext(dbOptions))
            {
                if (dbContext.Database.EnsureCreated())
                {
                    string alphas = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

                    foreach (char alpha in alphas)
                    {
                        var report = new Report { Title = $"Report { alpha }" };

                        for (int tagId = 0; tagId < 10; tagId++)
                            report.Tags.Add(new ReportTag { TagId = tagId });

                        dbContext.Reports.Add(report);
                        dbContext.SaveChanges();
                    }
                }
            }

            using (var dbContext = new ReportDbContext(dbOptions))
            {
                var reports = dbContext.Reports
                    .Select(r => new ReportDto
                    {
                        Id = r.Id,
                        Title = r.Title,
                        Tags = r.Tags.Select(rt => rt.TagId)
                    })
                    .ToList();
            }
        }
    }

    class ReportDbContext : DbContext
    {
        public DbSet<Report> Reports { get; set; }

        public ReportDbContext(DbContextOptions<ReportDbContext> options)
            : base(options) { }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<ReportTag>().HasKey(rt => new { rt.ReportId, rt.TagId });
        }
    }

    [Table("Report")]
    class Report
    {
        [Key]
        public int Id { get; set; }
        public string Title { get; set; }
        public virtual ICollection<ReportTag> Tags { get; set; }

        public Report()
        {
            Tags = new HashSet<ReportTag>();
        }
    }

    [Table("ReportTag")]
    class ReportTag
    {
        public int ReportId { get; set; }
        public int TagId { get; set; }
    }

    class ReportDto
    {
        public int Id { get; set; }
        public string Title { get; set; }
        public IEnumerable<int> Tags { get; set; }
    }
}

现在,当执行ToList()方法检索数据时,它将执行以下SQL语句

SELECT [r].[Id], [r].[Title]
FROM [Report] AS [r]

正如您所看到的,它没有任何努力加入[ReportTag]表,如果您尝试读取ReportDto上的Tags属性的值,则会触发另一个SQL查询。

SELECT [rt].[TagId]
FROM [ReportTag] AS [rt]
WHERE @_outer_Id = [rt].[ReportId]
我知道 EF Core 不支持延迟加载,但这看起来很像延迟加载。在这种情况下,我不想使用延迟加载。我尝试将 var reports = dbContext.Reports 更改为 var reports = dbContext.Reports.Include(r => r.Tags),但没有效果。 我甚至尝试将 Tags = r.Tags.Select(rt => rt.TagId) 更改为 Tags = r.Tags.Select(rt => rt.TagId).ToList(),但这会触发另外 26 次上述次要 SQL 查询。 最后,我绝望地尝试将 var reports = dbContext.Reports 更改为 var reports = dbContext.Reports.Include(r => r.Tags).ThenInclude((ReportTag rt) => rt.TagId),但可以理解地抛出异常,因为 ReportTag.TagId 不是导航属性。 有人有什么想法可以让它急切地加载到 ReportDto.Tags 属性中吗?
1个回答

3
正如您所注意到的,当前存在两个关于EF Core投影查询包含集合投影的问题-(1)它们会导致每个集合执行N个查询,(2)它们是懒执行的。 问题(2)很奇怪,因为具有讽刺意味的是,EF Core不支持与lazy loading相关的实体数据,而这种行为实际上实现了它用于投影。至少您可以通过使用ToList()或类似方法来强制立即执行,就像您已经发现的那样。 问题(1)在此时无法解决。它被Query: optimize queries projecting correlated collections, so that they don't result in N+1 database queries #9282跟踪,并根据Roadmap(Reduce n + 1 queries项)最终将在下一个EF Core 2.1版本中进行修复(改进)。 我唯一想到的解决方法是(以更高的数据传输和内存使用成本为代价)使用eager loading,在LINQ to Entities的上下文中进行投影:
var reports = dbContext.Reports
    .Include(r => r.Tags) // <-- eager load
    .AsEnumerable() // <-- force the execution of the LINQ to Entities query
    .Select(r => new ReportDto
    {
        Id = r.Id,
        Title = r.Title,
        Tags = r.Tags.Select(rt => rt.TagId)
    })
    .ToList();

2
感谢Ivan准确地找出了问题/错误。将数据集加载到内存中再进行投影的解决方案对我来说并没有太大帮助,因为它非常大,而且我不能修改合同以不允许返回DTO的LINQ操作。我会关注它是否在2.1版本中得到修复/更新。 - Dan Brentley

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,