Aggiornamento: Automapper applica questo automaticamente in casi semplici poiché aggiunge già un ToList()
. La questione che sto vedendo che mi ha portato ad aprire questa domanda si rivela essere un problema più complicato (il membro di SoftwareIds è il colpevole di N+1
Vedi questo .).
In EF Core 2.1, abbiamo ottenuto il supporto per l'aggiunta di ToList()
su una subquery LINQ per bufferizzare i risultati ed evitare le query del database N + 1. ( Documenti ) Funziona alla grande su semplici query LINQ contro un DbContext.
Tuttavia, se ho un profilo di Automapper che risulta in query N + 1:
public MyMappingProfile() =>
CreateMap<MyEntity, MyDto>().ForMember(e => e.MyCollectionProp, o => o.MapFrom(l => l.MyCollectionPropMany.Select(la => la.MyCollectionEntity)))
Aggiungere ToList()
genera un'eccezione:
public MyMappingProfile() =>
CreateMap<MyEntity, MyDto>().ForMember(e => e.MyCollectionProp, o => o.MapFrom(l => l.MyCollectionPropMany.Select(la => la.MyCollectionEntity).ToList()))
System.NotSupportedException: 'Impossibile analizzare l'espressione' MyDto.MyCollectionPropMany.Select (la => la.MyCollectionEntity) .ToList () ': Questo overload del metodo' System.Linq.Enumerable.ToList 'non è attualmente supportato.'
C'è un modo per abilitare il buffering delle subquery in un profilo di Automapper?
Modelli:
public class MyEntity
{
public int Id { get; set; }
public ICollection<MyCollectionPropMany> MyCollectionPropManys { get; set; }
...
}
public class MyCollectionPropMany
{
public int MyEntityId { get; set; }
public MyEntity MyEntity { get; set; }
public int MyCollectionPropId { get; set; }
public MyCollectionProp MyCollectionProp { get; set; }
}
public class MyCollectionProp
{
public int Id { get; set; }
public ICollection<MyCollectionPropMany> MyCollectionPropManys { get; set; }
...
}
public class MyDto
{
public int Id { get; set; }
public IEnumerable<MyCollectionPropDto> MyCollectionPropDtos { get; set; }
...
}
public class MyCollectionPropDto
{
public string Name { get; set; }
...
}
Automapper v7.0.1
Lo scenario reale (ho cercato di semplificare / rendere generico per SO): Source In questo esempio reale, i membri di Languages
e Tags
tramite molti a molti stanno attualmente generando query N + 1.
Risulta che AutoMapper a volte aggiunge automaticamente ToList
/ ToArray
all'espressione di proiezione quando si mappano tipi enumerabili, a volte no.
La regola sembra essere la seguente. Se il tipo enumerabile di destinazione è direttamente assegnabile dal tipo di espressione di origine, AutoMapper utilizza direttamente l'espressione di origine. In altre parole, se il seguente compito è valido (pseudo codice):
dst.Member = src.Expression;
In questo caso, spetta a te includere ToList
o non nella tua espressione di mappatura (quindi opt-in per l'ottimizzazione della query correlata EF Core).
In tutti gli altri casi AutoMapper esegue la mappatura degli elementi enumerabili se necessario e quindi aggiunge ToArray
o ToList
. Non c'è modo di rinunciare.
In breve, se il tipo di elemento enumerabile destinazione se Dto (richiede la mappatura), non includere ToList
nell'espressione fonte LINQ, se è di tipo primitivo o entità, COMPRENDONO ToList
per evitare di N + 1 query. Tutto ciò si applica se il tipo di raccolta di destinazione è IEnumerable<T>
. Qualsiasi altro tipo di raccolta derivata come IReadOnlyCollection<T>
, IReadOnlyList<T>
, ICollection<T>
, IList<T>
, List<T>
, T[]
ecc. Verrà gestito automaticamente da AutoMapper nel caso in cui l'espressione di origine restituisca IEnumerable<TSource>
.