После нескольких дней изучения EF, чтобы понять (вроде ..), как это работает, я наконец понял, что у меня может быть большая проблема.
Представьте, что у меня есть две сущности: Pais
и UF
. Отношения между ними Pais (0..1) ... (*) UF
. Скриншот: https://i.imgur.com/rSOFU.jpg.
Сказал, что у меня есть контроллер с именем UFController
, и у него есть действия для Edit
и Create
, которые вполне подходят. Мои представления используют помощник EditorFor
(или аналогичный) для входных данных, поэтому, когда я отправлю форму, контроллер получит объект UF
, заполненный всеми данными (автоматически) со ссылкой на почти пустой em > 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
. Afaik, невозможно изменить состояние отношения на Modified
(выбросить исключение), поэтому я ищу альтернативные решения.
Я прочитал кучу тем, которые нашел в Google, по поводу такого рода проблем, но до сих пор не нашел простого и элегантного решения. Последние, которые я прочитал здесь, в stackoverflow:
- Как сначала работать со свойствами навигации (/ внешними ключами) в коде ASP.NET MVC 3 и EF 4.1
- ASP.NET MVC с сильной типизацией и ADO.NET Entity Framework
- Получение ошибки 3007 при добавлении модели сущности
Несколько дней назад я задал вопрос о подобной проблеме (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.
Итак, в резюме: я пытаюсь использовать свойства навигации для отправки данных в контроллер и сохранения их непосредственно в базе данных, используя быстрый и простой способ (без особых проблем с сущностью - они просты, но у нас есть огромные возможности). и очень сложные с большим количеством FK!). Мой вопрос: есть решение, которое не требует выполнения другого запроса в базе данных (простого)?
Спасибо,
Рикардо
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();
Исключение выдается, когда я пытаюсь прикрепить editedUF к текущему контексту. Я работаю над этой идеей прямо сейчас, пытаюсь найти другие решения. Кроме того, вы правы, BennyM, присоединение Pais к контексту не генерирует дополнительный SELECT. Я не знаю, что произошло в тот раз, на самом деле это ничего не меняет с базой данных.
Тем не менее, это ручное решение: я должен делать это для каждого FK. Этого я пытаюсь избежать. Видите ли, некоторые программисты, даже если вы объясните 100 раз, не вспомнят, что нужно делать это с каждым FK. В конце концов, это вернется ко мне, поэтому я стараюсь избегать всего, что может привести к ошибкам (базы данных или кода), чтобы каждый мог работать без стресса. :)