我使用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。