添加到聚合根时未保存子实体

Child entity not saved when added to aggregate root

当我保存更改时,一切看起来都很好。 CaseWorkNote 实体已正确创建并添加到 workNotes 集合(案例实体的 属性)。 当 CurrentUnitOfWork 调用 DbContext->SaveChanges() 时,我看到我的实体在那里,状态为已添加。

最后什么都没有保存到数据库中。

我的代码中遗漏了什么或做错了什么?

下面是我的代码和跟踪实体的截图。

型号:

    public class Case : FullAuditedAggregateRoot<Guid>
    {
        [Required]
        public CaseType Type { get; set; }
        [Required]
        public string Subject { get; set; }
        public string Descripion { get; set; }
        //Aggregated entity
        private HashSet<CaseWorkNote> _workNotes;
        public IEnumerable<CaseWorkNote> WorkNotes => _workNotes?.ToList();
        //
        public CaseWorkNote AddNote(string text)
        {
            if (_workNotes is null)
            {
                _workNotes = new HashSet<CaseWorkNote>();
            }
            CaseWorkNote workNote = CaseWorkNote.Create(this, text);
            _workNotes.Add(workNote);
            return workNote;
        }
    }
    public class CaseWorkNote : FullAuditedEntity
    {
        [ForeignKey("CaseId")]
        [Required]
        public Case Case { get; private set; }    
        [Required]
        public string Text { get; set; }            
        private CaseWorkNote() : base() { }
        public static CaseWorkNote Create(Case kase, string text)
        {
            return new CaseWorkNote()
            {
                Case = kase,
                Text = text
            };
        }
    }

数据库上下文:

    public class testDbContext : AbpZeroDbContext<Tenant, Role, User, testDbContext>
    {
        public DbSet<Case> Cases { get; set; }
        public DbSet<CaseWorkNote> CaseWorkNotes { get; set; }
        public testDbContext(DbContextOptions<testDbContext> options)
            : base(options) { }
        public override int SaveChanges()
        {
            //Here I see CaseWorkNote entity with state = "Added"
            var entries = this.ChangeTracker.Entries();
            foreach (var item in entries)
            {
                Debug.WriteLine("State: {0}, Type: {1}", item.State.ToString(), item.Entity.GetType().FullName);
            }
            return base.SaveChanges();
        }
    }

应用服务Class:

    public class CaseAppService : AsyncCrudAppService<Case, CaseDto, Guid, PagedCaseResultRequestDto, CreateCaseDto, UpdateCaseDto>, ICaseAppService
    {
        //Removed for brevity
        ...
        //
        public async Task AddWorkNote(CreateUpdateCaseWorkNoteDto input)
        {
            var kase = await this.GetEntityByIdAsync(input.CaseId);
            kase.AddNote(input.Text);

            CurrentUnitOfWork.SaveChanges();
        }

        protected override async Task<Case> GetEntityByIdAsync(Guid id)
        {
            var kase = await Repository
                .GetAllIncluding(c => c.WorkNotes)
                .FirstOrDefaultAsync(c => c.Id == id);

            if (kase == null)
            {
                throw new EntityNotFoundException(typeof(Case), id);
            }
            return kase;
        }

        public async Task<ListResultDto<CaseWorkNoteDto>> GetWorkNotes(EntityDto<Guid> entity)
        {
            var kase = await this.GetEntityByIdAsync(entity.Id);
            return new ListResultDto<CaseWorkNoteDto>(MapToEntityDto(kase).WorkNotes.ToList());
        }
    }

谢谢

添加外键属性CaseId。 还添加了 Virtual 关键字。

public class CaseWorkNote : FullAuditedEntity
    {
        [ForeignKey("CaseId")]
        [Required]
        public virtual Case Case { get; private set; }    
        public virtual Guid CaseId { get; private set; }  /* Added */

        [Required]
        public virtual string Text { get; set; }   

        private CaseWorkNote() : base() { }

        public static CaseWorkNote Create(Case kase, string text)
        {
            return new CaseWorkNote()
            {
                Case = kase,
                Text = text
            };
        }
    }

问题是默认的EF Core 属性访问方式和ToList()调用这里

引起的
public IEnumerable<CaseWorkNote> WorkNotes => _workNotes?.ToList();

不确定您遵循的是哪种方法,但您违反了 属性(尤其是集合类型)不应在每次获取时分配的简单良好设计规则。不仅因为它效率低下,而且还允许像 EF Core 这样的 "smart" 客户端将实际类型检测为 List,并在 loading related data 时尝试使用它来添加项目。

实际上,对于这种类型的实施,他们正在添加到一个被丢弃的列表中,换句话说,没有地方。因此 EF Core 加载相关数据/导航 属性 修复不起作用,这也可能影响更改跟踪器并导致奇怪的行为。

要解决 EF Core 问题,您应该将 EF Core 配置为直接使用 backing field。最简单的方法是在 OnModelCreating 覆盖中全局设置它:

modelBuilder.UsePropertyAccessMode(PropertyAccessMode.Field);

它也可以按实体或按实体设置 属性,但我会建议以上内容,而且 EF Core 3.0 中的预期变化之一是 Backing fields will be used by default.

反正现在问题就解决了

不过,遵循良好做法会更好。 _workNotes 成员应该使用初始化器或在 class 构造函数中初始化,属性 getter 应该直接 return 。如果想法是阻止调用者通过强制转换结果来访问私有成员,那么还有其他方法可以阻止不克隆集合内容的方法。例如:

//Aggregated entity
private readonly HashSet<CaseWorkNote> _workNotes = new HashSet<CaseWorkNote>();
public IEnumerable<CaseWorkNote> WorkNotes => _workNotes.Select(e => e);
//

无论您是否保留当前的导航实现 属性,您必须 让 EF Core 直接使用支持字段。