Есть ли способ найти все сущности, у которых были удалены их отношения? - PullRequest
7 голосов
/ 17 декабря 2011

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

Но Entity Framework делает это сложно.Я могу вставить в коллекцию (в моем бизнес-уровне) без ссылки на ObjectContext:

order.Containers.Add(new Container { ContainerId = containerId, Order = order });

И это прекрасно экономит, когда приходит время сделать SaveChanges() на уровне данных.

Но чтобы удалить элемент из коллекции, мне нужна ссылка на ObjectContext.(Я рассматриваю дело № 1 в этом руководстве по удалению сущностей EF .) Если я просто сделаю это:

 delContainers.ForEach(container => order.Containers.Remove(container));

Затем, когда я звоню SaveChanges(), я получаю исключение, сообщающее, чтоМне нужно удалить объект и ссылку.

Итак, мои варианты, как я вижу это:

  1. Чтобы передать делегата в мою бизнес-логику, который будет вызывать сущностьFramework ObjectContext Удалить метод.
  2. Или (я надеюсь) найти способ удалить все сущности, для которых были удалены их ссылки, и фактически удалить их. (Прямо перед вызовом SaveChanges() в моем слое данных.)

Кто-нибудь знает способ сделать это?

ОБНОВЛЕНИЕ:

Я пробовал это:

// Add an event when Save Changes is called
this.ObjectContext.SavingChanges += OnSavingChanges; 

...

void OnSavingChanges(object sender, EventArgs e)
{
   var objectStateEntries = ObjectContext.ObjectStateManager
                                  .GetObjectStateEntries(EntityState.Deleted);

   foreach (var objectStateEntry in objectStateEntries)
   {
       if (objectStateEntry.IsRelationship)
       {
            // Find some way to delete the related entity
       }
   }
}

Но ничего, хотя я удалил отношение, набор удаленных элементов пуст.

(Я тоже пытался просмотреть все элементы, и мои отношения там отсутствуют. Ясно, что есть что-то фундаментальное, чего я не понимаю в ObjectStateManager.)

Ответы [ 2 ]

4 голосов
/ 17 декабря 2011

Правильное решение для EF - пункт 3. из связанной статьи.Это означает распространение FK на основной объект в PK для зависимого объекта.Это сформирует нечто, называемое идентифицирующим отношением , которое автоматически удаляет зависимый объект, когда он удаляется из родительского объекта.

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

public partial class YourObjectContext
{
    public override int SaveChanges(SaveOptions options)
    {
        foreach (ObjectStateEntry relationEntry in ObjectStateManager
                                             .GetObjectStateEntries(EntityState.Deleted)
                                             .Where(e => e.IsRelationship))
        {
            var entry = GetEntityEntryFromRelation(relationEntry, 0);
            // Find representation of the relation 
            IRelatedEnd relatedEnd = entry.RelationshipManager
                                          .GetAllRelatedEnds()
                                          .First(r => r.RelationshipSet == relationEntry.EntitySet);

            RelationshipType relationshipType = relatedEnd.RelationshipSet.ElementType;
            if (!SkipDeletion(relationshipType))
            {
                // Now we know that model is inconsistent and entity on many side must be deleted
                if (!(relatedEnd is EntityReference)) // related end is many side
                {
                    entry = GetEntityEntryFromRelation(relationEntry, 1);
                }

                if (entry.State != EntityState.Deleted)
                {
                    context.DeleteObject(entry.Entity);
                }
            }
        }

        return base.SaveChanges();
    }

    private ObjectStateEntry GetEntityEntryFromRelation(ObjectStateEntry relationEntry, int index)
    {
        var firstKey = (EntityKey) relationEntry.OriginalValues[index];
        ObjectStateEntry entry = ObjectStateManager.GetObjectStateEntry(firstKey);
        return entry;
    }

    private bool SkipDeletion(RelationshipType relationshipType)
    {
        return
            // Many-to-many
            relationshipType.RelationshipEndMembers.All(
                r => r.RelationshipMultiplicity == RelationshipMultiplicity.Many) ||
            // ZeroOrOne-to-many 
            relationshipType.RelationshipEndMembers.Any(
                r => r.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne);
    }
}

Чтобы это работало, ваши сущности должны быть включены для динамического отслеживания изменений (все свойства должны быть виртуальными, а сущность - прокси), или вы должнывручную вызовите DetectChanges.

В случае сопоставления внешних ключей ситуация, вероятно, будет намного хуже, потому что вы не найдете никаких удаленных отношений в менеджере состояний.Вам нужно будет отслеживать изменения в коллекциях или ключах вручную и сравнивать их, чтобы найти несоответствия (я не уверен, как это сделать в общих чертах). Сопоставление внешнего ключа IMHO требует идентифицирующего отношения.Использование свойств FK уже означает, что вы включили в свою модель дополнительную зависимость от персистентности.

2 голосов
/ 17 декабря 2011

Один из способов - написать обработчик изменений в вашем слое данных:

    private void ContainersChanged(object sender,
        CollectionChangeEventArgs e)
    {
        // Check for a related reference being removed. 
        if (e.Action == CollectionChangeAction.Remove)
        {
            Context.DeleteObject(e.Element);
        }
    }

Есть много мест, где вы можете подключить это - в конструкторе или репозитории вашего объекта, получить или SavingChanges или где угодно:

    entity.Containers.AssociationChanged += new CollectionChangeEventHandler(ContainersChanged);

Теперь вы можете удалить связь из другого места, и она будет «каскадно» соединяться с сущностью.

...