Сначала код EF: как удалить строку из коллекции сущности, следуя DDD?

Итак, вот сценарий:

DDD заявляет, что вы используете хранилище для получения совокупного корня, а затем используете его для добавления / удаления в любые имеющиеся у него коллекции.

Добавление просто, вы просто позвоните .Add(Item item) на Collection Вы хотите добавить в. Новая строка добавляется в базу данных при сохранении. Тем не менее, удаление отличается - вызов .Remove(Item item) не удаляет элемент из базы данных, он просто удаляет внешний ключ. Так что, хотя, да, технически он больше не является частью коллекции, он все еще находится в базе данных.

Читая вокруг, единственное решение - удалить его, используя контекст данных. Но в соответствии с DDD объект домена не должен знать о контексте данных, поэтому удаление должно выполняться вне домена.

Как правильно это сделать? Или приемлемо оставить базу данных, заполненную сиротами (возможно, запустив процедуру для их очистки)?

5 ответов

Решение

Я решил эту проблему в приложении, над которым я сейчас работаю, используя доменные события; концепция DDD, которую сказал Эрик Эванс, должна была быть в его книге.

В то время как объектам домена не разрешено знать о контексте объекта, IDomainEventHandler это - поэтому я получил DomainObjectDeletionHandler который удаляет "удаленные" объекты из контекста объекта, прежде чем элемент управления вернется на уровень приложения и изменения будут сохранены.

Для получения дополнительной информации я написал блог о своей реализации событий в домене и о том, как я подобрал все вместе.

Надеюсь, это поможет:)

редактировать

Например, если у вас есть Order класс, который имеет OrderItems коллекция типа OrderItem:

public class Order
{
    // Other stuff

    public void RemoveOrderItem(int orderItemId)
    {
        var orderItemToRemove = OrderItems.First(oi => oi.Id == orderItemId)

        OrderItems.Remove(orderItemToRemove);

        DomainEvents.Raise(new OrderItemRemoved(orderItemToRemove));
    }
}

При удалении дочерней сущности из коллекции EF оставляет ее как потерянную, удаляя только внешний ключ.

Если вы не хотите явно удалять его с помощью DbContext, вы можете использовать то, что называется "Идентификация отношений" ( http://msdn.microsoft.com/en-us/library/ee373856.aspx внизу).

Хитрость заключается в том, чтобы установить составной первичный ключ на дочернем элементе, включая первичный ключ родительского элемента.

После этого при удалении сущности из родительской коллекции она также будет удалена из таблицы.

Я не знаю, является ли это разработанным, но если у подробного объекта есть составной ключ, содержащий ключевые столбцы его главного объекта, он будет автоматически удален, если вы удалите его из коллекции главного объекта. Если у вас есть объект Order с ключом OrderID и навигационным свойством ICollection OrderLines, присвойте OrderLine составной ключ, содержащий OrderID и OrderLineID.

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

Я решил этот сценарий, настроив ссылочный столбец по мере необходимости и поведение удаления как

Пример:

      
modelBuilder.Entity<AggregateRoot>()
    .HasMany(x => x.Items)
    .WithOne()
    .IsRequired()
    .OnDelete(DeleteBehavior.Cascade);

В этом случае EF Core (6.x) больше не задает для столбца ссылки значение NULL, но удалил запись, просто удалив Itemот Itemsсбор сводного корня.

Решающей конфигурацией здесь было поведение удаления Cascade.

Почему бы не использовать два репозитория?

var parent = ParentRepo.Get(parentId);
parent.Children.Remove(childId); // remove it from the property Collection
ChildRepo.Delete(childId); // delete it from the database
ParentRepo.Commit(); // calls underlying context.SaveChanges()

Предполагая, что вы делитесь контекстами через IOC/DI, вызов commit с одним репо будет зафиксирован для обоих, в противном случае просто вызовите ChildRepo.Commit также.

Другие вопросы по тегам