Где мне создать экземпляр единицы работы в приложении ASP.Net MVC 3?

Я прочитал столько сообщений в Stackoverflow, сколько смог найти, в отношении использования шаблона единицы работы в приложении ASP.Net MVC 3, которое включает бизнес-уровень. Тем не менее, у меня все еще есть пара вопросов по этой теме, и я был бы очень признателен за любые отзывы, которые люди могут мне дать.

Я разрабатываю веб-приложение ASP.Net MVC 3, которое использует EF 4.1. В этом проекте я буду использовать как репозиторий, так и шаблоны единиц работы, подобно тому, как они используются в это отличное руководство

Разница в моем проекте заключается в том, что мне нужно также включить бизнес-уровень (отдельный проект в моем решении), чтобы выполнять различные бизнес-правила для приложения. Упомянутый выше учебник не имеет бизнес-уровня и поэтому создает экземпляр класса Unit of Work из контроллера.

public class CourseController : Controller
{
    private UnitOfWork unitOfWork = new UnitOfWork();

Однако мой вопрос: где мне создать экземпляр класса Unit of Work, если у меня есть бизнес-уровень?

Лично я считаю, что его следует создать в моем контроллере, а затем внедрить в бизнес-уровень следующим образом:

public class PeopleController : Controller
{
    private readonly IUnitOfWork _UoW;
    private IPersonService _personService;

    public PeopleController()
    {
        _UoW = new UnitOfWork();
        _personService = new PersonService(_UoW);
    }

    public PeopleController(IUnitOfWork UoW, IPersonService personService)
    {
        _UoW = UoW;
        _personService = personService;

    }

    public ActionResult Edit(int id)
    {
        Person person = _personService.Edit(id);
        return View(person);
    }

public class UnitOfWork : IUnitOfWork, IDisposable
{
    private BlogEntities _context = new BlogEntities();
    private PersonRepository personRepository = null;

    public IPersonRepository PersonRepository
    {
        get
        {

            if (this.personRepository == null)
            {
                this.personRepository = new PersonRepository(_context);
            }
            return personRepository;
        }
    }

    public void Save()
    {
        _context.SaveChanges();
    }


public class PersonService : IPersonService
{
    private readonly IUnitOfWork _UoW;

    public PersonService(IUnitOfWork UoW)
    {
        _UoW = UoW;
    }

    public Person Edit(int id)
    {
         Person person = _UoW.PersonRepository.GetPersonByID(id);
         return person;
    }

public class PersonRepository : IPersonRepository
{
    private readonly BlogEntities _context;

    public PersonRepository(BlogEntities context)
    {
        _context = context;
    }

    public Person GetPersonByID(int ID)
    {
        return _context.People.Where(p => p.ID == ID).Single();
    }

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

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

В очередной раз благодарим за помощь.


person tcode    schedule 02.02.2012    source источник


Ответы (1)


Я думаю, вам нужно внести пару изменений:

  1. Разрешите вашему DI-контейнеру внедрять экземпляр UnitOfWork в ваши сервисные классы в их конструкторах и полностью исключить его из вашего контроллера.

  2. Если ваш DI-контейнер поддерживает это (например, Ninject), настройте свой UnitOfWork для управления на основе каждого запроса; Таким образом, ваши услуги будут получать разные UnitOfWork для каждого запроса, и все готово. Или...

  3. Если ваш DI-контейнер не поддерживает время жизни для каждого запроса, настройте его для управления UnitOfWork как одиночным, чтобы каждый класс Service получал один и тот же экземпляр. Затем обновите свой UnitOfWork, чтобы сохранить его объект Entities в хранилище данных, которое хранит объекты для каждого запроса, например, в HttpContext.Current.Items, как описано здесь.

Изменить 1

Относительно того, куда следует вводить UnitOfWork; Я бы сказал, что сервисный уровень - правильное место. Если вы представляете свою систему как ряд уровней, где внешние уровни связаны с взаимодействием с пользователем, а нижние уровни — с хранением данных, каждый уровень должен меньше заботиться о пользователях и больше — о хранении данных. UnitOfWork — это концепция одного из «низкоуровневых» уровней, а контроллер относится к более высокому уровню; ваш слой Service помещается между ними. Поэтому имеет смысл поместить UnitOfWork в класс Service, а не Controller.

Изменить 2

Чтобы уточнить создание UnitOfWork и его отношение к HttpContext.Current.Items:

Ваш UnitOfWork больше не будет содержать ссылку на объект Entities, это будет сделано через объект HttpContext, внедренный в UnitOfWork за таким интерфейсом:

public interface IPerRequestDataStore : IDisposable
{
    bool Contains(string key);

    void Store<T>(string key, T value);

    T Get<T>(string key);
}

Затем объект HttpContext будет реализовывать IPerRequestDataStore следующим образом:

public class StaticHttpContextPerRequestDataStore : IPerRequestDataStore
{
    public bool Contains(string key)
    {
        return HttpContext.Current.Items.Contains(key);
    }

    public void Store<T>(string key, T value)
    {
        HttpContext.Current.Items[key] = value;
    }

    public T Get<T>(string key)
    {
        if (!this.Contains(key))
        {
            return default(T);
        }

        return (T)HttpContext.Current.Items[key];
    }

    public void Dispose()
    {
        var disposables = HttpContext.Current.Items.Values.OfType<IDisposable>();

        foreach (var disposable in disposables)
        {
            disposable.Dispose();
        }
    }
}

Кстати, я назвал его StaticHttpContextPerRequestDataStore, так как он использует статическое свойство HttpContext.Current; это не идеально для модульного тестирования (совсем другая тема), но, по крайней мере, имя указывает на характер его зависимости.

Затем ваш UnitOfWork передает IPerRequestDataStore каждому из своих Repository объектов, чтобы они могли получить доступ к Entities; это означает, что независимо от того, сколько экземпляров UnitOfWork вы создадите, вы будете использовать один и тот же объект Entities на протяжении всего запроса, поскольку он хранится и извлекается в файле IPerRequestDataStore.

У вас будет абстрактная база Repository, которая будет использовать свой IPerRequestDataStore для ленивой загрузки своего объекта Entities следующим образом:

public abstract class RepositoryBase : IDisposable
{
    private readonly IPerRequestDataStore _dataStore;

    private PersonRepository personRepository;

    protected RepositoryBase(IPerRequestDataStore dataStore)
    {
        this._dataStore = dataStore;
    }

    protected BlogEntities Context
    {
        get
        {
            const string contextKey = "context";

            if (!this._dataStore.Contains(contextKey))
            {
                this._dataStore.Store(contextKey, new BlogEntities());
            }

            return this._dataStore.Get<BlogEntities>(contextKey);
        }
    }

    public void Dispose()
    {
        this._dataStore.Dispose();
    }
}

Ваш PeopleRepository (например) будет выглядеть так:

public class PeopleRepository : RepositoryBase, IPersonRepository
{
    public PeopleRepository(IPerRequestDataStore dataStore) 
        : base(dataStore)
    {
    }

    public Person FindById(int personId)
    {
        return this.Context.Persons.FirstOrDefault(p => p.PersonId == personId);
    }
}

И, наконец, вот создание вашего PeopleController:

IPerRequestDataStore dataStore = new StaticHttpContextDataStore();
UnitOfWork unitOfWork = new UnitOfWork(dataStore);
PeopleService service = new PeopleService(unitOfWork);
PeopleController controller = new PeopleController(service);

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

person Steve Wilkes    schedule 02.02.2012
comment
Спасибо за это, Стив. В настоящее время я никогда не использовал и не реализовывал DI-контейнер, хотя определенно не исключаю этого. Однако для этого приложения, которое я создаю, если я решу не внедрять DI-контейнер и останусь с ручным DI, будет ли код, который я упомянул в своем вопросе (в частности, внедрение UoW в службу), можно было бы использовать? - person tcode; 03.02.2012
comment
Вы, конечно, можете вручную соединить все вместе; в этом случае вам придется использовать второй вариант создания объекта, который использует HttpContext.Current.Items для хранения вашего объекта Entities для каждого запроса. Независимо от того, используете ли вы контейнер или нет, я бы все же сказал, что UnitOfWork должен идти на уровень службы; Я обновил свой ответ, чтобы немного прояснить это :) - person Steve Wilkes; 03.02.2012
comment
Еще раз спасибо, Стив, хотя я, кажется, еще больше запутался! Вы говорите, что мне нужно создать объект, который использует HttpContext.Current.Items для хранения моих сущностей для каждого запроса, но мой ObjectContext будет хранить/управлять моими сущностями, и он будет создан внутри моего класса Unit of Work, и UoW будет существовать только для HTTP-запроса. - person tcode; 03.02.2012
comment
Кроме того, создание экземпляра UoW в Сервисе наверняка может привести к проблемам, например, предположим, что мне нужно обновить два разных типа объектов, используя два отдельных Сервиса как часть одной и той же транзакции. Если экземпляр UoW создается в каждой службе, то у меня будет отдельный экземпляр контекста базы данных для каждого обновления, однако один может завершиться успешно, а другой — потерпеть неудачу, что противоречит цели UoW, т. е. атомарной транзакции. - person tcode; 03.02.2012
comment
Извините, если я до сих пор не был достаточно ясен; Я обновил свой ответ, чтобы предоставить более подробную информацию. Надеюсь, теперь это имеет смысл! :) - person Steve Wilkes; 03.02.2012
comment
Спасибо, Стив. Очень признателен. - person tcode; 01.03.2012