Реализация шаблона репозитория в ASP.NET MVC

Мне все еще трудно осмыслить это. Я хочу разделить свои слои (dll) вот так:

1) MyProject.Web.dll - веб-приложение MVC (контроллеры, модели (редактирование / просмотр), представления)
2) MyProject.Services.dll - уровень обслуживания (бизнес-логика)
3) MyProject.Repositories.dll - Репозитории
4) MyProject.Domain.dll - Классы POCO
5) MyProject.Data.dll - EF4

Рабочий процесс:

1) Контроллеры вызывают службы для получения объектов для заполнения моделей просмотра / редактирования.
2) Службы вызывают репозитории для получения / сохранения объектов.
3) Репозитории вызывают EF для получения / сохранения объектов на SQL Server и из него.

Мои репозитории возвращают IQueryable (Of T), и внутри них они используют ObjectSet (Of T).

Итак, как я вижу, слои зависят именно от следующего уровня и библиотеки, содержащей классы POCO?

Некоторые проблемы:

1) Теперь, чтобы мои репозитории правильно работали с EF, они будут зависеть от System.Data.Objects, теперь у меня есть тесная связь с EF на уровне моего репозитория, это плохо?

2) Я использую шаблон UnitOfWork. Где это должно жить? Он имеет контекст свойства как ObjectContext, так что он также тесно связан с EF. Плохой?

3) Как я могу использовать DI, чтобы упростить эту задачу?

Я хочу, чтобы это было как можно слабее для тестирования. Какие-либо предложения?

---------- Изменить ----------

Пожалуйста, дайте мне знать, если я на правильном пути. Кроме того, так как Служба правильно вводит IRepository (Of Category), как она узнает разницу между этим и конкретным классом EFRepository (Of T)? То же самое с UnitOfWork и Сервисом?

Как только кто-то поможет мне разобраться в этом до того, как я это понимаю, я знаю, что это будет казаться тривиальным, но, черт возьми, я чертовски долго обдумываю это !!

Контроллер

Public Class CategoryController
    Private _Service As Domain.Interfaces.IService

    Public Sub New(ByVal Service As Domain.Interfaces.IService)
        _Service = Service

    End Sub

    Function ListCategories() As ActionResult
        Dim Model As New CategoryViewModel

        Using UOW As New Repositories.EFUnitOfWork
            Mapper.Map(Of Category, CategoryViewModel)(_Service.GetCategories)
        End Using

        Return View(Model)
    End Function

End Class

Сервис

Public Class CategoryService

    Private Repository As Domain.Interfaces.IRepository(Of Domain.Category)
    Private UnitOfWork As Domain.Interfaces.IUnitOfWork

    Public Sub New(ByVal UnitOfWork As Domain.Interfaces.IUnitOfWork, ByVal Repository As Domain.Interfaces.IRepository(Of Domain.Category))
        UnitOfWork = UnitOfWork
        Repository = Repository

    End Sub

    Public Function GetCategories() As IEnumerable(Of Domain.Category)
        Return Repository.GetAll()
    End Function

End Class

Репозиторий и UnitOfWork

Public MustInherit Class RepositoryBase(Of T As Class)
    Implements Domain.Interfaces.IRepository(Of T)

End Class

Public Class EFRepository(Of T As Class)
    Inherits RepositoryBase(Of T)

End Class

Public Class EFUnitOfWork
    Implements Domain.Interfaces.IUnitOfWork

    Public Property Context As ObjectContext

    Public Sub Commit() Implements Domain.Interfaces.IUnitOfWork.Commit

    End Sub

End Class



Ответы (4)


Оригинальный ответ

  1. Нет. Однако, чтобы избежать привязки к этому Сервисов, создайте ISomethingRepository интерфейс на уровне домена. Это будет разрешено вашим контейнером IoC.

  2. Шаблоны «Единица работы» должны быть реализованы в ваших репозиториях. Используйте то же решение, что и я, для отделения ваших репозиториев от ваших сервисов. Создайте IUnitOfWork или IUnitOfWork<TContext> на уровне домена и поместите реализацию на уровень репозитория. Я не вижу причин, по которым ваша реализация репозитория должна быть отделена от вашего уровня данных, если все репозитории сохраняют данные на ObjectContext на уровне данных. Интерфейс репозитория - это логика предметной области, но реализация связана с данными

  3. Вы можете использовать DI для внедрения ваших сервисов в контроллеры и ваших репозиториев в свои сервисы. С DI ваша служба будет зависеть от интерфейса репозитория ISomethingRepository и получит реализацию EFSomethingRepository без привязки к сборке данных / репозитория. По сути, ваша реализация IControllerFactory получит контейнер IoC для предоставления всех зависимостей конструктора для Контроллера. Для этого потребуется, чтобы контейнер IoC также предоставлял все зависимости конструкторов контроллеров (службы) и их зависимости конструкторов (репозитории). Все ваши сборки будут зависеть от уровня вашего домена (на котором есть репозиторий и сервисные интерфейсы), но не будут зависеть друг от друга, потому что они зависят от интерфейса, а не от реализации. Вам понадобится либо отдельная сборка для разрешения зависимостей, либо вам нужно будет включить этот код в свой веб-проект. (Я бы порекомендовал отдельную сборку). Единственной сборкой, зависящей от сборки разрешения зависимостей, будет сборка пользовательского интерфейса, хотя даже это не является полностью необходимым, если вы используете реализацию IHttpModule для регистрации своих зависимостей в событии Application_Start (проекту по-прежнему потребуется копия библиотеки DLL в ваша папка bin, но ссылка на проект не требуется). Существует множество подходящих контейнеров IoC с открытым исходным кодом. Лучший во многом зависит от того, что вы выберете. Мне лично нравится StructureMap. И он, и Ninject являются надежными и хорошо документированными фреймворками DI.

Ответ на правки Сэма Стриано

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

Public Class CategoryController
  Private _Service As Domain.Interfaces.IService

  'This is good.
  Public Sub New(ByVal Service As Domain.Interfaces.IService)
      _Service = Service
  End Sub


  Function ListCategories() As ActionResult
      Dim Model As New CategoryViewModel


      Using UOW As New Repositories.EFUnitOfWork

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

          Mapper.Map(Of Category, CategoryViewModel)(_Service.GetCategories)

Это вызов AutoMapper? Это не связано с вашим исходным вопросом, но вам следует переместить функцию сопоставления в ActionFilter, чтобы ваш возврат был просто Return View (_Service.GetCategories)

      End Using

      Return View(Model)
  End Function

С сервисным классом проблем не было.

Репозиторий и Единица работы в основном выглядят неполными. Ваш репозиторий должен обновить ObjectContext и внедрить его в единицу работы, а затем выполнить все транзакции в рамках единицы работы (аналогично тому, что вы делали в контроллере). Проблема с его наличием в контроллере заключается в том, что один вызов службы может быть ограничен несколькими единицами работы. Вот хорошая статья о том, как реализовать Unit of Work. http://martinfowler.com/eaaCatalog/unitOfWork.html. Книги и веб-сайт Мартина Фаулера - отличные источники информации по этим типам тем.

person smartcaveman    schedule 03.03.2011
comment
@smart - Итак 1) У вас есть интерфейс IRepository в MyProject.Domain.dll? 2) То же самое, что IUnitOfWork в Domain.dll? А как насчет конкретного класса? - person Sam; 03.03.2011
comment
Я обновил свой ответ. (1) Да. (2) Да. Конкретный класс находится в MyProject.Data.dll, а MyProject.Repositories.dll также сливается с MyProject.Data.dll. - person smartcaveman; 03.03.2011
comment
@sam интерфейсы репозитория могут быть где угодно, я бы часто имел их в основном проекте и имел реализации в проекте данных - person dove; 03.03.2011
comment
@dove, значит, вы тоже читали MVC в действии? Как видите, у него нет «Core» проекта. Близким к этому является проект домена. В остальном мы даем тот же совет. - person smartcaveman; 03.03.2011
comment
@ Сэм Стриано, я настоятельно рекомендую прочитать эту статью о «Луковой архитектуре»: jeffreypalermo.com/blog/the-onion-architecture-part-1. Он дает подробные объяснения по многим вашим вопросам. Кроме того, книги Mvc in Action и Mvc 2 in Action - еще один отличный источник (частично написанный автором этой статьи). - person smartcaveman; 03.03.2011
comment
@smart - Не могли бы вы взглянуть на мою правку выше и узнать, что вы думаете? - person Sam; 04.03.2011
comment
@smart - Значит, контроллер никогда не должен использовать более одной службы в действии? Разве вы не хотели бы обернуть действие в UOW, чтобы вы могли сделать это, получить доступ к нескольким службам и функциям внутри них? - person Sam; 04.03.2011
comment
@smart - Извините, что беспокою вас, но не могли бы вы написать небольшой пример каждого слоя (только основы), чтобы я мог видеть узор? Кажется, вы действительно знаете, что делаете с этим, и хорошо понимаете. Вам было трудно это понять? - person Sam; 04.03.2011
comment
@Sam Striano, у меня действительно нет времени написать образец заявки на вопрос SO. Для этого существует множество решений с открытым исходным кодом. Я бы порекомендовал взглянуть на CodeCampServer, который является образцом приложения для книги, которую я рекомендовал ранее. Я считаю, что достаточно ответил на ваш вопрос, но если вы хотите обсудить это более подробно / на уровне конкретного проекта, со мной можно связаться по [email protected] - person smartcaveman; 04.03.2011
comment
@smart - Я понимаю ... Я посмотрел исходный код CodeCampServer и думаю, что это будет хорошим началом. Спасибо большое, я ценю это! - person Sam; 04.03.2011
comment
Здорово. Я надеюсь, что это помогает. Чтобы понять все концепции, требуется некоторое время, но в конечном итоге он вам понравится (какое-то время я действительно был сбит с толку, но я прочитал кучу книг по этой теме). Я проконсультируюсь по этому поводу, если вы решите, что вам потребуется более подробная помощь в будущем. - person smartcaveman; 04.03.2011

Чтобы ответить на ваши вопросы по порядку

1) Не обязательно плохо, это зависит от того, насколько вероятно, что вы будете придерживаться EF. Есть несколько вещей, которые вы можете сделать, чтобы уменьшить это. Одна относительно низкая стоимость (при условии, что у вас есть некоторая установка инверсии управления, если не перейти к 3), заключается в том, чтобы ссылаться только на интерфейсы ваших репозиториев из ваших сервисов.

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

3) Инверсия управления снова должна позволить все необходимое вам тестирование. Таким образом, отпадает необходимость во множестве прямых ссылок и в тестировании любого слоя изолированно.

ОБНОВЛЕНИЕ для запрошенного образца.

public class QuestionService : IQuestionService
{

    private readonly IQuestionRepository _questionRepository;

    public QuestionService(IQuestionRepository questionRepository){
           _questionRepository = questionRepository
    }
}

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

person dove    schedule 03.03.2011
comment
Не могли бы вы отредактировать свой ответ, чтобы показать несколько очень простых примеров кода? Спасибо!! - person Sam; 03.03.2011
comment
Могли бы вы с помощью DI внедрить несколько репозиториев? Подумайте о CustomerService, у него будет CustomerRepository, а затем OrderRepository для обработки заказов клиента? - person Sam; 03.03.2011
comment
IOC - это ооочень больше, чем просто тестирование - person Will Du; 03.03.2011
comment
@Will Du, вы правы, но я не предполагал, что это так, я отвечал на его вопрос, который был задан специально для тестирования ... - person dove; 04.03.2011
comment
Не могли бы вы взглянуть на мою правку выше и узнать, что вы думаете? - person Sam; 04.03.2011

Я бы предложил использовать MEF. Он дает вам желаемый фреймворк для внедрения зависимостей, но он не является полноценным; он отлично подходит для модульного тестирования. Вот несколько ответов на связанный вопрос: Упрощение тестирования с помощью дизайна соображения при использовании внедрения зависимостей

person CarneyCode    schedule 03.03.2011

Полный пример кода можно найти здесь с MEF и шаблоном репозитория (также использует EFCodeFirst).

person Steve J    schedule 14.03.2011