变换

时间:2018-01-12 10:04:39

标签: c# entity-framework-core ef-core-2.0

在将实体模型转换为DTO时,我遇到了Entity Framework Core(v2.0.1)的问题。基本上,根据该短语的任何其他版本,当我不想要它时,延迟加载。这是一个简单的.NET Core Console应用程序(使用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]表,如果您真的尝试在Tags上读取ReportDto属性的值,那么它触发另一个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(),但这只会将上述辅助SQL查询再触发26次。

最后,在绝望中我尝试将var reports = dbContext.Reports更改为var reports = dbContext.Reports.Include(r => r.Tags).ThenInclude((ReportTag rt) => rt.TagId),但这可以理解地引发ReportTag.TagId不是导航属性的异常。

有没有人对我能做什么有任何想法,以便它急切加载到ReportDto.Tags属性?

1 个答案:

答案 0 :(得分:3)

正如您所注意到的,目前包含集合投影的EF Core投影查询存在两个问题 - (1)它们导致每个集合执行N个查询,(2)它们被懒惰地执行。

问题(2)很奇怪,因为具有讽刺意味的是,EF Core不支持延迟加载相关的实体数据,而这种行为有效地实现了它的投影。至少你可以使用你已经找到的ToList()或类似物来强行立即执行。

问题(1)此时无法解决。它由Query: optimize queries projecting correlated collections, so that they don't result in N+1 database queries #9282跟踪并根据Roadmap减少n + 1个查询项目)最终将在下一个EF Core 2.1版本中得到修复(改进)

我能想到的唯一解决方法是(使用更高的数据传输和内存使用成本)使用 eager loading 并在之后进行投影(在LINQ to Entities的上下文中):< / p>

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();