Доступ к объектам базы данных из контроллера - программирование
Подтвердить что ты не робот

Доступ к объектам базы данных из контроллера

TL;DR

В хорошем дизайне. Должен ли доступ к базе данных обрабатываться на отдельном уровне бизнес-логики (в модели MVC asp.net) или нормально ли передавать объекты IQueryable или DbContext на контроллер?

Почему? Каковы плюсы и минусы каждого?


Я создаю приложение ASP.NET MVC на С#. Он использует EntityFramework как ORM.

Немного упростите этот сценарий.

У меня есть таблица базы данных с симпатичными пушистыми котятами. У каждого котенка есть ссылка на изображение котенка, индекс пушистого котенка, имя котенка и котенок id. Они сопоставляются с EF, созданным POCO, называемым Kitten. Я мог бы использовать этот класс в других проектах, а не только в проекте asp.net MVC.

У меня есть KittenController, который должен забрать последних пушистых котят в /Kittens. Он может содержать некоторую логику выбора котенка, но не слишком много логики. Я спорил с другом о том, как реализовать это, я не буду раскрывать стороны:)

Вариант 1: db в контроллере:

public ActionResult Kittens() // some parameters might be here
{
   using(var db = new KittenEntities()){ // db can also be injected,
       var result = db.Kittens // this explicit query is here
                      .Where(kitten=>kitten.fluffiness > 10) 
                      .Select(kitten=>new {
                            Name=kitten.name,
                            Url=kitten.imageUrl
                      }).Take(10); 
       return Json(result,JsonRequestBehavior.AllowGet);
   }
}

Вариант 2: отдельная модель

public class Kitten{
   public string Name {get; set; }
   public string Url {get; set; }
   private Kitten(){
        _fluffiness = fluffinessIndex;
   }

   public static IEnumerable<Kitten> GetLatestKittens(int fluffinessIndex=10){ 
        using(var db = new KittenEntities()){ //connection can also be injected
            return db.Kittens.Where(kitten=>kitten.fluffiness > 10)
                     .Select(entity=>new Kitten(entity.name,entity.imageUrl))
                     .Take(10).ToList();
        }
    } // it static for simplicity here, in fact it probably also an object method
      // Also, in practice it might be a service in a services directory creating the
      // Objects and fetching them from the DB, and just the kitten MVC _type_ here

}

//----Then the controller:
public ActionResult Kittens() // some parameters might be here
{
    return Json(Kittens.GetLatestKittens(10),JsonRequestBehavior.AllowGet);
}

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

  • Каковы некоторые плюсы и минусы для каждого дизайна?
  • Есть ли явный победитель? Почему?

Примечание. Конечно, альтернативные подходы очень ценятся как ответы.

Разъяснение 1: На практике это не тривиальное применение. Это приложение с десятками контроллеров и тысячами строк кода, и сущности не только используются здесь, но и десятки других проектов С#. Примером здесь является уменьшенный тестовый пример.

4b9b3361

Ответ 1

Варианты 1 и 2 немного экстремальны и как выбор между дьяволом и синим морем, но если бы мне пришлось выбирать между ними, я бы предпочел вариант 1.

Прежде всего, вариант 2 будет генерировать исключение во время выполнения, потому что Entity Framework не поддерживает проект в объект (Select(e => new Kitten(...)) и не позволяет использовать конструктор с параметрами в проекции. Теперь эта заметка кажется бит в этом контексте, но, проецируя в объект и возвращая Kitten (или перечисление Kitten s), вы скрываете реальную проблему с этим подходом.

Очевидно, ваш метод возвращает два свойства объекта, которые вы хотите использовать в своем представлении - котенок name и imageUrl. Поскольку это всего лишь выбор всех свойств Kitten, возвращающих объект (полузаполненный) Kitten, не подходит. Итак, какой тип для фактического возврата из этого метода?

  • Вы можете вернуть object (или IEnumerable<object>) (как я понимаю ваш комментарий об "методе объекта" ), который является прекрасным, если вы передадите результат в Json(...), который будет обработан в Javascript позже. Но вы потеряете всю информацию типа времени компиляции, и я сомневаюсь, что тип результата object полезен для чего-либо еще.
  • Вы можете вернуть некоторый именованный тип, который просто содержит два свойства - возможно, называется "KittensListDto".

Теперь это только один метод для одного вида - просмотр списка котят. Затем у вас есть подробный вид, чтобы отобразить одного котенка, затем вид редактирования, а затем, возможно, подтверждение подтверждения. Четыре представления для существующего объекта Kitten, каждый из которых требует, возможно, разных свойств, и каждому из них потребуется отдельный метод и проекция, а также другой тип DTO. То же самое для объекта Dog и для 100 объектов больше в проекте, и вы получаете 400 методов и 400 возвращаемых типов.

И, скорее всего, ни один из них никогда не будет использоваться повторно в любом другом месте, кроме этого конкретного вида. Почему вы хотите Take 10 котят с помощью всего лишь name и imageUrl где-нибудь во второй раз? У вас есть второй список котят? Если это так, у него будет причина, и запросы будут идентичны только случайно, и теперь, и если один из них изменит другой, это не обязательно, иначе просмотр списка не будет должным образом "повторно использован" и не должен существовать дважды. Или может быть тот же список, используемый экспортом Excel? Но, возможно, пользователи Excel хотят завтра иметь 1000 котят, в то время как представление должно показываться только 10. Или представление должно отображать котенка Age завтра, но пользователи Excel не хотят этого, потому что их макросы Excel не будут правильнее работать с этим изменением. Просто потому, что два фрагмента кода идентичны, их не нужно учитывать в общем многократно используемом компоненте, если они находятся в другом контексте или имеют разную семантику. Вам лучше оставить его GetLatestKittensForListView и GetLatestKittensForExcelExport. Или у вас лучше нет таких методов на вашем уровне обслуживания вообще.


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

"Добро пожаловать в BigPizza, заказный магазин Pizza, могу ли я взять ваш заказ?" "Хорошо, я хотел бы иметь пиццу с маслинами, а томатный соус сверху и сыр внизу и выпекать его в духовке в течение 90 минут, пока он не станет черным и твердым, как плоская скала из гранита". "Хорошо, сэр, пользовательские Пиццы - наша профессия, мы это сделаем".

Кассир идет на кухню. "На прилавке есть пси, он хочет иметь пиццу с... это камень гранита с... подождите... нам нужно сначала иметь имя", - рассказывает он повару.

"Нет!", - кричит повар, - "опять!" Вы знаете, мы уже это пробовали ". Он берет стопку бумаги с 400 страницами," здесь у нас есть камень из гранита с 2005 года, но... у него не было маслин, а вместо паприки... или вот верхний помидор... но клиент этого захотел испекли всего полминуты "." Может быть, мы должны назвать это TopTomatoGraniteRockSpecial? "" Но это не учитывает сыр внизу ". Кассир:" Это то, что Специальный представитель должен выразить "." Но наличие пиццы, сформированной как пирамида, было бы особенным ", - отвечает повар." Хммм... это сложно... ", - говорит отчаянный кассир.

" МОЯ ПИЦЦА УЖЕ В ПЕЧЬ? ", вдруг она кричит через кухонную дверь." Позвольте прекратить эту дискуссию, просто скажите мне, как сделать эту пиццу, мы не будем иметь такую ​​пиццу во второй раз ", - решает повар." Хорошо, это пицца с маслинами, а томатный соус сверху и сыр внизу и выпекайте его в духовке в течение 90 минут, пока он не станет черным и твердым, как плоская скала из гранита".


Если параметр 1 нарушает принцип разделения проблем, используя контекст базы данных в слое представления, опция 2 нарушает тот же принцип, имея ориентированную на презентацию логику запросов на уровне обслуживания или бизнеса. С технической точки зрения это не так, но в конечном итоге это будет сервисный уровень, который является чем-то другим, кроме "повторно используемым" вне уровня представления. И он имеет гораздо более высокие затраты на разработку и обслуживание, потому что для каждой требуемой части данных в действии контроллера вы должны создавать сервисы, методы и типы возврата.

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

public IQueryable<Kitten> GetKittens()
{
    return context.Kittens.AsNoTracking().Where(k => !k.IsDeleted);
}

Что-нибудь еще, что следует за свойствами, подобными проецированию, - это конкретный вид, и я не хотел бы иметь его в этом слое. Чтобы сделать этот подход возможным, IQueryable<T> должен быть открыт из службы/репозитория. Это не означает, что select должен быть непосредственно в действии контроллера. Особенно жирные и сложные проекции (которые, возможно, соединяются с другими объектами свойствами навигации, выполняют группировки и т.д.), Могут быть перенесены в методы расширения IQueryable<T>, которые собираются в других файлах, каталогах или даже в другом проекте, но все же проект, который приложение к уровню представления и намного ближе к нему, чем к уровню обслуживания. Действие может выглядеть следующим образом:

public ActionResult Kittens()
{
    var result = kittenService.GetKittens()
        .Where(kitten => kitten.fluffiness > 10) 
        .OrderBy(kitten => kitten.name)
        .Select(kitten => new {
            Name=kitten.name,
            Url=kitten.imageUrl
        })
        .Take(10);
    return Json(result,JsonRequestBehavior.AllowGet);
}

Или вот так:

public ActionResult Kittens()
{
    var result = kittenService.GetKittens()
        .ToKittenListViewModel(10, 10);
    return Json(result,JsonRequestBehavior.AllowGet);
}

С ToKittenListViewModel() находится:

public static IEnumerable<object> ToKittenListViewModel(
    this IQueryable<Kitten> kittens, int minFluffiness, int pageItems)
{
    return kittens
        .Where(kitten => kitten.fluffiness > minFluffiness)
        .OrderBy(kitten => kitten.name)
        .Select(kitten => new {
            Name = kitten.name,
            Url = kitten.imageUrl
        })
        .Take(pageItems)
        .AsEnumerable()
        .Cast<object>();
}

Это просто базовая идея и эскиз, что другое решение может находиться в середине между вариантами 1 и 2.

Ну, все зависит от общей архитектуры и требований, и все, что я написал выше, может быть бесполезным и неправильным. Вы должны учитывать, что технология ORM или доступа к данным может быть изменена в будущем? Может ли быть физическая граница между контроллером и базой данных, является ли контроллер отключен от контекста и нужны ли данные для веб-службы, например, в будущем? Для этого потребуется совсем другой подход, который будет более склоняться к варианту 2.

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

Я говорил только о запросах или запросах GET в веб-приложении, которое редко бывало, что я бы назвал "бизнес-логикой" вообще. Запросы POST и изменение данных - это совершенно другая история. Если запрещено, что заказ может быть изменен после выставления счета, например, это общее "бизнес-правило", которое обычно применяется независимо от того, какой вид или веб-сервис или фоновый процесс или что-то еще пытается изменить заказ. Я бы определенно поставил такую ​​проверку статуса заказа в бизнес-службу или какой-либо общий компонент и никогда не был в контроллере.

Может существовать аргумент против использования IQueryable<T> в действии контроллера, потому что он связан с LINQ-to-Entities, и это затруднит тесты модулей. Но что будет тестировать unit test в действии контроллера, который не содержит какой-либо бизнес-логики, который получает переданные параметры, которые обычно поступают из представления через привязку или маршрутизацию модели - не охватываются unit test -, который использует mockinged repository/service return IEnumerable<T> - запрос базы данных и доступ не проверены - и что возвращает View - правильная визуализация представления не проверена?

Ответ 2

Второй подход превосходит. Попробуем хромую аналогию:

Вы входите в магазин пиццы и идите к стойке. "Добро пожаловать в McPizza Maestro Double Deluxe, могу ли я принять ваш заказ?" - спрашивает вас пузатый кассир, пустота в глазах, угрожающая заманить вас. "Да, у меня будет одна большая пицца с маслинами". "Хорошо", - отвечает кассир, и его голос кричит в середине звука "o". Он кричит на кухню "Один Джимми Картер!"

И затем, немного дождавшись, вы получите большую пиццу с маслинами. Вы заметили что-то особенное? Кассир не сказал: "Принеси немного теста, покрути его, как будто это рождественское время, налейте сыр и томатный соус, посыпьте оливки и поставьте в духовку около 8 минут!" Подумай об этом, это совсем не странно. Кассир - это просто шлюз между двумя мирами: клиент, который хочет пиццу, и повар, который делает пиццу. Для всех, кто знает кассир, повар получает свою пиццу у инопланетян или разрезает их у Джимми Картера (он сокращает ресурс, люди).

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

Теперь позвольте сказать, что владельцы решают, что пиццы - просто причуда (богохульство!), и вы переключаетесь на нечто более современное, причудливое соединение гамбургера. Давайте посмотрим, что получится:

Вы входите в причудливое соединение гамбургера и идите к стойке. "Добро пожаловать в Le Burger Maestro Double Deluxe, могу ли я принять ваш заказ?" "Да, у меня будет один большой гамбургер с маслинами". "Хорошо", и он поворачивается к кухне: "Один Джимми Картер!"

И тогда вы получите большой гамбургер с маслинами (ew).

Ответ 3

Это ключевая фраза:

Я мог бы использовать этот класс в других проектах, а не только в проекте asp.net MVC.

Контроллер является HTTP-ориентированным. Он обрабатывает только HTTP-запросы. Если вы хотите использовать свою модель в любом другом проекте, то есть в вашей бизнес-логике, не может иметь какую-либо логику в контроллерах. Вы должны быть в состоянии снять свою модель, поместить ее в другое место, и вся ваша бизнес-логика все еще работает.

Итак, нет, не обращайтесь к своей базе данных с вашего контроллера. Он убивает любое возможное повторное использование, которое вы когда-либо могли получить.

Вы действительно хотите переписать все свои запросы db/linq во всех своих проектах, когда у вас могут быть простые методы, которые вы повторно используете?

Другое дело: ваша функция в опции 1 имеет две обязанности: она извлекает результат из объекта mapper и отображает его. Это слишком много обязанностей. В списке обязанностей есть "и". У вашей опции 2 есть только одна ответственность: быть ссылкой между моделью и представлением.

Ответ 4

Я не уверен, как это делают ASP.NET или С#. Но я знаю MVC.

В MVC вы разделяете свое приложение на два основных слоя: презентационный слой (который содержит контроллер и представление) и слой модели (который содержит... модель).

Цель состоит в том, чтобы отделить 3 основные обязанности в приложении:

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

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

  • Контроллер меньше привязан к модели. Поскольку "работа" выполняется в модели, если вы хотите изменить свой контроллер, вы сможете сделать это более легко, если ваша обработка базы данных находится в модели.
  • Вы получаете большую гибкость. В случае, если вы хотите изменить схему сопоставления (я хочу переключиться на Postgres из MySQL), мне нужно изменить ее только один раз (в базовом определении Mapper).

Для получения дополнительной информации см. отличный ответ здесь: Как структурировать модель в MVC?

Ответ 5

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

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

namespace MyProject.Web.Controllers
{
   public class MyController : Controller
   {
      private readonly IKittenService _kittenService ;

      public MyController(IKittenService kittenService)
      {
         _kittenService = kittenService;
      }

      public ActionResult Kittens()
      {
          // var result = _kittenService.GetLatestKittens(10);
          // Return something.
      }
   }  
}

namespace MyProject.Domain.Kittens
{
   public class Kitten
   {
      public string Name {get; set; }
      public string Url {get; set; }
   }
}

namespace MyProject.Services.KittenService
{
   public interface IKittenService
   {
       IEnumerable<Kitten> GetLatestKittens(int fluffinessIndex=10);
   }
}

namespace MyProject.Services.KittenService
{
   public class KittenService : IKittenService
   {
      public IEnumerable<Kitten> GetLatestKittens(int fluffinessIndex=10)
      {
         using(var db = new KittenEntities())
         {
            return db.Kittens // this explicit query is here
                      .Where(kitten=>kitten.fluffiness > 10) 
                      .Select(kitten=>new {
                            Name=kitten.name,
                            Url=kitten.imageUrl
                      }).Take(10); 
         }
      }
   }
}

Ответ 6

@Win имеет идею, которую я бы более или менее последовал.

Представьте только Presentation.

Контроллер просто действует как мост, он ничего не делает, это средний человек. Должно быть легко протестировано.

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

Таким образом, ваш DAL полностью не зависит от вашего веб-сервера. Если кто-то взломает ваш сервер, DAL, вероятно, еще безопасен.

Это до вас, я думаю.

Ответ 7

Принцип единой ответственности. У каждого из ваших классов должна быть одна и только одна причина для изменения. @Zirak дает хороший пример того, как каждый человек имеет единую ответственность в цепочке событий.

Посмотрите на гипотетический тестовый пример, который вы предоставили.

public ActionResult Kittens() // some parameters might be here
{
   using(var db = new KittenEntities()){ // db can also be injected,
       var result = db.Kittens // this explicit query is here
                      .Where(kitten=>kitten.fluffiness > 10) 
                      .Select(kitten=>new {
                            Name=kitten.name,
                            Url=kitten.imageUrl
                      }).Take(10); 
       return Json(result,JsonRequestBehavior.AllowGet);
   }
}

С уровнем обслуживания между ними может выглядеть примерно так.

public ActionResult Kittens() // some parameters might be here
{
    using(var service = new KittenService())
    {
        var result =  service.GetFluffyKittens();  
        return Json(result,JsonRequestBehavior.AllowGet);
    }
}

public class KittenService : IDisposable
{
    public IEnumerable<Kitten> GetFluffyKittens()
    {
        using(var db = new KittenEntities()){ // db can also be injected,
            return db.Kittens // this explicit query is here
                      .Where(kitten=>kitten.fluffiness > 10) 
                      .Select(kitten=>new {
                            Name=kitten.name,
                            Url=kitten.imageUrl
                      }).Take(10); 
        }
    }
}

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

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

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

public class KittenService : IDisposable
{
    public IEnumerable<Kitten> GetFluffyKittens()
    {
        using(var db = GetDbContextForFuffyKittens()){ // db can also be injected,
            return db.Kittens // this explicit query is here
                      .Where(kitten=>kitten.fluffiness > 10) 
                      .Select(kitten=>new {
                            Name=kitten.name,
                            Url=kitten.imageUrl
                      }).Take(10); 
        }
    }

    protected KittenEntities GetDbContextForFuffyKittens(){
        // ... code to determine the least used shard and get connection string ...
        var connectionString = GetShardThatIsntBusy();
        return new KittensEntities(connectionString);
    }
}

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

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

public class KittenService : IDisposable
{
    public IEnumerable<Kitten> GetFluffyKittens()
    {
        Func<IEnumerable<Kitten>> func = () => {
            using(var db = GetDbContextForFuffyKittens()){ // db can also be injected,
                return db.Kittens // this explicit query is here
                        .Where(kitten=>kitten.fluffiness > 10) 
                        .Select(kitten=>new {
                                Name=kitten.name,
                                Url=kitten.imageUrl
                        }).Take(10); 
            }
        };
        return this.Execute(func);
    }

    protected KittenEntities GetDbContextForFuffyKittens(){
        // ... code to determine the least used shard and get connection string ...
        var connectionString = GetShardThatIsntBusy();
        return new KittensEntities(connectionString);
    }

    protected T Execute(Func<T> func){
        try
        {
            return func();
        }
        catch(Exception ex){
            Logging.Log(ex);
            throw ex;
        }
    }
}

Ответ 8

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

EDIT: немного больше на тестировании

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

из этой комнаты чата:

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

Ответ 9

Если бы у меня было (примечание: действительно было), чтобы выбрать между двумя заданными параметрами, я бы сказал 1 для простоты, но я не рекомендую использовать его, поскольку он трудно поддерживать и вызывает много дублирующегося кода. Контроллер должен содержать как можно меньше бизнес-логики. Он должен только делегировать доступ к данным, сопоставлять его с ViewModel и передавать его в представление.

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

Я не рекомендую размещать логику доступа к данным в вашем POCO, это не позволяет вам переключиться на другой ORM (например, NHibernate) и повторно использовать те же POCO.