0

Я попытался найти SO, но все результаты, которые я нашел, похоже, касаются обновления PK объектов, которые уже были сохранены в БД. Мое дело другое.Несколько взаимосвязанных отношений 1-1 не синхронизируют ПК как ожидалось

У меня есть 3 таблицы в базе данных с отношениями 1-0..1. Отношения выглядеть следующим образом:

A <- B <- C 

где «< -» представляет собой связь и указывает на основной цели. То есть каждый B всегда имеет родственный A, но A может не иметь B. Другими словами, мощность A равна 1, а B - 0..1.

Каждая взаимосвязь представлена ​​FK, которая переходит от PK дочернего объекта к PK родительского объекта. Каждый PK представляет собой столбец uniqueidentifierId с клиентским значением. Я создал модель EF 4 из базы данных, которая имеет те же отношения с той же мощью.

Я пытаюсь добавить дочерние элементы B и C к существующему объекту A. По соображениям дизайна пара новых экземпляров создается в одном мире кода, а объект A связан с объектом B в другом. Кроме того, я не хочу, чтобы последний знал, что существует C.

Вот как код B и C создания выглядит следующим образом:

public B CreateB() 
{ 
    return new B 
    { 
     Id = Guid.NewGuid(), 
     C = new C(), 
    }; 
} 

А теперь по ссылке и сохранить код:

// a is an instance of A that has been loaded from DB 
// and hence has a persistent Id value. 
// b is a just-created instance of B 
// that has a non-persistent Id value and null reference to A. 
void SaveBlahBlahBlah(A a, B b) 
{ 
    // At this point b and c have the same Id value. 
    // It differs from a's Id, but that's expected, they haven't been linked yet. 
    b.A = a; 
    // At this point b receives a's Id value, but c keeps the original one, 
    // therefore the existing b-c link gets broken! 

    using(var ctx = new MyContext()) 
    { 
    ctx.As.Attach(a); // This throws exception saying 
    // I've violated referential integrity. 
    // It doesn't say which relationship is broken, 
    // but I guess it's the B-C one since 
    // the debugger shows them to have different values if PKs 

    ctx.Bs.AddObject(b); 

    ctx.SaveChanges(); 
    } 
} 

Я попытался это и с генератором по умолчанию EF в коде (тот, который использует класс EF Entity в качестве базового класса для сгенерированных объектов) и с генератором кода Self-Tracking Entities. Результат тот же.

Итак, код падает. Причиной может быть то, что после того, как A и B были связаны, B и C получают разные значения PK, которые являются незаконными для объектов с отношением 1-1.

То, что я ожидал, было C, чтобы автоматически получить его PK, синхронизированное со значением B, полученным от экземпляра A. Это кажется разумным, потому что я работаю с графиком объекта, у меня есть существующее отношение B - C, которое в порядке, и я ожидаю, что он останется ОК после ссылки B с A. Почему это сломается? Я бы это понял, если в базе данных существовали B или C, и я не смог изменить их ПК. Но дело обстоит не так, обе идеи были созданы.

Я не могу разбить цепочку ключей, используя отдельные столбцы PKs для FK, потому что EF требует, чтобы обе стороны отношения 1-1 были PK.

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

Я считаю, что смогу обновить шаблон T4 генератора STE, чтобы каскадировать обновления PK вниз по отношениям 1-1. Но я не слишком хорошо знаком с T4 и не слишком счастлив сделать это.

У меня 2 вопроса:

  1. Это мое ожидание каскадных обновлений PK в моем случае неправильно по некоторым причинам? (Кажется, странно, хотя) I.e., это ошибка или особенность?
  2. Есть ли другие и, возможно, более простые способы устранения проблемы, чем изменение шаблона STE? Может быть, некоторые магические варианты в EF-сопоставлениях или контекстах?

Заранее спасибо.

ответ

3

Проблема в том, что служба, которая обрабатывает присвоение идентификаторов от одного объекта, на который ссылается, является контекстом. Но в то время, когда вы действительно делаете ассоциацию, ни один объект не находится в контексте. Обычно это не проблема, потому что отношения будут исправлены, когда вы добавите B в контекст.

К сожалению, вы этого не сделаете. Вместо этого вы создаете дополнительные отношения с A, но затем лежите в контексте и утверждаете, что все уже исправлено. Точнее, вы вызываете EntitySet.Attach, что действительно предназначено только для уже сфабрикованных объектов.

С другой стороны, код, как это должно работать нормально:

public B CreateB() 
{ 
    return new B 
    { 
     Id = Guid.NewGuid(), 
     C = new C(), 
    }; 
} 

void SaveBlahBlahBlah(A a, B b) 
{  
    using(var ctx = new MyContext()) 
    {  
    ctx.Bs.AddObject(b); 

    ctx.SaveChanges(); 
    } 
} 

Обратите внимание, что все, что я сделал здесь, это удалить проблемный код, который не имеет ничего общего с отношениями между B и C .

Одним словом, остерегайтесь Attach. Вам нужно знать, что вы делаете, когда называете это.

UPDATE

версия, которая обрабатывает существующие экземпляры из A:

void SaveBlahBlahBlah(A a, B b) 
{  
    Debug.Assert(a.B != b); 

    using(var ctx = new MyContext()) 
    {  
    ctx.As.Attach(a); 

    a.B = b; // it's crucial that this link is set after attaching a to context! 

    ctx.Bs.AddObject(b); 

    ctx.SaveChanges(); 
    } 
} 
+1

+1 Очень ясное объяснение! –

+0

Спасибо. Я должен отметить, что я использовал «Attach», потому что без него EF попытался вставить 'A', что привело к ошибке, поскольку' A' уже существовал в БД. С тех пор я изменил модель и аромат (я использовал EFCF). Поэтому, возможно, ваше предложение будет работать, я попробую. –

+0

Вы можете использовать 'Attach', но вам нужно сделать это * до * вы делаете отношения с B. –

Смежные вопросы