С учетом базы данных, отслеживания лиц и их необязательного супруга в качестве внешнего ключа, ссылающегося на себя:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int? SpouseId { get; set; }
}
Ядро Framwork Entity DbContext выглядит следующим образом, обратите внимание на DeleteBehavior.SetNull
:
public class PersonsContext : DbContext
{
public PersonsContext(DbContextOptions<PersonsContext> options) : base(options) {}
public DbSet<Person> Persons { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder
.Entity<Person>()
.HasOne(typeof(Person))
.WithOne()
.HasForeignKey(typeof(Person), nameof(Person.SpouseId))
.IsRequired(false)
.OnDelete(DeleteBehavior.SetNull);
}
}
Это даже не генерирует модель. Ошибка говорит:
Введение ограничения FOREIGN KEY 'FK_Persons_Persons_SpouseId' в
Таблица «Персоны» может вызывать циклы или несколько каскадных путей. Уточнить
НА УДАЛИТЬ НЕТ ДЕЙСТВИЙ или НА ОБНОВИТЬ НЕТ ДЕЙСТВИЙ, или изменить другие ИНОСТРАННЫЕ
КЛЮЧЕВЫЕ ограничения.
Хорошо, вторая попытка. Мы позаботимся о том, чтобы сломать ссылку самостоятельно. FK будет моделироваться с помощью DeleteBehavior.Restrict
:
public class PersonsContext : DbContext
{
public PersonsContext(DbContextOptions<PersonsContext> options) : base(options) {}
public DbSet<Person> Persons { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder
.Entity<Person>()
.HasOne(typeof(Person))
.WithOne()
.HasForeignKey(typeof(Person), nameof(Person.SpouseId))
.IsRequired(false)
.OnDelete(DeleteBehavior.Restrict);
}
}
Простой тест пытается обнулить ссылку с обеих сторон, а затем удаляет женатого человека, оставляя другого человека без SpouseId
:
[Fact]
public void Manually_Remove_Reference()
{
using (var personsContext = new PersonsContext(DbContextOptions))
{
var him = new Person {Id = 1, Name = "Him", SpouseId = 2};
var her = new Person {Id = 2, Name = "Her", SpouseId = 1};
personsContext.Persons.Add(him);
personsContext.Persons.Add(her);
personsContext.SaveChanges();
}
using (var personsContext = new PersonsContext(DbContextOptions))
{
var him = personsContext.Persons.Find(1);
var her = personsContext.Persons.Find(2);
him.SpouseId = null;
her.SpouseId = null;
personsContext.Persons.Remove(him);
personsContext.SaveChanges();
}
using (var personsContext = new PersonsContext(DbContextOptions))
{
Assert.Null(personsContext.Find<Person>(1));
}
}
В результате:
System.InvalidOperationException: невозможно сохранить изменения, поскольку в данных, которые необходимо сохранить, была обнаружена циклическая зависимость: 'ForeignKey: Person {' SpouseId '} -> Person {' Id '} Уникальный, ForeignKey: Person {' SpouseId ' } -> Person {'Id'} Уникальный '.
Трассировка стека:
at Microsoft.EntityFrameworkCore.Internal.Multigraph`2.BatchingTopologicalSort(Func`2 formatCycle)
at Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.TopologicalSort(IEnumerable`1 commands)
at Microsoft.EntityFrameworkCore.Update.Internal.CommandBatchPreparer.<BatchCommands>d__8.MoveNext()
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(Tuple`2 parameters)
at Microsoft.EntityFrameworkCore.Storage.Internal.SqlServerExecutionStrategy.Execute[TState,TResult](TState state, Func`3 operation, Func`3 verifySucceeded)
at Microsoft.EntityFrameworkCore.ExecutionStrategyExtensions.Execute[TState,TResult](IExecutionStrategy strategy, TState state, Func`2 operation)
at Microsoft.EntityFrameworkCore.Update.Internal.BatchExecutor.Execute(IEnumerable`1 commandBatches, IRelationalConnection connection)
at Microsoft.EntityFrameworkCore.Storage.RelationalDatabase.SaveChanges(IReadOnlyList`1 entries)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(IReadOnlyList`1 entriesToSave)
at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
at Microsoft.EntityFrameworkCore.DbContext.SaveChanges()
at Persistence.Tests.UnitTest1.Manually_Remove_Reference() in C:\Users\MarcWittke\source\scratchpad\Persistence\Persistence.Tests\UnitTest1.cs:line 58
Добавление больше SaveChanges()
после обнуления ссылок не имеет значения. Что действительно работает, так это: разорвать ссылку в отдельном экземпляре DbContext, сохранить его, открыть новый и удалить запись. Но это больше не будет атомным.
Полный код: https://github.com/marcwittke/DeletingOptionallySelfReferencedRecord