Привязка модели в контроллере при публикации формы - свойства навигации не загружаются автоматически - PullRequest
2 голосов
/ 16 января 2012

Я использую 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 и т. Д. В моем коде.

Затем я построил стандартную форму редактирования для моих testParents. Контроллер выглядит так:

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 выглядит нормально (идентификатор, имя и комментарий установлены, как и ожидалось). Однако свойство навигации TestChildren остается равным NULL.

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

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

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

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

Ответы [ 2 ]

3 голосов
/ 16 января 2012

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

  • Родительский объект должен быть присоединен к контексту 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

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

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

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

// 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().

1 голос
/ 16 января 2012

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

@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 и выборка дочерних элементов, связанных с данным родительским элементом, поскольку пользователь не может редактировать дочерние элементы.в любом случае внутри вида.

...