Дочерняя сущность не сохраняется при добавлении в объединенный корень - PullRequest
1 голос
/ 28 июня 2019

Когда я сохраняю изменения, все выглядит хорошо. Сущность CaseWorkNote правильно создана и добавлена ​​в коллекцию workNotes (свойство сущности Case). Когда 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
            };
        }
    }

DbContext:

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

Класс обслуживания приложения:

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

спасибо

debugger - you can see the entity is there.

Ответы [ 2 ]

2 голосов
/ 29 июня 2019

Проблема вызвана режимом доступа к свойству EF Core по умолчанию и ToList() вызовом здесь

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

Не уверен, какую методологию вы используете, но вы нарушаете простое правило хорошего дизайна, которое свойство (и особенно тип коллекции) не должно распределять при каждом получении. Не только потому, что это неэффективно, но и позволяет «умному» клиенту, такому как EF Core, определять фактический тип как List и пытаться использовать его для добавления элементов при загрузке связанных данных .

На самом деле с этим типом реализации они добавляют в список, который отбрасывается, другими словами - никуда. Так что исправление загрузки данных / свойств навигации в EF Core не работает, что также может повлиять на систему отслеживания изменений и привести к странным действиям.

Чтобы устранить проблему с EF Core, вы должны настроить EF Core на непосредственное использование вспомогательного поля . Самый простой способ - установить его глобально в переопределении OnModelCreating:

modelBuilder.UsePropertyAccessMode(PropertyAccessMode.Field);

Его также можно установить для каждой сущности или для свойства сущности, но я бы предложил вышеупомянутое, более того, одно из ожидаемых изменений в EF Core 3.0 заключается в том, что Поля поддержки будут использоваться по умолчанию .

В любом случае, теперь рассматриваемая проблема будет решена.

Тем не менее, будет лучше следовать хорошей практике. Элемент _workNotes должен быть инициализирован инициализатором или конструктором класса, а свойство getter должно возвращать его напрямую. Если идея состояла в том, чтобы запретить вызывающей стороне получить доступ к закрытому члену путем приведения результата, то есть другие способы предотвратить то, что не клонирует содержимое коллекции. Например:

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

Независимо от того, сохраняете ли вы текущую реализацию свойства навигации или нет, вы должны позволить EF Core напрямую использовать поле поддержки.

1 голос
/ 28 июня 2019

Добавить свойство внешнего ключа 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
            };
        }
    }
...