Связанные 1-1 отношения не синхронизируют PK, как ожидалось - PullRequest
0 голосов
/ 27 марта 2012

Я попытался найти SO, но все результаты, которые я обнаружил, похоже, касаются обновления PK сущностей, которые уже были сохранены в БД. Мой случай другой.

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

A <- B <- C

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

Каждое отношение представлено FK, который идет от PK дочернего объекта к PK родительского объекта. Каждый PK представляет собой столбец uniqueidentifier Id со сгенерированным клиентом значением. Я сгенерировал модель 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, и я не смог сменить их PK. Но это не тот случай, оба объекта были созданы.

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

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

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

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

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

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

1 Ответ

3 голосов
/ 27 марта 2012

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

ОБНОВЛЕНИЕ

Версия, которая обрабатывает существующие экземпляры 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();
  }
}
...