После нескольких дней изучения EF, чтобы понять (вроде ...), как это работает, я наконец понял, что у меня может быть большая проблема.
Представьте, что у меня есть две сущности: Pais
и UF
. Отношения между ними Pais (0..1) ... (*) UF
. Скриншот: http://i.imgur.com/rSOFU.jpg.
Сказал, что учтите, что у меня есть контроллер с именем UFController
, и у него есть действия для Edit
и Create
, которые просто хороши. Мои представления используют вспомогательный EditorFor
(или аналогичный) для входных данных, поэтому, когда я отправляю форму, контроллер получит объект UF
, заполненный всеми данными (автоматически) со ссылкой на почти пусто Pais
. Код моего просмотра (часть):
@* UF attributes *@
@Html.EditorFor(m => m.Sigla)
@Html.EditorFor(m => m.Descricao)
@Html.EditorFor(m => m.CodigoIBGE)
@Html.EditorFor(m => m.CodigoGIA)
@* Pais primary key ("ID") *@
@Html.EditorFor(m => m.Pais.Codigo) // Pais id
Контроллер Edit
Код действия:
[HttpPost]
public ActionResult Edit(UF uf)
{
try
{
if (ModelState.IsValid)
{
db.UFs.Attach(uf);
db.ObjectStateManager.ChangeObjectState(uf, EntityState.Modified);
db.SaveChanges();
return this.ClosePage(); // An extension. Just ignore it.
}
}
catch (Exception e)
{
this.ModelState.AddModelError("Model", e.Message.ToString());
}
return View(uf);
}
Когда я отправляю форму, это то, что действие получает как uf
:
{TOTALWeb.UF}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.UF}
(...)
CodigoGIA: 0
CodigoIBGE: 0
Descricao: "Foobar 2001"
ID: 936
Pais: {TOTALWeb.Pais}
PaisReference: {System.Data.Objects.DataClasses.EntityReference<TOTALWeb.Pais>}
И uf.Pais
:
{TOTALWeb.Pais}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.Pais}
Codigo: 0
CodigoBACEN: null
CodigoGIA: null
CodigoIBGE: null
Descricao: null
UF: {System.Data.Objects.DataClasses.EntityCollection<TOTALWeb.UF>}
Исходная информация (та, что в базе данных) uf.Pais.Codigo == 716
. Итак, сейчас я получаю обновленную информацию. Проблема в том, что контроллер не загружает FK в базу данных.
Я не хочу устанавливать EntityState от uf.Pais
до Modified
, потому что сама сущность не изменилась (я не изменил информацию из этой записи), но отношение был.
Другими словами, я пытаюсь изменить значение FK, указав uf.Pais
на другой экземпляр Pais
. Афаик, невозможно изменить состояние отношения на Modified
(сгенерировать исключение), поэтому я ищу альтернативные решения.
Я прочитал кучу тем, которые я нашел в Google об этой проблеме, но я все еще не нашел простого и элегантного решения. Последние, что я читал здесь на stackoverflow:
Несколько дней назад я задал вопрос о подобной проблеме ( Entity Framework 4.1 - EntityState по умолчанию для FK? ). Я не понимал, как EF работает в то время, поэтому теперь мне многое кажется ясным (вот почему я открываю новый вопрос).
Для действия Create
я тестировал это решение (предложенное Ладиславом по моему другому вопросу), но оно генерирует дополнительный выбор (который может быть в конечном итоге медленным для нас):
// Here UF.Pais is null
db.UFs.AddObject(uf);
// Create dummy Pais
var pais = new Pais { Id = "Codigo" };
// Make context aware of Pais
db.Pais.Attach(pais); // <- Executing a SELECT on the database, which -can- be slow.
// Now make the relation
uf.Pais = pais;
db.SaveChanges();
Я могу повторить это для Edit
(я полагаю), но я не хочу этот дополнительный SELECT.
Итак, в резюме: я пытаюсь использовать свойства навигации для отправки данных на контроллер и сохранения их непосредственно в базе данных, используя быстрый и простой способ (без особой путаницы с сущностью - эти простые, но у нас есть огромные и очень сложные из них с большим количеством ФК!). Мой вопрос: есть ли решение, которое не включает в себя выполнение другого запроса в базе данных (простой)?
Спасибо
Рикардо
PS: извините за любые английские ошибки и замешательство.
Обновление 1 : с использованием решения BennyM (вроде ..)
Я тестировал следующий код, но он не работает. Он выдает исключение: «Объект с таким же ключом уже существует в ObjectStateManager. ObjectStateManager не может отслеживать несколько объектов с одним и тем же ключом». Наверное, потому что Паис уже в контексте, наверное?
Я использую класс Entities (созданный EF) в качестве контекста. Кроме того, я не знаю, каков метод Entry
, и я не знаю, где это. Просто для «удовольствия» я проверил это:
// Attach the Pais referenced on editedUF, since editedUF has the new Pais ID, not the old one.
Pais existingPais = new Pais { Codigo = editedUF.Pais.Codigo };
db.Paises.Attach(existingPais);
// Attach the edited UF.
db.UFs.Attach(editedUF);
// Set the correct Pais reference (ignoring the current almost-null one).
editedUF.Pais = existingPais;
// Change the object state to modified.
db.ObjectStateManager.ChangeObjectState(editedUF, EntityState.Modified);
// Save changes.
db.SaveChanges();
return this.ClosePage();
Исключение выдается, когда я пытаюсь присоединить отредактированный UF к текущему контексту. Я работаю с этой идеей прямо сейчас, пытаясь найти другие решения. Кроме того, вы правы, BennyM, присоединение Pais к контексту не создает дополнительного SELECT. Я не знаю, что случилось в то время, с базой данных ничего не происходит.
Тем не менее, это ручное решение: я должен сделать это для каждого FK.Вот чего я пытаюсь избежать.Видите ли, некоторые программисты, даже если вы объясните 100 раз, не забудут сделать это с каждый FK.Со временем это вернется ко мне, поэтому я стараюсь избегать всего, что может привести к ошибкам (базы данных или кода), чтобы каждый мог работать без стресса.:)