AutoMapper бросает StackOverflowException при вызове ProjectTo <T>() на IQueryable

Я создал классы, используя EF Code First, которые имеют коллекции друг от друга. Объекты:

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

Отображения:

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

И я получаю StackOverflowException при вызове этого кода (Контекст - это мой dbContext):

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

Я предполагаю, что это происходит потому, что он циклически перекликается в списках, которые бесконечно называют друг друга. Но я не понимаю, почему это происходит. Являются ли мои сопоставления неправильными?

Ответ 1

У вас есть объекты саморегуляции и саморегуляционные DTO. Вообще говоря, самореферентные DTO - плохая идея. Особенно при выполнении проекции - EF не знает, как объединиться и объединиться и объединить иерархию элементов.

У вас есть два варианта.

Во-первых, вы можете принудительно установить определенную глубину иерархии, явно моделируя свои DTO с учетом иерархии:

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

Это предпочтительный способ, поскольку он является наиболее очевидным и явным.

Менее очевидным, менее явным способом является то, что AutoMapper должен иметь максимальную глубину, чтобы перейти к иерархическим отношениям:

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

Я предпочитаю идти # 1, потому что это наиболее легко понимается, но # 2 также работает.

Ответ 2

Другой вариант - использовать метод PreserveReferences().

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

Ответ 3

Я использую этот общий метод:

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

Ответ 4

...
MapperConfiguration(cfg =>
{
    cfg.ForAllMaps((map, exp) => exp.MaxDepth(1));
...