#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. Мне пришлось попробовать это, когда я вернулся к своим тестовым проектам, прежде чем я ответил, но, похоже, можно использовать эту взаимную транзакцию для ваших совместных ссылок. Я обновил свой ответ выше, чтобы описать, как это делается, и предостережения такого подхода.