Как удалить дочерние сущности перед родителем с помощью Entity Framework CF?
Я пытаюсь сначала использовать EF-код, чтобы удалить запись db (deleteMe
), а потом дети (deleteMe.Prices
).
foreach (var deleteMe in deleteThese)
{
// Delete validation
if(CanDeleteItem(deleteMe.ItemId))
{
db.Entry(deleteMe).State = EntityState.Deleted;
foreach (var item in deleteMe.Prices)
{
db.Entry(item).State = EntityState.Deleted; // cascade delete
}
}
}
db.SaveChanges();
Тем не менее, Entity Framework, похоже, не может отслеживать тот факт, что дочерние записи должны быть удалены перед родителем. Я получаю сообщение об ошибке:
Оператор DELETE противоречил ограничению REFERENCE "ItemPrice_Item".
Конфликт произошел в базе данных "DEVDB", таблице "dbo.ItemPrices", column 'Item_ItemId'.
Заявление завершено.
Как выполнить это удаление в EF?
Ответы
Ответ 1
Я закончил поиск быстрой строки, которая сделает это для меня:
foreach (var deleteMe in deleteThese)
{
// Delete validation
if(CanDeleteItem(deleteMe.ItemId))
{
///
deleteMe.Prices.ToList().ForEach(p => db.ItemPrices.Remove(p));
///
db.Entry(deleteMe).State = EntityState.Deleted;
}
}
db.SaveChanges();
Ответ 2
EF6
context.Children.RemoveRange(parent.Children)
Ответ 3
Каскадное удаление в EF зависит от каскадного удаления, настроенного по отношению к базе данных, поэтому, если у вас нет каскадного удаления, настроенного в базе данных, вы должны сначала загрузить все цены товара в приложение и пометить их как удаленные.
Ответ 4
Наилучшим образом самым простым решением было бы сначала перебрать цены и вызвать сохранение изменений, а затем установить запись для удаления для deleteMe и снова вызвать изменения сохранения, но вы проверили это: Код структуры Entity сначала удаляется с помощью каскада? Кажется, вы хотите.
Любопытно, но почему вы просто не удаляете сущности из контекста для удаления, а вместо этого устанавливаете состояние записи?
Другой вариант - установить каскадное удаление http://blogs.msdn.com/b/alexj/archive/2009/08/19/tip-33-how-cascade-delete-really-works-in-ef.aspx
Сделайте что-нибудь подобное (не проверено, но, надеюсь, вы получите jist):
using (TransactionScope scope = new TransactionScope())
{
foreach (var deleteMe in deleteThese)
{
// Delete validation
if(CanDeleteItem(deleteMe.ItemId))
{
foreach (var item in deleteMe.Prices)
{
db.Entry(item).State = EntityState.Deleted; // cascade delete
}
db.SaveChanges();
db.Entry(deleteMe).State = EntityState.Deleted;
}
}
db.SaveChanges();
scope.Complete();
}
Дополнительно вы можете позвонить:
db.Prices.Remove(item);
и
db.DeleteMes.Remove(deleteMe);
вместо установки состояния записи. Не уверен, есть ли разница между сценами между ними.
Ответ 5
Каскадное удаление в инфраструктуре Entity является сложной задачей, так как вам нужно быть уверенным в графе объектов сущности объекта. Лучше всегда писать тест интеграции для этих каскадных удалений.
Если вы попытаетесь удалить родительский объект в EF, он попытается выполнить инструкции delete для любых дочерних объектов в текущем dbcontext. В результате он не будет инициализировать дочерние объекты, которые не были загружены. Это приведет к ошибке выполнения RDBMS, которая нарушает ограничение внешнего ключа. Чтобы быть в безопасности, убедитесь, что все зависимые объекты загружены в текущий dbcontext перед удалением.
Ответ 6
Если ваш объект является саморегуляцией, вы можете удалить как "много-ко-многим", так и "один ко многим", используя приведенный ниже метод. Просто не забудьте затем вызвать db.SaveChanges():)
[HttpPost, ActionName("Delete")]
[ValidateAntiForgeryToken]
public ActionResult DeleteConfirmed(int id)
{
Object obj = this.db.Objects.Find(id);
this.DeleteObjectAndChildren(obj);
this.db.Objects.Remove(obj);
this.db.SaveChanges();
return this.Json(new { success = true });
}
/// <summary>
/// This deletes an object and all children, but does not commit changes to the db.
/// - MH @ 2016/08/15 14:42
/// </summary>
/// <param name="parent">
/// The object.
/// </param>
private void DeleteObjectAndChildren(Object parent)
{
// Deletes One-to-Many Children
if (parent.Things != null && parent.Things.Count > 0)
{
this.db.Things.RemoveRange(parent.Things);
}
// Deletes Self Referenced Children
if (parent.Children != null && parent.Children.Count > 0)
{
foreach (var child in parent.Children)
{
this.DeleteObjectAndChildren(child);
}
this.db.Objects.RemoveRange(parent.Children);
}
}
Ответ 7
У меня была похожая проблема, и мне показалось, что я не правильно установил отношения между Родителем и Ребенком в их соответствующих классах.
Мое исправление заключалось в добавлении атрибутов, указанных ниже, в класс Child для свойства, представляющего его Parent Id
public class Child
{
[Key, Column(Order = 1)]
public string Id { get; set; }
[Key, ForeignKey("Parent"), Column(Order = 2)] // adding this line fixed things for me
public string ParentId {get; set;}
}
public class Parent
{
[Key, Column(Order = 1)]
public string Id { get; set; }
...
public virtual ICollection<Child> Children{ get; set; }
}
Ответ 8
Следующее работает довольно эффективно.
Для каждой реляционной таблицы в вашей базе данных добавьте следующее (в вашем файле контекста).
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder
.Entity<TableA>()
.HasMany(x => x.TableB)
.WithRequired(x => x.TableA)
.WillCascadeOnDelete();
modelBuilder
.Entity<TableC>()
.HasMany(x => x.TableD)
.WithRequired(x => x.TableC)
.WillCascadeOnDelete();
modelBuilder
.Entity<TableE>()
.HasMany(x => x.TableF)
.WithRequired(x => x.TableE)
.WillCascadeOnDelete(); }
Затем в своем коде не забудьте загрузить эти таблицы перед удалением
context.TableA.Load();
context.TableB.Load();
context.TableC.Load();
context.TableD.Load();
context.TableE.Load();
context.TableF.Load();
var tableAEntity= TableA.Where(x => x.Condition == [yourcondition].FirstOrDefault();
context.TableA.Remove(tableAEntity);
context.SaveChanges();
Это приведет к быстрому и эффективному удалению сущности (записи) из основной таблицы записей и всех записей связанных таблиц (связанных через FK) (даже если отношения глубоко каскадируются на нескольких уровнях).