ASP.NET MVC с EF 4.1 Свойства навигации - PullRequest
5 голосов
/ 09 августа 2011

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

Ответы [ 2 ]

2 голосов
/ 12 августа 2011

Я отвечаю на свой вопрос, потому что нашел простое решение (по крайней мере, в моем случае).Мой сценарий использует много представлений для ввода данных (что означает, что у меня много сущностей).Мне нужно было простое и удобное в использовании решение, поэтому я удалил весь файл EDMX сущностей (Ctrl + A, Удалить!).

Затем я решил снова добавить Pais и UF сущности, но проверяяфлажок для выставления атрибута FK.Сначала я думаю, что они не могут работать вместе, но они могут, но вы должны быть немного осторожны в том, как его использовать.Теперь они связаны со свойствами навигации и открытым FK.

Причина, по которой я не смог добавить атрибут FK, заключается в том, что я делал это вручную.Используя «Обновить модель из базы данных», снова проверив правильную опцию, она работала безупречно.

В моем представлении редактирования я устанавливаю идентификатор Pais в атрибут FK, а не Pais.Codigo.Причина, по которой я это делаю, заключается в том, что атрибут FK является скалярным свойством, и тогда я могу обнаружить изменения.

Это текущий код представления для ввода Pais (это не совсем так, но похоже наэто):

@Html.EditorFor(m => m.PaisCodigo)

Кстати, PaisCodigo это ФК.Да, это может немного запутать с Pais.Codigo, но мы не определили никаких правил именования (пока).Будем благодарны за любые предложения по этой идее.

Окончательный код действия Edit выглядит следующим образом (я убрал обработку ошибок, чтобы она выглядела просто!):

[HttpPost]
public ActionResult Edit(UF editedUF)
{
    if (ModelState.IsValid)
    {
        // Attach the edited UF into the context and change the state to Modified.
        db.UFs.Attach(editedUF);
        db.ObjectStateManager.ChangeObjectState(editedUF, EntityState.Modified);

        // Save changes.
        db.SaveChanges();

        // Call an extension (it's a redirect action to another page, just ignore it).
        return this.ClosePage();
    }
}

Вот чтоПолучается, когда я отправляю форму для editedUF:

{TOTALWeb.UF}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.UF}
(...)
CodigoGIA: 0
CodigoIBGE: 0
CodigoPais: 0 <-- new Pais ID!
Descricao: "Foobar 2000"
ID: 902
Pais: {TOTALWeb.Pais}
PaisReference: {System.Data.Objects.DataClasses.EntityReference<TOTALWeb.Pais>}
Sigla: "RI"
Usuarios: {System.Data.Objects.DataClasses.EntityCollection<TOTALWeb.Usuario>}

Как видите, CodigoPais указывает на новый Pais ID.

О editedUF.PaisСвойство навигации, есть небольшая деталь.Прежде чем вложить его в контекст, он null .Но, эй, после добавления вот что происходит:

{TOTALWeb.Pais}
base {System.Data.Objects.DataClasses.EntityObject}: {TOTALWeb.Pais}
(...)
Codigo: 0
CodigoBACEN: 1058
CodigoGIA: 0
CodigoIBGE: null
Descricao: "Brasil"
UFs: {System.Data.Objects.DataClasses.EntityCollection<TOTALWeb.UF>}

Итак, оно заполнено.Стоимость этого запроса должна составлять один запрос, но я не смог зафиксировать его на мониторе.

Другими словами, просто откройте FK, измените его с помощью View и используйте свойство навигации, чтобы сделать коднемного яснее.Это оно!:)

Спасибо всем,

Рикардо

PS: я использую dotConnect для Oracle в качестве основы для EF 4.1.Мы не используем SQL Server (по крайней мере, на данный момент).«Монитор», о котором я говорил ранее, был devArt dbMonitor, поэтому я могу видеть все запросы, отправленные в базу данных Oracle.И, опять же, извините за любые английские ошибки!

0 голосов
/ 10 августа 2011

Если вы включите внешние ключи в вашу модель. Поэтому добавьте свойство PaisId к объекту UF, вы можете установить его напрямую, и оно обновит ассоциацию.

using (var db = new Context())
{
   db.UFs.Attach(editedUF);
   editedUF.PaisId = theForeignKey;
   db.Entry(editedUF).State = EntityState.Modified;
   db.SaveChanges();
}

Также я протестировал подход, который вы уже упоминали в своем вопросе, и я не получаю этот дополнительный выбор при присоединении сущности. Так что это работает также:

using (var db = new Context())
{
   ExistingPais pais = new Pais{Id =theId};
   db.Pais.Attach(pais);
   db.UF.Attach(editedUF);
   editedUF.Pais = pais;
   db.Entry(editedUF).State = EntityState.Modified;
   db.SaveChanges();
}

Предположим, ваш код выглядит следующим образом:

public class  Pais
{
    public int Id { get; set; }
    public virtual ICollection<UF> UFs { get; set; }
} 

public class UF
{
  public int Id { get; set; }
  public virtual Pais Pais { get; set; }
  public int PaisId { get; set; }
}  
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...