Я следую подходу 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
.