Запросы MVC ViewModels и Entity Framework - PullRequest
19 голосов
/ 22 февраля 2011

Я новичок как в MVC, так и в Entity Framework, и у меня есть вопрос о том, как это сделать.

Я следил за приложением Nerd Dinner MVC, как я пишу это приложение. У меня есть страница с данными из нескольких разных мест. Он показывает детали из нескольких разных таблиц, а также выпадающий список из справочной таблицы.

Я создал класс ViewModel, который содержит всю эту информацию:

class DetailsViewModel {
    public List<Foo> DropdownListData { get; set; }

    // comes from table 1
    public string Property1 { get; set; } 
    public string Property2 { get; set; }

    public Bar SomeBarObject { get; set; } // comes from table 2
}

В коде Nerd Dinner их примеры слишком упрощены. DinnerFormViewModel принимает одну сущность: Dinner. На основе Обеда создается список выбора для стран на основе местоположения обедов.

Из-за простоты их код доступа к данным также довольно прост. У него есть простой DinnerRepository с методом GetDinner (). В своих методах действия он может делать простые вещи, такие как:

Dinner dinner = new Dinner();

// return the view model
return View(new DinnerFormViewModel(dinner));

OR

Dinner dinner = repository.GetDinner(id);

return View(new DinnerFormViewModel(dinner));

Мой запрос намного сложнее, он состоит из нескольких таблиц и создает анонимный тип:

var query = from a in ctx.Table1
            where a.Id == id
            select new { a.Property1, a.Property2, a.Foo, a.Bar };

У меня такой вопрос:

Как должен выглядеть мой класс репозитория? Должен ли класс репозитория возвращать сам ViewModel? Это не похоже на правильный способ сделать что-то, так как вид ViewModel подразумевает, что он используется в представлении. Поскольку мой запрос возвращает анонимный объект, как мне вернуть его из своего хранилища, чтобы я мог создать ViewModel в действиях моего контроллера?

Ответы [ 5 ]

10 голосов
/ 22 февраля 2011

Хотя большинство ответов хороши, я думаю, что они пропускают промежуточную часть вашего вопроса.

Прежде всего, нет 100% правильного пути, и я бы не стал слишком зацикливаться на деталях точного шаблона, который нужно использовать.По мере того, как ваше приложение будет развиваться, вы начнете видеть, что работает, а что нет, и выяснить, как лучше всего изменить его, чтобы оно работало для вас и вашего приложения.Я только что закончил полностью менять структуру моего бэкэнда Asp.Net MVC, в основном потому, что многие советы, которые я нашел, не сработали для того, что я пытался сделать.

Как говорится, посмотрите на ваши слоитем, что они должны делать.Слой репозитория предназначен исключительно для добавления / удаления / и редактирования данных из вашего источника данных.Он не знает, как эти данные будут использоваться, и, честно говоря, ему все равно.Поэтому репозитории должны просто возвращать ваши сущности EF.

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

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

Сервисный уровень должен быть единственным уровнем, который также вызывает классы хранилища.

8 голосов
/ 22 февраля 2011

Вы правы, репозиторий не должен возвращать модель представления. Изменения в вашем представлении приведут к изменению слоя данных.

Ваш репозиторий должен быть совокупным корнем . Если ваши property1, property2, Foo, Bar связаны каким-либо образом, я бы извлек новый класс для обработки этого.

public class FooBarDetails
{
   public string Property1 {get;set;}
   public string Property2 {get;set;}
   public Foo Foo {get;set;}
   public Bar Bar {get;set;}
}

var details = _repo.GetDetails(detailId);

Если Foo и Bar вообще не связаны, возможно, будет предложено ввести службу для создания ваших FooBarDetails.

FooBarDetails details = _service.GetFooBar(id);

где GetFooBar(int) будет выглядеть примерно так:

_fooRepo.Get(id);
_barRepo.Get(id);

return new FooBarDetails{Foo = foo, Bar = bar, Property1 = "something", Property2 = "something else"};

Это все предположение, поскольку дизайн репозитория действительно зависит от вашего домена. Использование общих терминов затрудняет развитие потенциальных отношений между вашими объектами.

Обновлено Из комментария, если мы имеем дело с совокупным корнем ордера. Заказ будет иметь OrderItem, а также клиента, который разместил заказ.

public class Order
{
    public List<OrderItem> Items{get; private set;}
    public Customer OrderedBy {get; private set;}
    //Other stuff
}

public class Customer
{
  public List<Orders> Orders{get;set;}
}

Ваш репо должен вернуть полностью гидратированный объект заказа.

var order = _rep.Get(orderId);

Поскольку в вашем заказе есть вся необходимая информация, я бы передал заказ непосредственно модели представления.

public class OrderDetailsViewModel
{
  public Order Order {get;set;}
  public OrderDetailsViewModel(Order order)
  {
    Order = order;
  }
}

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

public class OrderDetailsViewModel
{
  public Order Order {get;set;}
  public List<Order> SimilarOrders {get;set;}
  public OrderDetailsViewModel(Order order, List<Order> similarOrders)
  {
    Order = order;
    SimilarOrders = similarOrders;
  }
}
2 голосов
/ 22 февраля 2011

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

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

var dinner = dinnerRepository.Get(dinnerId);
var bar = barRepository.Get(barId);

var viewModel = new DinnerAndBarFormViewModel(dinner, bar);
return View(viewModel);
2 голосов
/ 22 февраля 2011

Репозиторий должен работать только с моделями, а не с анонимными типами, и должен реализовывать только операции CRUD.Если вам нужна некоторая фильтрация, вы можете добавить для этого служебный слой.

Для сопоставления между моделями представления и моделями вы можете использовать любую из библиотек сопоставления, например Automapper .

0 голосов
/ 07 июня 2013

У меня такое же сомнение относительно плаката, и я все еще не убежден. Мне лично не очень нравится данный совет ограничивать репозиторий простым выполнением основных операций CRUD. ИМХО, производительность всегда должна учитываться при разработке реального приложения, и замена внешнего соединения SQL двумя различными запросами для отношений мастер-детализация не кажется мне слишком хорошей. Кроме того, таким образом принцип, согласно которому должны запрашиваться только необходимые поля, полностью теряется: используя этот подход, мы вынуждены всегда извлекать все поля всех задействованных таблиц, что просто безумно для не игрушечных приложений!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...