Подтвердить что ты не робот

Запросы MVC ViewModels и Entity Framework

Я новичок в 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 принимает единый объект: Ужин. На основе ужина он создает SelectList для стран, основанных на месте ужинов.

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

Dinner dinner = new Dinner();

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

ИЛИ

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 в своих действиях контроллера?

4b9b3361

Ответ 1

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

Ваш репозиторий должен быть агрегированным корнем. Если ваши свойства1, 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;
  }
}

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

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

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

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

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

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

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

Уровень сервиса должен быть единственным слоем, который также вызывает классы репозитория.

Ответ 3

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

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

Ответ 4

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

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

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

var viewModel = new DinnerAndBarFormViewModel(dinner, bar);
return View(viewModel);

Ответ 5

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