在将实体模型转换为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
属性?
答案 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();