自定义 MappedEntityDomainManager 抛出 StackOverflowException
Custom MappedEntityDomainManager throws StackOverflowException
我创建了一个自定义的 MappedEntityDomainManager 来将我的数据对象映射到 DTO。在过去的几天里,我试图在 TableController 中找出它抛出 WhosebugException 的原因。这是迄今为止我唯一用于测试 AutoMapper 的 MappedEntityDomainManager。
数据对象和 DTO 模型。
public class PostDto : EntityData
{
public PostDto()
{
User = new UserDto();
PhotoUrls = new HashSet<PostPhotoUrlDto>();
}
public DateTimeOffset DatePosted { get; set; }
public string StatusText { get; set; }
public int TypeOfPost { get; set; }
public UserDto User { get; set; }
public ICollection<PostPhotoUrlDto> PhotoUrls { get; set; }
}
public class Post : EntityData
{
public string UserId { get; set; }
public DateTimeOffset DatePosted { get; set; }
public string StatusText { get; set; }
public PostType TypeOfPost { get; set; }
public virtual User User { get; set; }
public virtual ICollection<PostPhotoUrl> PhotoUrls { get; set; }
}
DomainManager。
public class PostDtoToPostMappedEntityDomainManager : MappedEntityDomainManager<PostDto, Post>
{
public PostDtoToPostMappedEntityDomainManager(DbContext context, HttpRequestMessage request, ApiServices services)
: base(context, request, services)
{
}
public PostDtoToPostMappedEntityDomainManager(DbContext context, HttpRequestMessage request, ApiServices services, bool enableSoftDelete)
: base(context, request, services, enableSoftDelete)
{
}
public override IQueryable<PostDto> Query()
{
return base.Query();
}
public override SingleResult<PostDto> Lookup(string id)
{
return this.LookupEntity(model => model.Id == id);
}
public override Task<PostDto> UpdateAsync(string id, Delta<PostDto> patch)
{
return base.UpdateEntityAsync(patch, id);
}
public override Task<bool> DeleteAsync(string id)
{
return base.DeleteItemAsync(id);
}
}
映射配置。
cfg.CreateMap<Post, PostDto>()
.ForMember(postDto => postDto.TypeOfPost, map => map.MapFrom(post => (int) post.TypeOfPost))
.ForMember(postDto => postDto.User, map => map.MapFrom(post => post.User))
.ForMember(postDto => postDto.PhotoUrls, map => map.MapFrom(post => post.PhotoUrls));
cfg.CreateMap<PostDto, Post>()
.ForMember(post => post.TypeOfPost, map => map.MapFrom(postDto => postDto.TypeOfPost))
.ForMember(post => post.User, map => map.MapFrom(postDto => postDto.User))
.ForMember(post => post.PhotoUrls, map => map.MapFrom(postDto => postDto.PhotoUrls));
在 PostController 中。
// GET tables/Post
public IQueryable<PostDto> GetAllPost()
{
return Query();
}
PostDtoToPostMappedEntityDomainManager
public override IQueryable<PostDto> Query()
方法抛出异常
An unhandled exception of type 'System.WhosebugException' occurred in mscorlib.dll
{Cannot evaluate expression because the current thread is in a stack overflow state.}
如果我将 Query() 方法更改为 select 我的新 DTO 手动它工作正常,我只是不明白此时 AutoMapper 需要什么。
public override IQueryable<PostDto> Query()
{
MobileServiceContext ctx = this.Context as MobileServiceContext;
return ctx
.Posts
.Include(post => post.User)
.Include(post => post.PhotoUrls).AsNoTracking()
.ToArray()
.Select(x => new PostDto
{
Id = x.Id,
DatePosted = x.DatePosted,
StatusText = x.StatusText,
TypeOfPost = (int)x.TypeOfPost,
User = new UserDto
{
Id = x.User.Id,
FirstName = x.User.FirstName
},
PhotoUrls = new List<PostPhotoUrlDto>()
{
new PostPhotoUrlDto() { Url = "AURL" }
}
}).AsQueryable();
}
编辑
回答 whitelatino
之后,我开始评论导航属性并尝试以解决方案结尾的事情。
问题在 District => Clubs => District 的循环依赖关系中更深层次,见下面的设置。
public class PostEntityTypeConfiguration : EntityTypeConfiguration<Post>
{
public PostEntityTypeConfiguration()
{
// Properties
HasRequired(post => post.User);
HasMany(post => post.PhotoUrls).WithRequired().HasForeignKey(ph => ph.PostId);
}
}
public class UserEntityTypeConfiguration : EntityTypeConfiguration<User>
{
public UserEntityTypeConfiguration()
{
// Properties
HasOptional(user => user.Club);
HasMany(user => user.Posts).WithRequired().HasForeignKey(post => post.UserId).WillCascadeOnDelete(false);
}
}
public class ClubEntityTypeConfiguration : EntityTypeConfiguration<Club>
{
public ClubEntityTypeConfiguration()
{
// Properties
HasMany(club => club.Members).WithOptional(user => user.Club).HasForeignKey(user => user.ClubId).WillCascadeOnDelete(false);
HasRequired(club => club.ClubDistrict); // Left this as is
}
}
public class DistrictEntityTypeConfiguration : EntityTypeConfiguration<District>
{
public DistrictEntityTypeConfiguration()
{
// Properties
// HasMany(district => district.Clubs).WithRequired().HasForeignKey(club => club.DistrictId).WillCascadeOnDelete(false); // I had to comment this and remove the navigation list property "Clubs" from the District data object.
}
}
PostPhotoUrlDto 没有 EntityTypeConfiguration。
public class PostPhotoUrl : EntityData
{
public string PostId { get; set; }
public string Url { get; set; }
public virtual Post Post { get; set; }
}
public class Club : EntityData
{
// Properties
public virtual District ClubDistrict { get; set; }
public virtual ICollection<User> Members { get; set; }
}
public class User : EntityData
{
// Properties
public virtual Club Club { get; set; }
public virtual ICollection<Post> Posts { get; set; }
public User()
{
Posts = new HashSet<Post>();
}
}
public class District : EntityData
{
// Properties
// public virtual ICollection<Club> Clubs { get; set; } // Had to comment this, it is causing a Whosebug because of the circular dependency
}
我真的不需要访问导航列表 属性 所以这解决了我的问题。我还看到数据库架构关系已正确创建。
值得注意的是,我必须将 public IQueryable<PostDto> GetAllPost()
方法更改为 IEnumerable<PostDto> GetAllPost()
才能正确接收包含导航属性的 JSON。
请参阅下面生成的最终方法。
// GET tables/Post
public async Task<IEnumerable<PostDto>> GetAllPost()
{
return await Query().ToListAsync();
}
Whosebugexception 是由递归循环引起的,即两个导航属性相互引用。我在多对多关系中遇到了同样的问题。我通过删除其中一个实体上的导航 属性(列表)来修复它,而是使用以下方法进行多对多映射:
modelBuilder.Entity<Event>().HasMany(m => m.HostList).WithMany();
我创建了一个自定义的 MappedEntityDomainManager 来将我的数据对象映射到 DTO。在过去的几天里,我试图在 TableController 中找出它抛出 WhosebugException 的原因。这是迄今为止我唯一用于测试 AutoMapper 的 MappedEntityDomainManager。
数据对象和 DTO 模型。
public class PostDto : EntityData
{
public PostDto()
{
User = new UserDto();
PhotoUrls = new HashSet<PostPhotoUrlDto>();
}
public DateTimeOffset DatePosted { get; set; }
public string StatusText { get; set; }
public int TypeOfPost { get; set; }
public UserDto User { get; set; }
public ICollection<PostPhotoUrlDto> PhotoUrls { get; set; }
}
public class Post : EntityData
{
public string UserId { get; set; }
public DateTimeOffset DatePosted { get; set; }
public string StatusText { get; set; }
public PostType TypeOfPost { get; set; }
public virtual User User { get; set; }
public virtual ICollection<PostPhotoUrl> PhotoUrls { get; set; }
}
DomainManager。
public class PostDtoToPostMappedEntityDomainManager : MappedEntityDomainManager<PostDto, Post>
{
public PostDtoToPostMappedEntityDomainManager(DbContext context, HttpRequestMessage request, ApiServices services)
: base(context, request, services)
{
}
public PostDtoToPostMappedEntityDomainManager(DbContext context, HttpRequestMessage request, ApiServices services, bool enableSoftDelete)
: base(context, request, services, enableSoftDelete)
{
}
public override IQueryable<PostDto> Query()
{
return base.Query();
}
public override SingleResult<PostDto> Lookup(string id)
{
return this.LookupEntity(model => model.Id == id);
}
public override Task<PostDto> UpdateAsync(string id, Delta<PostDto> patch)
{
return base.UpdateEntityAsync(patch, id);
}
public override Task<bool> DeleteAsync(string id)
{
return base.DeleteItemAsync(id);
}
}
映射配置。
cfg.CreateMap<Post, PostDto>()
.ForMember(postDto => postDto.TypeOfPost, map => map.MapFrom(post => (int) post.TypeOfPost))
.ForMember(postDto => postDto.User, map => map.MapFrom(post => post.User))
.ForMember(postDto => postDto.PhotoUrls, map => map.MapFrom(post => post.PhotoUrls));
cfg.CreateMap<PostDto, Post>()
.ForMember(post => post.TypeOfPost, map => map.MapFrom(postDto => postDto.TypeOfPost))
.ForMember(post => post.User, map => map.MapFrom(postDto => postDto.User))
.ForMember(post => post.PhotoUrls, map => map.MapFrom(postDto => postDto.PhotoUrls));
在 PostController 中。
// GET tables/Post
public IQueryable<PostDto> GetAllPost()
{
return Query();
}
PostDtoToPostMappedEntityDomainManager
public override IQueryable<PostDto> Query()
方法抛出异常
An unhandled exception of type 'System.WhosebugException' occurred in mscorlib.dll
{Cannot evaluate expression because the current thread is in a stack overflow state.}
如果我将 Query() 方法更改为 select 我的新 DTO 手动它工作正常,我只是不明白此时 AutoMapper 需要什么。
public override IQueryable<PostDto> Query()
{
MobileServiceContext ctx = this.Context as MobileServiceContext;
return ctx
.Posts
.Include(post => post.User)
.Include(post => post.PhotoUrls).AsNoTracking()
.ToArray()
.Select(x => new PostDto
{
Id = x.Id,
DatePosted = x.DatePosted,
StatusText = x.StatusText,
TypeOfPost = (int)x.TypeOfPost,
User = new UserDto
{
Id = x.User.Id,
FirstName = x.User.FirstName
},
PhotoUrls = new List<PostPhotoUrlDto>()
{
new PostPhotoUrlDto() { Url = "AURL" }
}
}).AsQueryable();
}
编辑
回答 whitelatino
之后,我开始评论导航属性并尝试以解决方案结尾的事情。
问题在 District => Clubs => District 的循环依赖关系中更深层次,见下面的设置。
public class PostEntityTypeConfiguration : EntityTypeConfiguration<Post>
{
public PostEntityTypeConfiguration()
{
// Properties
HasRequired(post => post.User);
HasMany(post => post.PhotoUrls).WithRequired().HasForeignKey(ph => ph.PostId);
}
}
public class UserEntityTypeConfiguration : EntityTypeConfiguration<User>
{
public UserEntityTypeConfiguration()
{
// Properties
HasOptional(user => user.Club);
HasMany(user => user.Posts).WithRequired().HasForeignKey(post => post.UserId).WillCascadeOnDelete(false);
}
}
public class ClubEntityTypeConfiguration : EntityTypeConfiguration<Club>
{
public ClubEntityTypeConfiguration()
{
// Properties
HasMany(club => club.Members).WithOptional(user => user.Club).HasForeignKey(user => user.ClubId).WillCascadeOnDelete(false);
HasRequired(club => club.ClubDistrict); // Left this as is
}
}
public class DistrictEntityTypeConfiguration : EntityTypeConfiguration<District>
{
public DistrictEntityTypeConfiguration()
{
// Properties
// HasMany(district => district.Clubs).WithRequired().HasForeignKey(club => club.DistrictId).WillCascadeOnDelete(false); // I had to comment this and remove the navigation list property "Clubs" from the District data object.
}
}
PostPhotoUrlDto 没有 EntityTypeConfiguration。
public class PostPhotoUrl : EntityData
{
public string PostId { get; set; }
public string Url { get; set; }
public virtual Post Post { get; set; }
}
public class Club : EntityData
{
// Properties
public virtual District ClubDistrict { get; set; }
public virtual ICollection<User> Members { get; set; }
}
public class User : EntityData
{
// Properties
public virtual Club Club { get; set; }
public virtual ICollection<Post> Posts { get; set; }
public User()
{
Posts = new HashSet<Post>();
}
}
public class District : EntityData
{
// Properties
// public virtual ICollection<Club> Clubs { get; set; } // Had to comment this, it is causing a Whosebug because of the circular dependency
}
我真的不需要访问导航列表 属性 所以这解决了我的问题。我还看到数据库架构关系已正确创建。
值得注意的是,我必须将 public IQueryable<PostDto> GetAllPost()
方法更改为 IEnumerable<PostDto> GetAllPost()
才能正确接收包含导航属性的 JSON。
请参阅下面生成的最终方法。
// GET tables/Post
public async Task<IEnumerable<PostDto>> GetAllPost()
{
return await Query().ToListAsync();
}
Whosebugexception 是由递归循环引起的,即两个导航属性相互引用。我在多对多关系中遇到了同样的问题。我通过删除其中一个实体上的导航 属性(列表)来修复它,而是使用以下方法进行多对多映射:
modelBuilder.Entity<Event>().HasMany(m => m.HostList).WithMany();