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



我使用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>();

当调用此代码时,我得到了StackOverflowException(Context是我的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也适用。

Other选项使用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赋予第二个实体,反之亦然时,它将进入无限循环状态。因此,编译器会自动抛出堆栈溢出异常。

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

最新更新