EntityFramework: Как настроить Cascade-Delete для аннулирования внешних ключей
Документация EntityFramework утверждает, что возможно следующее поведение:
Если внешний ключ зависимого объекта является нулевым, Code First делает не устанавливать каскадное удаление в отношении, а когда основной удаленный внешний ключ будет установлен на нуль.
(из http://msdn.microsoft.com/en-us/jj591620)
Однако я не могу добиться такого поведения.
У меня есть следующие Entities, определенные с помощью code-first:
public class TestMaster
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<TestChild> Children { get; set; }
}
public class TestChild
{
public int Id { get; set; }
public string Name { get; set; }
public virtual TestMaster Master { get; set; }
public int? MasterId { get; set; }
}
Вот конфигурация отображения Fluent API:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<TestMaster>()
.HasMany(e => e.Children)
.WithOptional(p => p.Master).WillCascadeOnDelete(false);
modelBuilder.Entity<TestChild>()
.HasOptional(e => e.Master)
.WithMany(e => e.Children)
.HasForeignKey(e => e.MasterId).WillCascadeOnDelete(false);
}
Внешний ключ имеет значение NULL, свойство навигации отображается как Необязательно, поэтому я ожидаю, что каскадное удаление будет работать, как описано как MSDN, т.е. свести на нет MasterID всех дочерних элементов и затем удалить главный объект.
Но когда я действительно пытаюсь удалить, я получаю ошибку нарушения FK:
using (var dbContext = new TestContext())
{
var master = dbContext.Set<TestMaster>().Find(1);
dbContext.Set<TestMaster>().Remove(master);
dbContext.SaveChanges();
}
В SaveChanges() он выдает следующее:
System.Data.Entity.Infrastructure.DbUpdateException : An error occurred while updating the entries. See the inner exception for details.
----> System.Data.UpdateException : An error occurred while updating the entries. See the inner exception for details.
----> System.Data.SqlClient.SqlException : The DELETE statement conflicted with the REFERENCE constraint "FK_dbo.TestChilds_dbo.TestMasters_MasterId". The conflict occurred in database "SCM_Test", table "dbo.TestChilds", column 'MasterId'.
The statement has been terminated.
Я делаю что-то неправильно или я неправильно понял, что говорит MSDN?
Ответы
Ответ 1
Это работает так, как описано, но статья о MSDN пропускает, чтобы подчеркнуть, что он работает, только если дети загружаются в контекст, а не только родительский объект. Таким образом, вместо использования Find
(который загружает только родителя) вы должны использовать загрузку с помощью Include
(или любым другим способом для загрузки детей в контекст):
using (var dbContext = new TestContext())
{
var master = dbContext.Set<TestMaster>().Include(m => m.Children)
.SingleOrDefault(m => m.Id == 1);
dbContext.Set<TestMaster>().Remove(master);
dbContext.SaveChanges();
}
Это приведет к удалению мастера из базы данных, установите все внешние ключи в объектах Child
на null
и напишите инструкции UPDATE для детей в базу данных.
Ответ 2
После следующего @Slauma большого ответа я все еще получал ту же ошибку, что и OP.
Итак, не будь таким наивным, как я, и подумайте, что приведенные ниже примеры приведут к тому же результату.
dbCtx.Entry(principal).State = EntityState.Deleted;
dbCtx.Dependant.Where(d => d.PrincipalId == principalId).Load();
// code above will give error and code below will work on dbCtx.SaveChanges()
dbCtx.Dependant.Where(d => d.PrincipalId == principalId).Load();
dbCtx.Entry(principal).State = EntityState.Deleted;
Сначала загрузите детей в контекст до, чтобы удалить состояние объекта (если вы это делаете).