Entity Framework Core è il caricamento lento durante la trasformazione

Sto riscontrando un problema con Entity Framework Core (v2.0.1) durante la trasformazione di un modello di quadro in un DTO. Fondamentalmente è, con qualsiasi altra versione della frase, il caricamento pigro quando non lo voglio. Ecco una semplice applicazione .NET Core Console (con il pacchetto 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() .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  new ReportDto { Id = r.Id, Title = r.Title, Tags = r.Tags.Select(rt => rt.TagId) }) .ToList(); } } } class ReportDbContext : DbContext { public DbSet Reports { get; set; } public ReportDbContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity().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 Tags { get; set; } public Report() { Tags = new HashSet(); } } [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 Tags { get; set; } } } 

Ora, quando viene eseguito il metodo ToList() per recuperare i dati, sta eseguendo il seguente SQL

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

Come puoi vedere, non ha fatto nessuno sforzo per unirsi alla tabella [ReportTag] , e se in effetti provi a leggere i valori della proprietà Tags su un ReportDto viene ReportDto un’altra query SQL

 SELECT [rt].[TagId] FROM [ReportTag] AS [rt] WHERE @_outer_Id = [rt].[ReportId] 

Ora so che l’EF Core non supporta il caricamento lento, ma sembra molto pigro per me. In questo caso non voglio che si carichi lentamente. Ho provato a cambiare var reports = dbContext.Reports in var reports = dbContext.Reports.Include(r => r.Tags) che non ha alcun effetto.

Ho anche provato a cambiare Tags = r.Tags.Select(rt => rt.TagId) a Tags = r.Tags.Select(rt => rt.TagId).ToList() ma che spara appena sopra SQL secondario sopra interrogare altre 26 volte.

Alla fine, disperato, ho provato a cambiare var reports = dbContext.Reports in var reports = dbContext.Reports.Include(r => r.Tags).ThenInclude((ReportTag rt) => rt.TagId) ma comprensibilmente getta un’eccezione a ReportTag.TagId non è una proprietà di navigazione.

Qualcuno ha qualche idea su cosa posso fare in modo che carichi carichi nella proprietà ReportDto.Tags ?

Come hai notato, attualmente ci sono due problemi con le query di proiezione di EF Core contenenti le proiezioni di raccolta – (1) che causano l’esecuzione di N query per raccolta e (2) sono eseguite pigramente.

Il problema (2) è strano, perché ironicamente EF Core non supporta i dati di quadro lazy relativi al caricamento , mentre questo comportamento lo implementa in modo efficace per le proiezioni. Almeno puoi forzare l’esecuzione immediata usando ToList() o simili, come hai già trovato.

Il problema (1) è irrisolvibile in questo momento. È tracciato da Query: ottimizza le query proiettando le raccolte correlate, in modo che non risultino nelle query del database N + 1 n. 9282 e in base alla Roadmap (elemento di query Riduci n + 1 ) verrà eventualmente corretto (migliorato) nella prossima EF Versione 2.1 di base.

L’unica soluzione a cui riesco a pensare è (con il costo di un maggiore trasferimento di dati e utilizzo della memoria) per utilizzare il caricamento impaziente e successivamente effettuare la proiezione (nel contesto di LINQ alle quadro):

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