I've been searching for the Repository and Unit of Work Pattern in C#, and so far this is what I found:
public class Repository<T> : IRepository<T> where T:class
{
private DbContext context;
private DbSet<T> _dbSet;
public Repository(DbContext context)
{
this.context = context;
_dbSet = context.Set<T>();
}
}
However, in a project that was given to me for study, I saw this one:
public class Repository<T> : IRepository<T> where T:class
{
private readonly IUnitOfWork _unitOfWork
private readonly DbSet<T> _dbSet;
public Repository(IUnitOfWork unitOfWork)
{
_unitOfWork = unitOfWork
_dbSet = ((DbContext)_unitOfWork).Set<T>();
}
}
Can someone please clarify the difference for me? Any explanation will be highly appreciated. Thanks!
No much diference technically, but the use of the interface is much better, because you can abstract the DbContext, look if you need to change the EF to another ORM, you will just need to implement an concrete UnitOfWork, so the change will be less traumatic.
Related
I have a project where I use mediatr, CQRS and onion architecture.
public class CreateOrderCommandHandler : IRequestHandler<CreateOrderCommand, CreatedOrderDto>
{
private readonly IOrderRepository _orderRepository;
private readonly IProductRepository _productRepository;
private readonly IMapper _mapper;
public CreateOrderCommandHandler(IOrderRepository orderRepository,IProductRepository
productRepository, IMapper mapper)
{
_orderRepository = orderRepository;
_productRepository = productRepository;
_mapper = mapper;
}
public async Task<CreatedOrderDto> Handle(CreateOrderCommand request, CancellationToken
cancellationToken)
{
// _orderRepository.Add(order);
// _ productRepository.Update(product);
}
}
Should CreateOrder Command depend on Product Service instead of ProductRepository?
Should the repository be used in the Handler?
As per your code in the question, it should depend on ProductRepository not on the service, and service is a collection of classes while Reposiriry represents a single class so you cannot inject a service but you can inject a repository.
and by the way, I cannot see any Product Service in your code.
I'm hitting the error in the title when running this setup code:
Program.cs:
builder.Services.AddDbContext<TDBContext>(opt => opt.UseInMemoryDatabase("My"));
// Can't work out how to wire up the Repository?
//builder.Services.AddScoped<IRepository>(p => new TDBContext());
//builder.Services.AddScoped<IRepository, Repository>();
builder.Services.AddScoped(typeof(IRepository), typeof(Repository<>));
//builder.Services.AddScoped(typeof(IRepository), typeof(Repository<TDBContext>));
builder.Services.AddScoped<IMyService, MyService>();
var app = builder.Build(); //ERROR HERE!
Service and Repository:
public class MyService : IMyService
{
private readonly IRepository _repository;
public MyService(IRepository repository)
{
_repository = repository;
}
}
public class Repository<TDBContext> : IRepository where TDBContext : DbContext
{
protected DbContext dbContext;
public Repository(DbContext context)
{
dbContext = context;
}
public async Task<int> CreateAsync<T>(T entity) where T : class
{
this.dbContext.Set<T>().Add(entity);
return await this.dbContext.SaveChangesAsync();
}
//.....
}
public class TDBContext : DbContext
{
public TDBContext(DbContextOptions<TDBContext> options)
: base(options)
{
}
public virtual DbSet<MyTransaction> Transactions { get; set; } = null!;
public TDBContext()
{
}
}
I've tried a few suggestions found on here shown as code comments but no luck. Can someone please clarify how I wire up the Repository and get the DI to load in the DbContext?
Check the repository constructor. The container does not know how to handle DbContext as dependency when resolving the repository.
Did you mean to use the generic argument type instead?
Also the naming of the generic parameter might cause confusion.
public class Repository<TContext> : IRepository where TContext : DbContext {
protected DbContext dbContext;
public Repository(TContext context) {
dbContext = context;
}
public async Task<int> CreateAsync<T>(T entity) where T : class {
this.dbContext.Set<T>().Add(entity);
return await this.dbContext.SaveChangesAsync();
}
//.....
}
And the registration will need to use the closed type
//...
builder.Services.AddScoped<IRepository, Repository<TDBContext>>();
//...
I'm working on a web api project decoupled and the bussiness logic its decoupled in extensions (separated projects, that gives me a lot of shared code between projects), thats why I'm working on a data layer also decoupled, everything its working but the only thing that keeps me everything coupled its AppDbContext.cs
Here is a POC code so you can get my idea (my problem):
AppDbContext.cs
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> dbContextOptions) : base(dbContextOptions)
{
}
}
IEntity.cs
public interface IEntity<TKey>
{
TKey Id { get; set; }
}
IRepository.cs
public interface IRepository<TEntity, TKey>
where TEntity : class, IEntity<TKey>
{
IEnumerable<TEntity> GetAll();
}
GenericRepository.cs
public class GenericRepository<TEntity, TKey> : IRepository<TEntity, TKey>
where TEntity : class, IEntity<TKey>
{
private readonly AppDbContext dbContext;
public GenericRepository(AppDbContext dbContext)
{
this.dbContext = dbContext;
}
public IEnumerable<TEntity> GetAll()
{
return dbContext.Set<TEntity>().ToList();
}
}
and register it in the composition root like this:
services.AddScoped(typeof(IRepository<,>), typeof(GenericRepository<,>));
As you can see, my generic repository uses AppDbContext, but what if in a different project that is called different ? or inherits from IdentityContext, how can I make my Generic Repository, DbContext independient but also configurable at startup ?
Update:
I forgot to mention that, in some cases there will be more than one DbContext implementation.
Lowest common factor here is DbContext.
Rafactor GenericRepository to explicitly depend on DbContext
public class GenericRepository<TEntity, TKey> : IRepository<TEntity, TKey>
where TEntity : class, IEntity<TKey> {
private readonly DbContext dbContext;
public GenericRepository(DbContext dbContext) {
this.dbContext = dbContext;
}
public IEnumerable<TEntity> GetAll() {
return dbContext.Set<TEntity>().ToList();
}
}
At composition root you would then make the association
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(Configuration["database:connectionString"]));
services.AddScoped(typeof(IRepository<,>), typeof(GenericRepository<,>));
services.AddScoped<DbContext, AppDbContext>();
Update
In case of multiple Contexts, That would require a little more abstraction. In cases like that I create a specific abstraction for each context. like IDbContext or ILoggingContext
public interface IDbContext : IDisposable {
int SaveContext();
DbSet<TEntity> Set<TEntity>();
//...other relevant EF members, etc
}
public interface IAppDbContext : IDbContext {
}
public interface ILogDbContext : IDbContext {
}
and have my DbContext derived classes inherit from the one relevant to it.
public class AppDbContext : DbContext, IAppDbContext {
public AppDbContext(DbContextOptions<AppDbContext> dbContextOptions) : base(dbContextOptions) {
}
}
public class LogDbContext : DbContext, ILogDbContext {
public AppDbContext(DbContextOptions<LogDbContext> dbContextOptions) : base(dbContextOptions) {
}
}
From there the generic repository would explicitly depend on the relevant abstraction(s)
public class GenericRepository<TEntity, TKey> : IRepository<TEntity, TKey>
where TEntity : class, IEntity<TKey> {
private readonly IDbContext dbContext;
public GenericRepository(IAppDbContext dbContext) {
this.dbContext = dbContext;
}
//...code removed for brevity
}
and then do the necessary configuration at composition root.
services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(Configuration["database:appConnectionString"]));
services.AddDbContext<LogDbContext>(options =>
options.UseSqlServer(Configuration["database:logConnectionString"]));
services.AddScoped(typeof(IRepository<,>), typeof(GenericRepository<,>));
services.AddScoped<IAppDbContext, AppDbContext>();
services.AddScoped<ILogDbContext, LogDbContext>();
I found a simpler solution (but not cleaner). For the notice I'm actually against using Repository pattern for EF.
Startup.cs
services.AddDbContext<context1>(options => options.UseSqlServer(Connection1), ServiceLifetime.Scoped);
services.AddDbContext<context2>(options => options.UseSqlServer(DbConnection2), ServiceLifetime.Scoped);
// Build an intermediate service provider
var sp = services.BuildServiceProvider();
services
.AddScoped<IGenericRepository<Repository.Models.SubmissionEntry>>(_ => new GenericRepository<SomeSpecificModelThatUsesContext2>(sp.GetService<context2>(), true))
.AddScoped(typeof(IGenericRepository<>), typeof(GenericRepository<>))
GenericRepository.cs
just add second constructor
private readonly DbContext dbContext;
public GenericRepository(context1 ctx)
{dbContext=ctx}
public GenericRepository(context2 ctx, bool fakeParamToAddSecondConstructor)
{dbContext=ctx}
When playing around with AutoMapper I was wondering whether the following is possible to implement like this (haven't been able to set it up correctly).
Base Service:
public class BaseService<T, IEntityDTO> : IService<T, IEntityDTO> where T : class, IEntity
{
private IUnitOfWork _unitOfWork;
private IRepository<IEntity> _repository;
private IMapper _mapper;
public BaseService(IUnitOfWork unitOfWork, IMapper mapper)
{
_unitOfWork = unitOfWork;
_repository = unitOfWork.Repository<IEntity>();
_mapper = mapper;
}
public IList<IEntityDTO> GetAll()
{
return _mapper.Map<IList<IEntityDTO>>(_repository.GetAll().ToList());
}
}
Concrete Service:
public class HotelService : BaseService<Hotels, HotelsDTO>, IHotelService
{
private IUnitOfWork _unitOfWork;
private IRepository<Hotels> _hotelsRepository;
private IMapper _mapper;
public HotelService(IUnitOfWork unitOfWork, IMapper mapper) : base(unitOfWork, mapper)
{
_unitOfWork = unitOfWork;
_hotelsRepository = unitOfWork.Repository<Hotels>();
_mapper = mapper;
}
}
Current mappings:
public class AutoMapperProfileConfiguration : Profile
{
protected override void Configure()
{
CreateMap<Hotels, HotelsDTO>().ReverseMap();
}
}
I'm kindly clueless on how the mapping should be done. Anyone any advice or is this just not the way to go?
You can specify DTO type in BaseService as generic parameter:
public class BaseService<T, TDTO> : IService<T, TDTO>
where T : class, IEntity
where TDTO : class, IEntityDTO
{
private IRepository<T> _repository;
...
...
public IList<TDTO> GetAll()
{
return _mapper.Map<IList<TDTO>>(_repository.GetAll().ToList());
}
}
Managed to solve my problem with the following line of code which looks up the mapping of the passed entity to the basecontroller.
public List<TDTO> GetAll()
{
var list = _repository.GetAll().ToList();
return (List<TDTO>)_mapper.Map(list, list.GetType(), typeof(IList<TDTO>));
}
I am using Ninject for IoC. I have the following classes.
// Repository
public class EFProductRepository : IProductRepository, IUnitOfWorkRepository
{
private IUnitOfWork unitOfWork;
private EFDbContext efDbContext;
public EFProductRepository(IUnitOfWork uow)
{
unitOfWork = uow;
efDbContext = new EFDbContext();
}
//
}
// Controller
public class ProductController : Controller
{
private IUnitOfWork unitOfWork;
private IProductRepository productRepository;
public ProductController(IUnitOfWork uow, IProductRepository repo)
{
unitOfWork = uow;
productRepository = repo;
}
}
Currently my ninject bindings are as follow which assign new instance of the concrete class for the interface.
ninjectKernel.Bind<IUnitOfWork>().To<UnitOfWork>();
ninjectKernel.Bind<IProductRepository>().To<EFProductRepository>();
using my ninject controller factory, I need to inject same instance of the IUnitOfWork class to the ProductController and EFProductRepository. Please guide me.