2012-01-16 1 views
2

Я использую версию Entity Framework версии 4.2. В моем маленьком тестовом приложении есть два класса:Привязка модели к контроллеру при отправке формы - свойства навигации не загружаются автоматически

public class TestParent 
{ 
    public int TestParentID { get; set; } 
    public string Name { get; set; } 
    public string Comment { get; set; } 

    public virtual ICollection<TestChild> TestChildren { get; set; } 
} 

public class TestChild 
{ 
    public int TestChildID { get; set; } 
    public int TestParentID { get; set; } 
    public string Name { get; set; } 
    public string Comment { get; set; } 

    public virtual TestParent TestParent { get; set; } 
} 

Заполнение объектов данными из базы данных хорошо работает. Поэтому я могу использовать testParent.TestChildren.OrderBy(tc => tc.Name).First().Name и т. Д. В моем коде.

Затем я построил стандартную форму редактирования для своих тестовых партов. Внешний вид контроллера, как это:

public class TestController : Controller 
{ 
    private EFDbTestParentRepository testParentRepository = new EFDbTestParentRepository(); 
    private EFDbTestChildRepository testChildRepository = new EFDbTestChildRepository(); 

    public ActionResult ListParents() 
    { 
     return View(testParentRepository.TestParents); 
    } 

    public ViewResult EditParent(int testParentID) 
    { 
     return View(testParentRepository.TestParents.First(tp => tp.TestParentID == testParentID)); 
    } 

    [HttpPost] 
    public ActionResult EditParent(TestParent testParent) 
    { 
     if (ModelState.IsValid) 
     { 
      testParentRepository.SaveTestParent(testParent); 
      TempData["message"] = string.Format("Changes to test parents have been saved: {0} (ID = {1})", 
                 testParent.Name, 
                 testParent.TestParentID); 
      return RedirectToAction("ListParents"); 
     } 
     // something wrong with the data values 
     return View(testParent); 
    } 
} 

Когда форма размещена на сервер модель появится связывание работает хорошо - то есть testParent выглядит хорошо (ID, имя и комментарий установить, как и ожидалось). Однако свойство навигации TestChildren остается в NULL.

Это, я думаю, не удивительно, так как привязка модели просто извлекает значения формы, поскольку они были отправлены из браузера и вталкивает их в объект класса TestParent. Однако для заполнения TestParent.TestChildren требуется немедленная обратная связь с базой данных, которая несет ответственность за структуру Entity Framework. И EF, вероятно, не участвует в процессе привязки.

Я ожидал, что ленивая загрузка начнется, когда я позвоню testParent.TestChildren.First(). Вместо этого это приводит к ArgumentNullException.

Нужно ли пометить объект особым образом после привязки к модели, чтобы Entity Framework выполнила ленивую загрузку? Как я могу это достичь?

Очевидно, что я мог бы вручную восстановить детей со вторым репозиторием testChildRepository. Но (a) не чувствует себя хорошо и (b) приводит к проблемам с тем, как настроены мои хранилища (каждый из которых использует свой собственный DBC-текст), что является проблемой, с которой мне еще не удалось договориться.

ответ

3

Для того, чтобы получить отложенной загрузки для вашей коллекции ребенка два требования должны быть выполнены:

  • Родитель объект должен быть присоединен к контексте EF
  • Ваш родительский объект должен быть отложенной загрузки прокси

Оба требования выполняются, если вы загружаете родительский объект из базы данных через контекст (а ваши свойства навигации - virtual, чтобы разрешить создание прокси).

Если вы не загрузите объект из базы данных, но создать его вручную, вы можете достичь того же, используя соответствующие EF функции:

var parent = context.TestParents.Create(); 
parent.TestParentID = 1; 
context.TestParents.Attach(parent); 

Использование Create и не new важно здесь, потому что это создает требуется ленивый загрузочный прокси. Вы можете получить доступ к коллекции детской и дети родителей с ID = 1 будут загружаться лениво:

var children = parent.TestChildren; // no NullReferenceException 

Теперь ModelBinder по умолчанию не имеет ни малейшего представления об этих конкретных функций EF и будет просто создать экземпляр родителя с new и также не привязывает его к какому-либо контексту. Оба требования не выполняются, и ленивая загрузка не может работать.

Вы можете написать собственное связующее устройство для создания экземпляра с Create(), но это, вероятно, самое худшее решение, так как это будет очень сильно зависеть от вашего уровня просмотра.

Если вам нужна коллекция ребенка после модели связывания я бы в этом случае загрузите его с помощью явной загрузки:

// parent comes as parameter from POST action method 
context.TestParents.Attach(parent); 
context.Entry(parent).Collection(p => p.TestChildren).Load(); 

Если контекст и EF скрывается за хранилище, вам будет нужен новый метод хранилища для этого , как:

void LoadNavigationCollection<TElement>(T entity, 
    Expression<Func<T, ICollection<TElement>>> navigationProperty) 
    where TElement : class 
{ 
    _context.Set<T>().Attach(entity); 
    _context.Entry(entity).Collection(navigationProperty).Load(); 
} 

... где _context является членом класса репозитория.

Но лучший способ, как сказал Дарин, - связать ViewModels, а затем сопоставить их с вашими объектами по мере необходимости. Тогда у вас будет возможность создать экземпляр объектов с помощью Create().

+0

Большое спасибо за ваш ответ и извините за мой поздний ответ. Я был в сети последние три дня и довольно болотистый. Я думаю, ваше объяснение показало мне, что есть некоторые фундаментальные ямы в моем знании EF. Я работал над примером Университета Contoso на веб-сайте Microsoft и в некоторых вводных статьях. Не могли бы вы порекомендовать ресурс, который я могу использовать, чтобы двигаться немного дальше? – ralfonso

+0

Что касается вашего последнего замечания о привязке к ViewModels: это техника, о которой я не знал. Я использую ViewModels, но только для передачи их в представления. Не могли бы вы объяснить, почему выгодно использовать модель представления вместо класса из моей модели домена? Так как я в значительной степени отображаю все свойства моего класса, поскольку поля ввода в исходном представлении не будут выглядеть так же, как мой класс? Это означает, что мне нужно, чтобы они синхронизировались, когда я добавляю больше свойств.Если я использую Html.EditorForModel() в представлении запроса GET, тогда не нужно будет настраивать добавленные свойства. – ralfonso

+0

@ralfonso: Джулия Лерман написала книгу о Code-First: http://www.amazon.com/Programming-Entity-Framework-Code-First/dp/1449312942 и написала статьи и создала видео: http: // thedatafarm .com/blog/data-access/code-first-entity-framework-4-1-videos-and-articles-on-msdn/I вам нравится учиться, просматривая видео, которые я бы рекомендовал Pluralsight: http: // www. multipalsight-training.net (у них есть материал о MVC и EF и многие другие темы). Некоторые из видеороликов бесплатны, но коммерческие подписки вполне доступны. – Slauma

1

Одна возможность состоит в том, чтобы использовать скрытые поля внутри формы, которая будет хранить ценности коллекции ребенка:

@model TestParent 
@using (Html.BegniForm()) 
{ 
    ... some input fields of the parent 

    @Html.EditorFor(x => x.TestChildren) 
    <button type="submit">OK</button> 
} 

и затем шаблон редактора для детей, содержащих скрытые поля (~/Views/Shared/EditorTemplates/TestChild.cshtml):

@model TestChild 
@Html.HiddenFor(x => x.TestChildID) 
@Html.HiddenFor(x => x.Name) 
... 

Но так как вы не придерживаетесь хороших практик здесь и прямо передаете свои модели домена в представление вместо использования моделей просмотра, у вас возникнет проблема с рекурсивными отношениями между детьми и родителями , Возможно, вам потребуется вручную заполнить родительский элемент для каждого ребенка.

Но лучшим способом было бы запросить вашу базу данных в действии POST и получить дочерние элементы, связанные с данным родителем, так как пользователь не может редактировать детей внутри представления в любом случае.

+0

Согласен, что я могу вручную запросить базу данных для дочерних записей. Но разве это не одна из особенностей Entity Framework, о которой мне больше не нужно беспокоиться? Всякий раз, когда у меня есть объект, я могу переселиться в него, не беспокоясь о том, как (и когда) объект заполняется из базы данных - по крайней мере, это было мое (по общему признанию, надежное) понимание. – ralfonso