Таблица EF Core Взаимно-Однозначной Двунаправленной Самореференции

#c# #entity-framework #one-to-one #self-reference #bidirectional

Вопрос:

У меня возникли некоторые проблемы с ядром EF.

Я получаю эту ошибку:

Исключение InvalidOperationException: Не удается сохранить изменения, поскольку в сохраняемых данных была обнаружена циклическая зависимость

Вот моя модель:

 public class Transaction
{
    public int TransactionId { get; set; }

    public int? MutualTransactionId { get; set; }
    public virtual Transaction MutualTransaction { get; set; }

    public long Debit { get; set; }
    public long Credit { get; set; }
}
 

И вот моя OnModelCreating конфигурация:

 public class Repository : DbContext
{
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Transaction>(entity =>
        {
            entity.HasKey(x => x.TransactionId);

            entity.HasOne(x => x.MutualTransaction)
                .WithOne()
                .HasForeignKey<Transaction>(x => x.MutualTransactionId)
                .OnDelete(DeleteBehavior.NoAction)
                .HasConstraintName("FK_Transactions_Mutuals");
        });
    }
}
 

Проблема возникает, когда я хочу ввести ряд данных в базу данных.

Сама по себе ошибка не важна. Я просто хочу знать, как создать двунаправленную таблицу самоссылок один к одному, используя ядро EF.

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

Комментарии:

1. Вы НЕ ДОЛЖНЫ Писать С Большой Буквы Каждое Слово В Вашем Английском Языке!

2. Вы можете вставить направленный ациклический график только за один вызов, чтобы сохранить изменения. Либо [добавить, сохранить, обновить, сохранить], либо, возможно, написать триггер для исправления первой записи при вставке второй.

Ответ №1:

вы можете попробовать, это может сработать :

  public class Transaction
    {
        public int TransactionId { get; set; }
        [ForeignKey("MutualTransaction"), Column(Order = 0)]
        public int? MutualTransactionId { get; set; }
        
    
        public long Debit { get; set; }
        public long Credit { get; set; }
        public virtual Transaction MutualTransaction { get; set; }

    }
 

Ответ №2:

То, что вы называете отношениями «Один к одному», на самом деле не то, что такое отношения «Один к одному», это самоназвивающиеся отношения «Многие к одному».

Вам не нужно явное сопоставление для этой связи, простое сопоставление атрибутов будет работать просто отлично, но если вам нужно явное сопоставление /w ModelBuilder:

     modelBuilder.Entity<Transaction>(entity =>
    {
        entity.HasKey(x => x.TransactionId);

        entity.HasOne(x => x.MutualTransaction)
            .WithMany()
            .HasForeignKey(x => x.MutualTransactionId)
            .OnDelete(DeleteBehavior.NoAction)
            .HasConstraintName("FK_Transactions_Mutuals");
    });
 

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

Это типичное отношение «многие к 1» в родительском стиле с самоназванием. Если вы хотите убедиться, что на сущность когда-либо ссылается только одна другая сущность (1 дочерний элемент), и у этого дочернего элемента может быть только 1 родитель, вам нужно либо применить это с помощью логики приложения, либо принять объединяющую таблицу «многие ко многим» с уникальными ограничениями, добавленными для каждого из ключей, составляющих составной PK.

Обновление: Если вы хотите, чтобы MutualTransaction ссылался на взаимозависимых, то это разрешено, но не принудительно. Например, если я хочу, чтобы транзакции A и B были взаимозависимыми, где Взаимное значение A равно B, а B равно A, то вы можете выполнить это с помощью приведенного выше сопоставления и соотношения «Многие к одному», но это подразумеваемое правило, которое должно выполняться приложением (а не базой данных), поэтому вам придется полагаться на код и, возможно, проверки работоспособности сценариев базы данных для регулирования его применения. Загвоздка в том, что для этого требуется 2 явных сохранения:

 var transactionA = new Transaction{ Name = "A" };
var transactionB = new Transaction{ Name = "B", MutualTransaction = transactionA };
context.Transactions.Add(transactionA);
context.Transactions.Add(transactionB);
context.SaveChanges();
transactionA.MutualTransaction = transactionB;
context.SaveChanges();
 

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

Т.е.

 var test = context.Transactions
    .Include(x => x.MutualTransaction)
    .Single(x => x.Name == "A");
// or
var test2 = context.Transactions
    .Include(x => x.MutualTransaction)
    .Single(x => x.Name == "B");
 

Было бы здорово, если бы это сработало:

 var transactionA = new Transaction{ Name = "A" };
var transactionB = new Transaction{ Name = "B", MutualTransaction = transactionA };
transactionA.MutualTransaction = transactionB;
context.Transactions.Add(transactionA);
context.Transactions.Add(transactionB);
context.SaveChanges();
 

но это приводит к проблеме круговой ссылки в EF.

Комментарии:

1. Но тогда как я могу получить доступ к родителю от ребенка или наоборот? Я имею в виду, что я могу получить взаимную транзакцию, просто позвонив, например var transaction2 = transaction1.MutualTransaction , но можно ли получить к ней обратный доступ из того же свойства? Я имею в виду, правда ли это : transaction2.MutualTransaction.TransactionId == transaction1.TransactionId

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