Общий UnitOfWork

#c# #entity-framework #design-patterns #repository-pattern #unit-of-work

#c# #entity-framework #шаблоны проектирования #репозиторий-шаблон #единица работы

Вопрос:

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

Ниже приведен мой общий репозиторий, интерфейс и классы UnitOfWork

 public interface IRepositoryBase<T> where T : class
{
    IList<T> FindAll();
    T FindByCondition(Expression<Func<T, bool>> expression);
    void Create(T entity);
    void Update(T entity);
    void Delete(T entity);
}

public abstract class RepositoryBase<T> : IRepositoryBase<T> where T : class
{
    protected DBContext _dbContext { get; set; }

    public RepositoryBase(DBContext dbContext)
    {
        _dbContext = dbContext;
    }
    //other methods removed
    public void Create(T entity)
    {
        _dbContext.Set<T>().Add(entity);
    }
}

public interface IUnitOfWork 
{
    IReminderRepository Reminder { get; }
    void Save();
}

public class UnitOfWork : IUnitOfWork, IDisposable
{
    protected DBContext _dbContext { get; set; }
    private IReminderRepository _reminderRepository;

    public UnitOfWork(DBContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IReminderRepository Reminder
    {
        get
        {
            return _reminderRepository = _reminderRepository ?? new ReminderRepository(_dbContext);
        }
    }

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

    public void Dispose()
    {
        _dbContext.Dispose();
    }
}
  

Здесь я могу расширить свой репозиторий в соответствии с моими конкретными потребностями, реализовав конкретный репозиторий как

 public interface IReminderRepository : IRepositoryBase<Reminder>
{
    IList<Reminder> GetAllReminders();
    Reminder GetReminderById(Guid id);    
    Reminder GetReminderByName(string name);    
    void CreateReminder(Reminder reminder);    
    void UpdateReminder(Reminder reminder);    
    void DeleteReminder(Reminder reminder);    
}

public class ReminderRepository : RepositoryBase<Reminder>, IReminderRepository
{
    public ReminderRepository(DBContext dbContext)
    : base(dbContext)
    {
        _dbContext = dbContext;
    }
    //other methods removed
    public Reminder GetReminderByName(string name)
    {
        return FindAll()
            .OrderByDescending(r => r.Name)
            .FirstOrDefault(r => r.Name == name);

        //return FindByCondition(r => r.Name == name);
    }
}
  

Это нормально, но когда я когда-либо создам новый конкретный репозиторий, мне также придется изменить класс UnitOfWork, добавив новое свойство для нового репозитория.

При поиске в Интернете я нашел следующее, но в моем случае это не работает, поскольку моя база репозиториев является абстрактным классом.

 public interface IUnitOfWork 
{
    void Save();
}

public class UnitOfWork : IUnitOfWork, IDisposable
{
    private readonly DBContext _dbContext { get; set; }
    private readonly Dictionary<Type, object> _repositories = new Dictionary<Type, object>();

    public Dictionary<Type, object> Repositories
    {
        get { return _repositories; }
        set { Repositories = value; }
    }

    public UnitOfWork(DBContext dbContext)
    {
        _dbContext = dbContext;
    }

    public IRepositoryBase<T> Repository<T>() where T : class
    {
        if (Repositories.Keys.Contains(typeof(T)))
        {
            return Repositories[typeof(T)] as IRepositoryBase<T>;
        }

        IRepositoryBase<T> repo = new RepositoryBase<T>(_dbContext);//This does not work
        Repositories.Add(typeof(T), repo);
        return repo;
    }

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

Комментарии:

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

2. Я полагаю, у вас есть какой-то контейнер IoC. Вы можете зарегистрироваться ReminderRepository , а затем использовать универсальный метод в своем UoW: GetRepo<T>() => _IoC.GetService<T>() . Вы также можете зарегистрировать их как IRepositoryBase<Reminder> , а затем привести репозиторий к определенному типу, если вам нужны эти методы

3. @KrzysztofSkowronek Я не использую никакой контейнер IoC. Я регистрирую свой класс UnitOfWork в файле StartUp.cs моего проекта WebAPI как services.AddTransient<IUnitOfWork, UnitOfWork>();

4. Это контейнер IoC, предоставляемый ASP.

5. @Mackhdo, да, я думаю, что я могу сделать RepositoryBase также неабстрактным.

Ответ №1:

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

Если вы не хотите расширять свой UnitOfWork класс для возврата IReminderRepository , вы можете создать его самостоятельно в методе, который фактически использует конкретный репозиторий, например:

 using (var context = new DBContext())
{
    IUnitOfWork uow = new UnitOfWork(context);
    ReminderRepository repository = new ReminderRepository(context);
    Reminder remainder = repository.GetReminderByName("...");
    remainder.SomeProperty = "updated value..";
    uow.Save();
}
  

Единственная цель использования единицы работы — в любом случае иметь возможность совместно использовать один и тот же контекст между несколькими различными репозиториями. Раскрытие a Dictionary<Type, object> в вашем UnitOfWork ничего не решит, поскольку целью использования дженериков является обеспечение безопасности типов во время компиляции.

Комментарии:

1. что я сделал, так это зарегистрировал IUnitOfWork в Startup.cs как services.AddTransient<IUnitOfWork, UnitOfWork>(); , а затем в моем контроллере использую _unitOfWork.Reminder.FindAll().ToList();

2. @NikhilGupta: Значит, у IUnitOfWork него Reminder есть свойство? Я думал, вы хотели избежать этого.

3. Да, на данный момент UoW имеет напоминание как свойство, и я хотел бы избавиться от него, потому что в будущем таких объектов может быть много, и тогда будет излишним всегда изменять UoW при введении нового объекта.

4. @NikhilGupta: Да. Итак, вы прочитали мой ответ? Он предоставляет альтернативу расширению UnitOfWork с помощью свойств репозитория. Просто позвольте ему вызывать Save контекст и самостоятельно создавать репозитории в DAL, где они вам действительно нужны.

5. и, как вы предложили, я создаю объект для IReminderRepository в методе, который его использует, тогда мне нужно будет зарегистрировать все мои репозитории в Startup.cs, а затем с помощью DI я смогу их использовать …. это правильно?