как реализовать UOW на С# с помощью EF6

Я пытаюсь реализовать UOW с шаблоном репозитория в своем приложении.

Пока независимый репозиторий на месте, но пока несколько репозиториев в одной транзакции (UOW) сводят меня с ума.

EF Relation One Customer - Many CustomerContacts

IUnitOfWork

 public interface IUnitOfWork
    : IDisposable
{
    void InitTransaction();

    void Rollback();

    void CommitTransaction();

}

БазаUOW

 public class UnitOfWork :
    IUnitOfWork
{

    protected DbContextTransaction _transaction;


    #region IUnitOfWork

     public void CommitTransaction()
    {
        _transaction.UnderlyingTransaction.Commit();
    }

    public void Rollback()
    {
        _transaction.UnderlyingTransaction.Rollback();
    }
    #endregion IUnitOfWork
}

КлиентUOW

 public class CustomerUOW
    : UnitOfWork
{
    private IRepository<CustomerRepository> _customerRepository;
    private IRepository<CustomerContactRepository> _customerContactRepository;

    public BranchUOW(IRepository<CustomerRepository> customerRepository, 
        IRepository<CustomerContactRepository> customerContactRepository)
    {
        _customerRepository= customerRepository;
        _customerContactRepository= customerContactRepository;
    }
    public override void InitTransaction()
    {
        _transaction.Commit();
    }


}

Как мне реализовать мой CustomerUOW, чтобы репозиторий Customer и CustomerContact использовал один и тот же DbContext и выполнялся в одной транзакции??

Примечание. Каждый репозиторий имеет реализацию CRUD в своем отдельном классе. как

 public class EntityRepository<C, T>
   : BaseRepository<FoodieTenantContext, T>
    where T : class
    where C : CustomerContext
{
    private DbSet<T> _dataSet
    {
        get
        {
            return _ctx.Set<T>();
        }
    }

    public EntityRepository(FoodieTenantContext ctx)
        : base(ctx)
    {
    }

    public override void Add(T entity)
    {
        _dataSet.Add(entity);
    }

    public override void Delete(T entity)
    {
        throw new NotImplementedException();
    }

    public override IEnumerable<T> Find(Expression<Func<T, bool>> predicate)
    {
        return _dataSet.Where(predicate).ToList<T>();
    }

    public override IEnumerable<T> GetAll()
    {
        return _dataSet.ToList<T>();
    }

    public override IQueryable<T> GetQuery()
    {
        return _dataSet;
    }

    public override int Save()
    {
        return _ctx.SaveChanges();
    }

    public override T Single(Expression<Func<T, bool>> predicate)
    {
        return _dataSet.Where(predicate).SingleOrDefault();
    }

    public override void Update(T entity)
    {
        _dataSet.Attach(entity);
        _ctx.Entry<T>(entity).State = EntityState.Modified;
    }
}

Спасибо


person Kgn-web    schedule 22.07.2018    source источник
comment
Как вы создаете свой UoW? И как инициализируется this.Database?   -  person Michal Ciechan    schedule 22.07.2018
comment
@MichalCiechan Я бы звонил CustomerUOW из CustomerService, который, в свою очередь, вызывался бы из API.   -  person Kgn-web    schedule 22.07.2018
comment
EF — это уже реализация UoW, зачем вам еще одна? Во-первых, DbContext itself implements the Unit of work pattern   -  person MickyD    schedule 22.07.2018
comment
@MickyD, я не хочу тесно связывать мой DAL с EF, и модульное тестирование было бы легко   -  person Kgn-web    schedule 22.07.2018
comment
Чрезмерная разработка вашего приложения для включения абстракций по сравнению с уже существующей ORM, которая сама является абстракцией любого конкретного поставщика базы данных, слишком затратна в краткосрочной и среднесрочной перспективе, а какая-либо долгосрочная выгода маловероятна. Повторное создание UoW дает мало преимуществ базовому ORM.   -  person MickyD    schedule 22.07.2018
comment
Кроме того, вы до сих пор не поняли, что DbContext является уже единицей работы, как указано в ссылке. Кажется, вы перепутали репозиторий с UoW.   -  person MickyD    schedule 22.07.2018
comment
модульный тест будет легким, вы можете издеваться над dbContecxt !!!   -  person Marcus Höglund    schedule 22.07.2018


Ответы (1)


По пути было бы предоставить Func<FoodieTenantContext, IRepository<CustomerContactRepository>> в вашем CustomerUow

public abstract class UnitOfWork : IUnitOfWork
{
    public UnitOfWork(FoodieTenantContext context)
    {
        this.Context = context;
    }

    // ... rest of the class
}

// usage could be like the following

public class CustomerUOW : UnitOfWork
{
    public CustomerService(Func<FoodieTenantContext, IRepository<CustomerRepository>> customerRepo
        , Func<FoodieTenantContext, IRepository<CustomerContactRepository>> contactRepo
        , FoodieTenantContext context) 
        : (context)
    {        
        _customerRepo = customerRepo(context);
        _contactRepo = contactRepo(context);
    }
}

Другим вариантом было бы создание RepositoryFactory, но это означало бы, что вам нужно будет предоставить свойство Context из IRepository<T>.

public class RepositoryFactory
{
    IServiceProvider _ioc; // This would be your IoC/DI Container

    public RepositoryFactory(IServiceProvider ioc)
    {
        _ioc = ioc;
    }

    // Resolve T passing in the provided `FoodieTenantContext` into the constructor
    public IRepository<T> CreateRepository<T>(FoodieTenantContext context) =>
        _ioc.Resolve<T>(context); 

}

Другим решением может быть (мое наименее любимое) раскрытие методов в RepositoryFactory для каждого типа IRepository<T>

public class RepositoryFactory
{
    public IRepository CreateCustomerContactRepository(FoodieTenantContext context) => 
        return new CustomerContactRepository(context);
}

Регистрация Func в Castle.Windsor

Согласно комментарию, чтобы зарегистрировать Func<T> в Castle.Windsor, вы можете попробовать что-то вроде следующего, который представляет собой модифицированную версию ответа Anton на Func, вводящего с помощью Вопрос о контейнере Windsor.. (Я не могу проверить это прямо сейчас)

Container.Register(

  Component.For<Func<FoodieTenantContext, IRepository<CustomerRepository>>>()
           .Instance((FoodieTenantContext context) => Container.Resolve<IRepository<CustomerRepository>>(new {context = context}))
)

В противном случае вы можете попробовать поиграть с AsFactory(). Для получения дополнительной информации ознакомьтесь с документацией Windsor

И, в крайнем случае, вы всегда можете вернуться к ручному созданию фабрики или переключению контейнеров IoC/DI, которые поддерживают Func<[TIn1, TIn2, ...], T> из коробки или, по крайней мере, изначально.

person Michal Ciechan    schedule 22.07.2018
comment
Михаил, если я сделаю предложенный вами способ №1, то у меня возникнет какая-то ошибка DI, как мне настроить DI Setup (используя CastleWindsor) - person Kgn-web; 22.07.2018
comment
Спасибо Михаил!!. Пожалуйста, проголосуйте за пост, если вы считаете, что он стоит - person Kgn-web; 24.07.2018