在 IQueryable 上调用 ProjectTo<T>() 时 AutoMapper 抛出 StackOverflowException

AutoMapper throwing StackOverflowException when calling ProjectTo<T>() on 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>();
    }
}

DTO:

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

调用此代码时出现 WhosebugException(上下文是我的 dbContext):

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

我猜这是因为它在列表中循环无休止地互相调用。但我不明白为什么会这样。我的映射有误吗?

您有自引用实体和自引用 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 也可以。

其他选项是使用 PreserveReferences() 方法。

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

我使用这个通用方法:

        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);
    }
...
MapperConfiguration(cfg =>
{
    cfg.ForAllMaps((map, exp) => exp.MaxDepth(1));
...

当你给第二个实体 1 navigation_property 时,反之亦然,它进入无限循环状态。因此,编译器会自动抛出 Whosebug 异常。

因此,为避免这种情况,您只需从任何实体中删除一个 navigation_property。