EF Core 3.1 не позволяет обновлять родительский элемент при добавлении дочерних сущностей в отношении «один ко многим» с использованием подхода DDD - PullRequest
2 голосов
/ 06 мая 2020

Я следую подходу DDD, и у меня есть родительская и дочерняя сущности с отношением «один ко многим», как показано ниже:

Родительская сущность:

public class AnnotationEntity
{
    public int Id { get; private set; }
    public string Value { get; private set; }

    private readonly List<AnnotationObjectEntity> _annotationObjects = new List<AnnotationObjectEntity>();
    public virtual IReadOnlyList<AnnotationObjectEntity> AnnotationObjects => _annotationObjects.ToList();

    protected AnnotationEntity()
    {

    }

    private AnnotationEntity(string value) : this()
    {
        Value = value;
    }

    private void UpdateContainer(string container)
    {
        Value = container;
    }

    public void AddAnnotation(AnnotationObjectEntity annotationObject)
    {
        if (!_annotationObjects.Any(x => x.Id == annotationObject.Id))
        {
            var annotationObjectEntity = new AnnotationObjectEntity
            (
                annotationObject.Id,
                annotationObject.PageNumber,
                annotationObject.AnnotationType
            );
            _annotationObjects.Add(annotationObjectEntity);
            return;
        }

        var existingAnnotation = _annotationObjects.FirstOrDefault(x => x.Id.Equals(annotationObject.Id));

        if (existingAnnotation != null)
        {
            existingAnnotation.Update(annotationObject);
        }
    }

    public void RemoveAnnotation(Guid id)
    {
        XDocument annotation = XDocument.Parse(Value);

        var annotationObjectEntity = _annotationObjects.FirstOrDefault(x => x.Id == id);

        if (annotationObjectEntity != null)
        {
            _annotationObjects.Remove(annotationObjectEntity);
            annotation.Descendants("Object").Where(x => x.Element("Guid").Value == annotationObjectEntity.Id.ToString()).Remove();
        }

        UpdateContainer(annotation.ToString());
    }

    public static AnnotationEntity Create(int folderId, int documentId, string annotation)
    {
        XDocument doc = XDocument.Parse(annotation);
        var documentAnnotations = new List<AnnotationEntity>();

        var annotationEntity = new AnnotationEntity
            (
                annotation
            );

        doc.Descendants("Container").ToList().ForEach(container =>
        {
            container.Descendants("Object").ToList().ForEach(annotationObject =>
            {
                var annotationObjectEntity = new AnnotationObjectEntity
                (
                    Guid.Parse(annotationObject.Element("Guid").Value.ToString()),
                    Convert.ToInt32(container.Element("PageNumber").Value.ToString()),
                    true
                );

                annotationEntity.AddAnnotation(annotationObjectEntity);
            });

        });

        return annotationEntity;
    }

    public void UpdateAnnotation(string modifiedAnnotationXml)
    {
        var modifiedAnnotationEntity = Create(modifiedAnnotationXml);

        //Removing deleted entities
        var deletedAnnotationsId = _annotationObjects.Where(x => !modifiedAnnotationEntity.AnnotationObjects.Any(y => y.Id.Equals(x.Id))).Select(x => x.Id).ToList();

        foreach (var annotationId in deletedAnnotationsId)
        {
            RemoveAnnotation(annotationId);
        }

        XDocument annotation = XDocument.Parse(Value);
        XDocument modifiedAnnotation = XDocument.Parse(modifiedAnnotationXml);

        //update or add
        foreach (var annotationObject in modifiedAnnotationEntity.AnnotationObjects)
        {
            AddAnnotation(annotationObject);
            annotation.Descendants("Object").Where(x => x.Element("Guid").Value == annotationObject.Id.ToString()).Remove();
            // extract annotation object xml from modified xml and add to existing xml

            var modifiedAnnotationObject = modifiedAnnotation.Descendants("Object").SingleOrDefault(x => x.Element("Guid").Value == annotationObject.Id.ToString());
            annotation
                .Descendants("Container").SingleOrDefault(x => x.Element("PageNumber").Value == annotationObject.PageNumber.ToString())
                .Descendants("Objects").SingleOrDefault()
                .Add(modifiedAnnotationObject);

            UpdateContainer(annotation.ToString());
        }
    }
}

Дочерний объект:

public class AnnotationObjectEntity
{
    public Guid Id { get; private set; }
    public int PageNumber { get; private set; }
    public bool AnnotationType { get; private set; }

    public AnnotationEntity Annotation { get; }

    protected AnnotationObjectEntity()
    {

    }

    public AnnotationObjectEntity(Guid id, int pageNumber, bool annotationType) : this()
    {
        Id = id;
        PageNumber = pageNumber;
        AnnotationType = privateToSelectedUserGroup;
    }

    public void Update(AnnotationObjectEntity annotationObject)
    {
        PageNumber = annotationObject.PageNumber;
        AnnotationType = annotationObject.AnnotationType;
    }
}

Я настроил свои объекты в моем DbContext, как показано ниже:

DbContext:

protected override void OnModelCreating(ModelBuilder modelBuilder) 
{
    modelBuilder.Entity<AnnotationEntity>(x =>
    {
        x.Property(y => y.Value).IsRequired();

       x.HasMany(p => p.AnnotationObjects).WithOne(p => p.Annotation)
            .OnDelete(DeleteBehavior.Cascade)
            .Metadata.PrincipalToDependent.SetPropertyAccessMode(PropertyAccessMode.Field);
    });
    modelBuilder.Entity<AnnotationObjectEntity>(x =>
    {
        x.HasKey(k => k.Id);
        x.Property(p => p.Id).HasColumnName("Id");
        x.HasOne(p => p.Annotation).WithMany(p => p.AnnotationObjects);
    });
}

После прочтения родительского объекта вместе с дочерним объектом, и если я удалю / обновлю существующий дочерний элемент и добавлю новый дочерний элемент, и когда я попытаюсь вызвать сохранение в базе данных, как показано ниже:

Репозиторий:

annotationEntity.UpdateAnnotation(annotationToUpdate);
_context.Annotation.Attach(annotationEntity);

//var changes = _context.ChangeTracker.Entries().Where(x => x.Entity is AnnotationObjectEntity).Select(x => (AnnotationObjectEntity)x.Entity).ToList();

//foreach (var change in changes)
//{
//   _context.Entry(change).State = EntityState.Added;
//}

await _context.SaveChangesAsync();

Я получаю следующую ошибку:

Ожидается, что операция с базой данных повлияет на 1 строку (строки), но фактически повлияла на 0 строк. Данные могли быть изменены или удалены после загрузки объектов. См. http://go.microsoft.com/fwlink/?LinkId=527962 для получения информации о понимании и обработке исключений optimisti c concurrency.

При дальнейшем анализе я обнаружил, что это происходит, когда я добавляю новые дочерние объекты к родительскому объекту . А также я заметил, что ChangeTracker имеет Entity State как Modified для вновь добавленных объектов. Когда я меняю его вручную на Added. Тогда SaveChangesAsync() работает.

Пожалуйста, объясните, что я делаю неправильно и почему ChangeTracker() неправильно определяет состояние объекта Added как Modified.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...