2016-05-16 41 views
15

Ho creato classi utilizzando prima il codice EF che dispone di raccolte l'una dell'altra. Entità:AutoMapper che lancia StackOverflowException quando si chiama ProjectTo <T>() su IQueryable

public class Field 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public virtual List<AppUser> Teachers { get; set; } 
    public Field() 
    { 
     Teachers = new List<AppUser>(); 
    } 
} 

public class AppUser 
{ 
    public int Id { get; set; } 
    public string Email { get; set; } 
    public string Password { get; set; } 
    public string UserName => Email; 
    public virtual List<Field> Fields { get; set; } 
    public AppUser() 
    { 
     Fields = new List<FieldDTO>(); 
    } 
} 

DTOs:

public class FieldDTO 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public List<AppUserDTO> Teachers { get; set; } 
    public FieldDTO() 
    { 
     Teachers = new List<AppUserDTO>(); 
    } 
} 

public class AppUserDTO 
{ 
    public int Id { get; set; } 
    public string Email { get; set; } 
    public string Password { get; set; } 
    public string UserName => Email; 
    public List<FieldDTO> Fields { get; set; } 
    public AppUserDTO() 
    { 
     Fields = new List<FieldDTO>(); 
    } 
} 

Mapping:

Mapper.CreateMap<Field, FieldDTO>(); 
Mapper.CreateMap<FieldDTO, Field>(); 
Mapper.CreateMap<AppUserDTO, AppUser>(); 
Mapper.CreateMap<AppUser, AppUserDTO>(); 

E io sono sempre StackOverflowException quando si chiama questo codice (Context è il mio DbContext):

protected override IQueryable<FieldDTO> GetQueryable() 
{ 
    IQueryable<Field> query = Context.Fields; 
    return query.ProjectTo<FieldDTO>();//exception thrown here 
} 

Suppongo che ciò accada perché scorre in elenchi che si chiamano all'infinito. Ma non capisco perché questo accada. Le mie mappature sono sbagliate?

+0

Hai ragione. Il problema è un ciclo infinito quando si chiama il mapper sugli elenchi. I tuoi mapping sono corretti. Puoi provare a svuotare le liste prima di convertire le entità. – erikscandola

risposta

23

Si dispone di entità autoreferenziali E DTO autoreferenziali. In generale, le DTO autoreferenziali sono una cattiva idea. Soprattutto quando si fa una proiezione - EF non sa come unirsi e unirsi e unire una gerarchia di elementi.

Hai due possibilità.

In primo luogo, è possibile forzare una profondità specifica di gerarchia modellando in modo esplicito i tuoi DTOs con una gerarchia in mente:

public class FieldDTO 
{ 
    public int Id { get; set; } 
    public string Name { get; set; } 
    public List<TeacherDTO> Teachers { get; set; } 
    public FieldDTO() 
    { 
     Teachers = new List<TeacherDTO>(); 
    } 
} 

public class TeacherDTO 
{ 
    public int Id { get; set; } 
    public string Email { get; set; } 
    public string Password { get; set; } 
    public string UserName => Email; 
} 

public class AppUserDTO : TeacherDTO 
{ 
    public List<FieldDTO> Fields { get; set; } 
    public AppUserDTO() 
    { 
     Fields = new List<FieldDTO>(); 
    } 
} 

Questo è il modo preferito, in quanto è il più evidente ed esplicito.

Il meno ovvio, modo meno esplicito è quello di configurare automapper di avere una profondità massima andrà ad attraversare le relazioni gerarchiche:

CreateMap<AppUser, AppUserDTO>().MaxDepth(3); 

preferisco andare # 1 perché è il più facilmente comprensibile, ma # 2 funziona pure.

+0

Grazie Jimmy. Mi hai davvero aiutato. Penso che andrò per la prima scelta. Ci vorrà del tempo per ridefinire l'intero codice, ma ne varrà la pena. – Peter

+0

@Jimmy Bogard: nel primo approccio, l'ordine di mappatura è importante? –

+0

Trovo che .MaxDepth (n) sia molto utile per completare la mappa durante i test in modo da poter poi scavare per scoprire da dove proviene la ricorsione per i grafici complessi. Ma lo prendo sempre dopo, in modo che se il modello cambia, e la ricorsione viene reintrodotta per errore, avremo di nuovo un errore. –

7

Un'altra opzione utilizza il metodo PreserveReferences().

CreateMap<AppUser, AppUserDTO>().PreserveReferences(); 
+0

Se non sto sbagliando, MaxDepth lo abilita automaticamente. – cdie

+0

PreserveReferences non si applica a ProjectTo. –

0

Io uso questo metodo generico:

 public static TTarget Convert<TSource, TTarget>(TSource sourceItem) 
    { 
     if (null == sourceItem) 
     { 
      return default(TTarget); 
     } 

     var deserializeSettings = new JsonSerializerSettings { ObjectCreationHandling = ObjectCreationHandling.Replace, ReferenceLoopHandling = ReferenceLoopHandling.Ignore }; 

     var serializedObject = JsonConvert.SerializeObject(sourceItem, deserializeSettings); 

     return JsonConvert.DeserializeObject<TTarget>(serializedObject); 
    } 
+0

Le prestazioni sarebbero molto patetiche! –