Реализация шаблона репозитория в ASP.NET MVC - PullRequest
7 голосов
/ 03 марта 2011

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

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

Workflow:

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

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

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

Несколько проблем:

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

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

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

Service

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 ]

7 голосов
/ 03 марта 2011

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

  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

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

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

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

      End Using

      Return View(Model)
  End Function

Класс Service не имел проблем.

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

1 голос
/ 03 марта 2011

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

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

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

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

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

public class QuestionService : IQuestionService
{

    private readonly IQuestionRepository _questionRepository;

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

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

0 голосов
/ 15 марта 2011

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

0 голосов
/ 03 марта 2011

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

...