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

Модели, ViewModels, DTO в приложении MVC 3

У меня есть веб-решение (в VS2010) с двумя подпроектами:

  • Domain, который содержит классы Model (сопоставленные с таблицами базы данных через Entity Framework) и Services, которые (помимо других вещей) несут ответственность за операции CRUD

  • WebUI, который ссылается на проект домена

Для первых страниц, которые я создал, я использовал классы Model из проекта Domain непосредственно как Model в своих сильно типизированных представлениях, потому что классы были небольшими, и я хотел отображать и изменять все свойства.

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

  • Я представляю ViewModels, которые живут в проекте WebUI и выставляют IQueryables и EF data context из службы в проект WebUI. Тогда я мог бы напрямую проецировать на эти ViewModels.

  • Если я не хочу раскрывать IQueryables и контекст данных EF, я помещаю классы ViewModel в проект Domain, тогда я могу вернуть ViewModels напрямую в результате запросов и прогнозов из Сервисные классы.

  • В дополнение к ViewModels в проекте WebUI я представляю Data transfer objects, который перемещает данные из запросов в классах службы в ViewModels.

Решение 1 и 2 похоже на то же количество работы, и я склонен предпочитать решение 2, чтобы сохранить все проблемы с базой данных в отдельном проекте. Но почему-то кажется неправильным, что в проекте Domain есть модели View-Models.

Решение 3 звучит намного больше, поскольку у меня есть больше классов для создания и ухода за отображением Model-DTO-ViewModel. Я также не понимаю, какая разница между DTO и ViewModels. Не являются ли ViewModels именно коллекцией выбранных свойств моего класса Model, которые я хочу отобразить? Разве они не будут содержать те же члены, что и DTO? Почему я хочу различать ViewModels и DTO?

Какое из этих трех вариантов предпочтительнее и каковы преимущества и недостатки? Существуют ли другие варианты?

Благодарим вас за отзыв!

Изменить (потому что у меня была, возможно, слишком длинная стена текста и была запрошена код)

Пример: у меня есть Customer Entity...

public class Customer
{
    public int ID { get; set; }
    public string Name { get; set; }
    public City { get; set; }
    // ... and many more properties
}

... и хотите создать представление, которое отображает (и, возможно, позволяет редактировать) Name клиентов в списке. В классе Service я извлекаю данные, которые мне нужны для представления через проекцию:

public class CustomerService
{
    public List<SomeClass1> GetCustomerNameList()
    {
        using (var dbContext = new MyDbContext())
        {
            return dbContext.Customers
                .Select(c => new SomeClass1
                             {
                                 ID = c.ID,
                                 Name = c.Name
                             })
                .ToList();
        }
    }
}

Тогда есть CustomerController с методом действия. Как это должно выглядеть?

Либо этот способ (а)...

public ActionResult Index()
{
    List<SomeClass1> list = _service.GetCustomerNameList();
    return View(list);
}

... или лучше (b):

public ActionResult Index()
{
    List<SomeClass1> list = _service.GetCustomerNameList();

    List<SomeClass2> newList = CreateNewList(list);

    return View(newList);
}

Что касается варианта 3 выше, я бы сказал: SomeClass1 (живет в проекте Domain) - это DTO и SomeClass2 (живет в проекте WebUI) - это ViewModel.

Мне интересно, имеет ли смысл различать два класса. Почему бы мне не выбрать вариант (а) для действия контроллера (потому что это проще)? Есть ли причины ввести ViewModel (SomeClass2) в дополнение к DTO (SomeClass1)?

4b9b3361

Ответ 1

ввести ViewModels, которые живут в Проект WebUI и выставить IQueryables и контекст данных EF из сервис для проекта WebUI. Затем я может напрямую проектировать в те ViewModels.

Проблема с этим заключается в том, что вы вскоре сталкиваетесь с проблемами, использующими EF, пытающиеся "сгладить" модели. Я столкнулся с чем-то подобным, когда у меня был класс CommentViewModel, который выглядел следующим образом:

public class CommentViewModel
{
    public string Content { get; set; }
    public string DateCreated { get; set; }
}

Следующая проекция запроса EF4 на CommentViewModel не будет работать, поскольку не может перевести метод ToString() в SQL:

var comments = from c in DbSet where c.PostId == postId 
               select new CommentViewModel() 
               { 
                   Content = c.Content,
                   DateCreated = c.DateCreated.ToShortTimeString() 
               };

Использование чего-то вроде Automapper - хороший выбор, особенно если у вас много конверсий. Тем не менее, вы также можете создавать свои собственные конвертеры, которые в основном конвертируют вашу модель домена в вашу модель просмотра. В моем случае я создал свои собственные методы расширения для преобразования моей модели домена Comment в my CommentViewModel следующим образом:

public static class ViewModelConverters
{
    public static CommentViewModel ToCommentViewModel(this Comment comment)
    {
        return new CommentViewModel() 
        { 
            Content = comment.Content,
            DateCreated = comment.DateCreated.ToShortDateString() 
        };
    }

    public static IEnumerable<CommentViewModel> ToCommentViewModelList(this IEnumerable<Comment> comments)
    {
        List<CommentViewModel> commentModels = new List<CommentViewModel>(comments.Count());

        foreach (var c in comments)
        {
            commentModels.Add(c.ToCommentViewModel());
        }

        return commentModels;
    }
}

В основном, я выполняю стандартный запрос EF, чтобы вернуть модель домена, а затем использовать методы расширения для преобразования результатов в модель представления. Например, следующие методы иллюстрируют использование:

public Comment GetComment(int commentId)
{
    return CommentRepository.GetById(commentId);
}

public CommentViewModel GetCommentViewModel(int commentId)
{
    return CommentRepository.GetById(commentId).ToCommentViewModel();
}

public IEnumerable<Comment> GetCommentsForPost(int postId)
{
    return CommentRepository.GetCommentsForPost(postId);
}

public IEnumerable<CommentViewModel> GetCommentViewModelsForPost(int postId)
{
    return CommentRepository.GetCommentsForPost(postId).ToCommentViewModelList();
}

Ответ 2

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

Таким образом, вы можете иметь свои сущности в Domain, а несколько классов модели представлений в вашем WebUI и где-нибудь (желательно в WebUI или подпространстве имен того же самого) определяют карты между ними. Ваши модели просмотра фактически будут DTO, но вам не придется беспокоиться о процессе преобразования между доменом и вашими классами DTO.

Примечание. Я бы настоятельно рекомендовал против предоставить сущности домена прямо в представлениях вашего веб-интерфейса MVC. Вы не хотите, чтобы EF "придерживался" до самого уровня интерфейса, если вы позже захотите использовать что-то другое, кроме EF.

Ответ 3

Говоря о моделях, ViewModels и DTO сбивает с толку, лично я не люблю использовать эти термины. Я предпочитаю говорить о Сущности домена, Доменные службы, Операция ввода/результат (также известный как DTO). Все эти типы находятся в доменном слое. Операции - это поведение объектов и служб. Если вы не создаете чисто CRUD-приложение, уровень представления относится только к типам ввода/результата, а не к объектам. Вам не нужны дополнительные типы ViewModel, это ViewModels (другими словами, модель представления). В представлении отображается перевод результатов операции в HTML, но тот же результат может быть сериализован как XML или JSON. То, что вы используете как ViewModel, является частью домена, а не уровня представления.