Общая проблема с единицей работы - PullRequest
3 голосов
/ 12 августа 2011

Мне нужна помощь с дизайном шаблона Unit of Work + Repository + IoC.У меня есть несколько интерфейсов, определенных следующим образом:

public interface IRepository<T>
{       
    T GetEntity(int id);
}

public interface IUserRepository : IRepository<User>
{   
    User GetUserByXyz(int id);
}

public interface IUnitOfWork
{
    T Respository<T>() where T : IRepository<T>;
}

Я использую Unity для разрешения некоторых ссылок.Вот реализация UoW:

public class UnitOfWork : IUnitOfWork
{
    public T Respository<T>() where T : IRepository<T>
    {
        var container = new UnityContainer();
        return container.Resolve<T>();
    }
}

Теперь у меня возникают проблемы с вызовом интерфейса:

User user = _unitOfWork.Respository<IUserRepository>().GetUserByXyz(1);

Тип 'IUserRepository' нельзя использовать в качестве параметра типа 'T'в универсальном типе или методе' IUnitOfWork.Respository () '.Не существует неявного преобразования ссылок из IUserRepository в IRepository.

Как обойти ошибку общего ограничения?

Ответы [ 4 ]

3 голосов
/ 12 августа 2011

Расширение моего комментария:

Утверждение public T Respository<T>() where T : IRepository<T> подразумевает, что вы ожидаете тип, который сам является репозиторием, например IUserRepository должен быть IRepository<IUserRepository>, чтобы удовлетворить ваше условие.

Вам нужны два разных дженерика, один для элемента, который хранится в хранилище TItem, а другой для самого хранилища TRepo.

Тогда весь код становится:

public interface IRepository<TItem>
{       
    TItem GetEntity(int id);
}

public interface IUserRepository : IRepository<User>
{   
}

public interface IUnitOfWork
{
    TRepo Respository<TRepo,TItem>() where TRepo : IRepository<TItm>;
}

и

public class UnitOfWork : IUnitOfWork
{
    public TRepo Respository<TRepo,TItem>() where TRepo : IRepository<TItem>
    {
        var container = new UnityContainer();
        return container.Resolve<TRepo>();
    }
}

наконец, вызов становится:

User user = _unitOfWork.Respository<IUserRepository,User>().GetEntity(1);
2 голосов
/ 12 августа 2011

Начальная нота:

_unitOfWork.Respository<IUserRepository>()…

Как таковой, вы, по сути, «злоупотребляете» UnityOfWork в качестве локатора службы (вы можете запросить у него любой тип хранилища), но, похоже, он не дает никаких дополнительных преимуществ , Это действительно то, что вы хотите? Не могли бы вы просто отказаться от UnitOfWork и вместо этого сделать следующее:

_unityContainer.Resolve<IUserRepository>()…

Альтернативное решение, не требующее параметра второго типа:

Я согласен с @Jon Egerton, что для правильной работы одной из них можно было бы ввести второй параметр универсального типа (TItem рядом с TItemRepository). Однако существует другое решение, включающее интерфейс маркера IRepository:

// non-generic marker interface (empty)
public interface IRepository {}

public interface IRepository<T> : IRepository { … /* as before */ }
//                              ^^^^^^^^^^^^^
//                                  added

public class UnitOfWork
{
    public TRepository Get<TRepository>() where TRepository : IRepository
                                       // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
                                       // this way, no 2nd type parameter is
                                       // needed since the marker interface is
                                       // non-generic.
    { 
        return new UnityContainer().Resolve<TRespository>();
    }
}

По запросу: Пример единицы работы:

Если вы будете следовать определению Мартина Фаулера для модели единиц работы , вы получите нечто весьма отличное от того, что есть у вас сейчас. Скорее, единица работы в соответствии с его превосходством просто отслеживает все изменения, которые были внесены в коллекцию объектов. Идея, лежащая в основе этого, состоит в том, что изменения не сохраняются (например, в базе данных) по одному, а все одновременно, когда запрашивается через объект единицы работы; таким образом, имя шаблона:

class UnitOfWork<T>
{
    // the UnitOfWork object tracks changes to objects of type T:
    private HashSet<T> newItems;
    private HashSet<T> modifiedItems;
    private HashSet<T> removedItems;

    public void Commit()
    {
        // let's say items are persisted to an RDBMS, then:
        // * generate 'DELETE FROM [tableForItemsOfTypeT]' statements
        //   for all items in the 'removedItems' set;
        // * generate 'INSERT INTO [tableForItemsOfTypeT]' statements
        //   for all items in the 'newItems' set;
        // * generate 'UPDATE [tableForItemsOfTypeT]' statements
        //   for all items in the 'modifiedItems' set.
    }
}
1 голос
/ 12 августа 2011

Ваше определение IUnitOfWork кажется немного странным, и кажется, что вы неправильно установили ограничение общего параметра:

public interface IUnitOfWork
{
    T Respository<T>() where T : IRepository<T>;
}

Я бы попытался избавиться от ограничения общего параметра, есливозможный.Например:

public interface IUnitOfWork<T>
{
    IRepository<T> Respository { get; }
}

public class UnitOfWork<T> : IUnitOfWork<T>
{
    public IRepository<T> Respository
    {
        get
        {
            var container = new UnityContainer();
            return container.Resolve<IRepository<T>>();
        }
    }
}

(Правда, я не уверен, что это хорошая идея - ограничить класс UnitOfWork одним конкретным типом объекта, параметризовав его таким образом. В теории вы могли бы также иметьUnitOfWork класс, который реализует IUnitOfWork<T> несколько раз, для разных T, хотя это, вероятно, в равной степени неразумно. Судите сами, что наиболее подходит для ваших целей.)

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

PS: Возможно, Repository действительно имеет больше смысла, если это метод, а несвойство, как показано выше.Я бы выбрал, основываясь на том, насколько дорого «получить» хранилище.Если это дорого, сделайте это методом;если это дешевая операция, собственность может быть просто в порядке.Если вы оставите его как метод, я бы переименовал его в GetRepository, чтобы лучше придерживаться общих правил именования .NET.Альтернативный подход:

public interface IUnitOfWork
{
    IRepository<T> GetRespository<T>()
}
0 голосов
/ 12 августа 2011

Вы путаете свое общее ограничение:

public T Respository<T,U>() where T : IRepository<U> 

User user = _unitOfWork.Respository<IUserRepository,User>().GetEntity(1);
...