I have more DbContext's. I want to use just one GenericRepository.
I try to create a GenericDbContextFactory. But how can I create TContext? What do I have to do, so the context is not null?
public class GenericRepository<TTable, TContext> : IGenericRepository<TTable, TContext>
where TTable : class
where TContext : DbContext
{
private TContext _context { get; set; } = default!;
private IGenericDbContextFactory _contextFactory;
private DbSet<TTable> _table { get; set; } = default!;
private readonly string _connectionString;
public GenericRepository(IGenericDbContextFactory contextFactory, string connectionString)
{
_connectionString = connectionString;
_contextFactory = contextFactory;
_context = GetNew();
}
public virtual void Reset()
{
_context.Dispose();
_context = GetNew();
}
public TContext GetNew()
{
var context = _contextFactory.Create(_connectionString) as TContext;
_table = context.Set<TTable>();
return context;
}
public async Task Save()
{
try
{
await _context.SaveChangesAsync();
}
catch (Exception ex)
{
Reset();
throw new Exception(ex.Message);
}
_context.ChangeTracker.Clear();
}
public class GenericDbContextFactory : IGenericDbContextFactory
{
public DbContext Create(string connectionString)
{
if (!string.IsNullOrEmpty(connectionString))
{
var optionsBuilder = new DbContextOptionsBuilder<DbContext>();
optionsBuilder.UseSqlServer(connectionString);
var context = new DbContext(optionsBuilder.Options);
return context;
}
else
{
throw new ArgumentNullException("ConnectionId");
}
}
}
GetNew return null (throw NullReferenceExceptioninstead?) because :
public TContext GetNew()
{
// GenericDbContextFactory.Create return GenericDbContext
GenericDbContext genericDbContext = _contextFactory.Create(_connectionString);
// GenericDbContext isn't TContext, then as operator return null
// context is set with null
var context = genericDbContext as TContext;
// throw NullReference Exception
_table = context.Set<TTable>();
return context;
}
To resolve this, you need GenericDbContextFactory.Create return TContext instance. But it isn't possible to have generic constrain with constructor parameters. One solution :
public class GenericRepository<TTable, TContext> : IGenericRepository<TTable, TContext>
where TTable : class
where TContext : GenericDbContext, new()
{
public TContext GetNew()
{
var context = _contextFactory.Create<TContext>(_connectionString);
_table = context.Set<TTable>();
return context;
}
...
}
public class GenericDbContextFactory : IGenericDbContextFactory
{
public TContext Create<TContext>(string connectionString) where TContext : GenericDbContext, new()
{
if (!string.IsNullOrEmpty(connectionString))
{
var context = new TContext();
context.Initialize(connectionString);
return context;
}
...
}
}
public abstract class GenericDbContext : DbContext
{
private string _connectionString;
public GenericDbContext()
{ }
public abstract void Initialize(string connectionString)
=> _connectionString = connectionString;
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (string.IsNullOrEmpty(_connectionString))
throw new InvalidOperationException();
optionsBuilder.UseSqlServer(_connectionString);
}
}
You can found other possibilities in this other question :
Is there a generic constructor with parameter constraint in C#?
But please read carefully the remark of #PanagiotisKanavos. It's a really bad implementation of the repository pattern, because EF Core isn't hidden.
To use GenericRepository, it need to specify the real DbContext type and DbSet is raw exposed. With a good repository, you only manipulate the repository, without know what is under the hood.
In the majority of cases, EF Core can be use directly like a repository.
Related
I'm trying to use generics to create an abstract query request handler for aggregate roots in a domain-driven design architecture.
I've got the following classes to setup a Mediatr request.
However, I get a compiler error when using ISender.Send because it thinks my response object is just an object instead a QueryResult<T>.
Do you know what I'm doing wrong?
public interface IQuery<T> : IRequest<T>
where T : class
{
Guid CorrelationId { get; }
}
public abstract class BaseResult
{
protected BaseResult(ResultStatus status, Dictionary<string, List<string>>? errors)
{
this.Status = status;
this.Errors = errors ?? new Dictionary<string, List<string>>();
this.IsSuccess = status == ResultStatus.Success;
}
public bool IsSuccess { get; private set; }
public ResultStatus Status { get; private set; }
public Dictionary<string, List<string>> Errors { get; private set; }
...
}
public class QueryResult<T> : BaseResult
where T : class
{
private QueryResult(T? data, ResultStatus status, Dictionary<string, List<string>>? errors)
: base(status, errors)
{
this.Data = data;
}
public T? Data { get; }
...
}
public class AggregateRootQuery<TAggregate, TDto>
: IRequest<QueryResult<IEnumerable<TDto>>>, IQuery<IEnumerable<TDto>>
where TAggregate : class, IAggregateRoot // IAggregate root is a marker interface to only allow queries to start at the aggregate root when using my EF core DB context wrapper
where TDto : class
{
public AggregateRootQuery(Guid correlationId)
{
this.CorrelationId = correlationId;
}
public Guid CorrelationId { get; }
}
public abstract class BaseAggregateRootQueryHandler<TAggregate, TDto> : IRequestHandler<AggregateRootQuery<TAggregate, TDto>, QueryResult<IEnumerable<TDto>>>
where TAggregate : class, IAggregateRoot
where TDto : class
{
protected BaseAggregateRootQueryHandler(IDbContext dbContext, IMapper mapper, ILogger logger)
{
this.DbContext = dbContext;
this.Mapper = mapper;
this.Logger = logger;
}
protected IDbContext DbContext { get; }
protected IMapper Mapper { get; }
protected ILogger Logger { get; }
public async Task<QueryResult<IEnumerable<TDto>>> Handle(AggregateRootQuery<TAggregate, TDto> request, CancellationToken cancellationToken)
{
var entities = await this.ApplyFilter(this.DbContext.AggregateRoot<TAggregate>())
.ToArrayAsync(cancellationToken);
var dtos = this.MapToDataTransferObjects(entities);
this.Logger.Debug("{Count} {EntityType} read from database", entities.Length, nameof(TAggregate));
return QueryResult<IEnumerable<TDto>>.Success(dtos);
}
protected abstract IQueryable<TAggregate> ApplyFilter(IQueryable<TAggregate> source);
protected virtual IEnumerable<TDto> MapToDataTransferObjects(IEnumerable<TAggregate> source)
{
return this.Mapper.Map<IEnumerable<TDto>>(source);
}
}
Usage
// Domain.Order implements IAggregateRoot
public class OrderQueryHandler : BaseAggregateRootQueryHandler<Domain.Order, OrderDto>
{
public OrderQueryHandler(IDbContext dbContext, IMapper mapper, ILogger logger)
:base(dbContext, mapper, logger)
{
}
protected override IQueryable<Domain.Order> ApplyFilter(IQueryable<Domain.Order> source)
{
return source.OrderBy(x => x.Name);
{
}
var result = await this.Mediator.Send(new AggregateRootQuery<Domain.Order, OrdrDto>(Guid.NewGuid()));
// `IsSuccess` and `Data` have compiler errors because it thinks `result` is an object and not a `QueryResult<IEnumerable<OrderDto>>`
if (!result.IsSuccess || result.Data == null)
{
// Error handing
}
I am receiving null exception error on my framework. I have tried to apply Repository and Unit of Work design patterns in my application. What I am trying to do is simply retreiving user titles from my data base with GetAll() method.
Here is my repository class:
public class Repository<T> : IRepository<T> where T : class
{
protected readonly DbContext Context;
public Repository(DbContext context)
{
this.Context = context;
}
public T Get(int id)
{
return Context.Set<T>().Find(id);
}
public IEnumerable<T> GetAll()
{
return Context.Set<T>().ToList();
}
public IEnumerable<T> Find(Expression<Func<T, bool>> predicate)
{
return Context.Set<T>().Where(predicate);
}
public void Add(T entity)
{
Context.Set<T>().Add(entity);
}
public void AddRange(IEnumerable<T> entityList)
{
Context.Set<T>().AddRange(entityList);
}
public void Remove(T entity)
{
Context.Set<T>().Remove(entity);
}
public void RemoveRange(IEnumerable<T> entityList)
{
Context.Set<T>().RemoveRange(entityList);
}
}
This is IUserTitlesRepository:
public interface IUserTitlesRepository : IRepository<UserTitles>
{
}
And, the class where above interface implemented:
public UserTitlesRepository(XaPaDataContext context) : base(context)
{
}
public XaPaDataContext XaPaDataContext
{
get { return Context as XaPaDataContext; }
}
Before coming to Controller layer, I have two more layers, which are Operation and Manager layers. And, I think I have messed up on that part (on Base Manager class as shown below).
This is operation layer:
public class UserTitlesOperations
{
private readonly IUnitOfWork _uow;
public UserTitlesOperations(IUnitOfWork uow)
{
_uow = uow;
}
public List<UserTitles> GetAllUserTitles()
{
try
{
List<UserTitles> userTitleList = _uow.UserTitles.GetAll().ToList();
_uow.Complete();
return userTitleList;
}
catch (Exception ex)
{
throw new Exception(ex.ToString());
}
}
}
Below is the BaseManager class which gives inheritance to all manager classes.
public abstract class BaseManager
{
private IUnitOfWork _iUow;
private readonly XaPaDataContext _context;
public IUnitOfWork IUOW
{
get
{
if (_iUow == null)
{
_iUow = new XaPaUnitOfWork(_context);
}
return _iUow;
}
}
}
This is the manager class:
public class UserTitlesManager : BaseManager
{
private readonly UserTitlesOperations _userTitlesOperations;
public UserTitlesManager()
{
_userTitlesOperations = new UserTitlesOperations(base.IUOW);
}
public List<UserTitlesWM> GetAllUserTitles()
{
try
{
return UserTitlesMapping.MaptoWM(_userTitlesOperations.GetAllUserTitles());
}
catch (Exception ex)
{
throw new Exception(ex.ToString());
}
}
}
Finally, this is my API Controller:
[Route("api/LoginRequest")]
public class TitlesController : BaseController
{
UserTitlesManager _userTitlesManager;
public LoginController()
{
_userTitlesManager = new UserTitlesManager();
}
[Route("RetreiveTitles")]
public HttpResponseMessage GetTitles()
{
try
{
return Request.CreateResponse(HttpStatusCode.OK, _userTitlesManager.GetAllUserTitles());
}
catch (Exception ex)
{
return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ex.ToString());
}
}
}
By the way BaseController is just another API controller which gives inheritance to all other API controllers, and houses a method which is used by all the other controllers.
So, I'm still trying to sharpen my self on this design patterns and would be glad if anyone could show my mistake on BaseManager class. As I said, I suppose the problem is caused by that private readonly XaPaDataContext _context; line. On the other hand,I can't figure out how to corrrect it as my operation classes' constructors are asking for IUnitOfWork.
Thank you in advance!
EDIT:
Just realized that I forgot to share my Unit of Work class:
public class XaPaUnitOfWork : IUnitOfWork
{
private readonly XaPaDataContext _context;
public XaPaUnitOfWork(XaPaDataContext context)
{
_context = context;
Categories = new CategoriesRepository(_context);
OrderDetails = new OrderDetailsRepository(_context);
Orders = new OrdersRepository(_context);
ProductImages = new ProductImagesRepository(_context);
Products = new ProductsRepository(_context);
Users = new UsersRepository(_context);
UserTitles = new UserTitlesRepository(_context);
UserTokens = new UserTokensRepository(_context);
}
public ICategoriesRepository Categories { get; private set; }
public IOrderDetailsRepository OrderDetails { get; private set; }
public IOrdersRepository Orders { get; private set; }
public IProductImagesRepository ProductImages { get; private set; }
public IProductsRepository Products { get; private set; }
public IUsersRepository Users { get; private set; }
public IUserTitlesRepository UserTitles { get; private set; }
public IUserTokensRepository UserTokens { get; private set; }
public int Complete()
{
return _context.SaveChanges();
}
public void Dispose()
{
_context.Dispose();
}
}
After I have changed my BaseManager class as below:
public abstract class BaseManager
{
private IUnitOfWork _iUow;
public IUnitOfWork IUOW
{
get
{
if (_iUow == null)
{
_iUow = new XaPaUnitOfWork(new XaPaDataContext());
}
return _iUow;
}
}
}
I have achived to receive HttpStatusCode.OK
But, honestly, I'm still unsure about the real reason. I make this correction mostly by heart.
In WebApi Core, I pass the context to a service collections via AddDataAccess. I really don't understand why the context is null at this line :
var res = this.Context.Set<TEntity>().AsEnumerable();
Some people will say because there is null in in the parameter in the constructor but to my it's correct. Should be something else.
//In the Stratup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<myConnectionStringContext>
(options => options.UseSqlServer(_config["ConnectionStrings:myConnectionString"]));
services.AddDataAccess<myConnectionStringContext>();
// Add framework services.
services.AddMvc();
}
//In my personnal package
public static class ServiceCollectionExtentions
{
public static IServiceCollection AddDataAccess<TEntityContext>(this IServiceCollection services)
where TEntityContext : DbContext
{
RegisterDataAccess<TEntityContext>(services);
return services;
}
private static void RegisterDataAccess<TEntityContext>(IServiceCollection services)
where TEntityContext : DbContext
{
services.AddTransient(typeof(TEntityContext));
services.AddTransient(typeof(IRepository<>), typeof(GenericEntityRepository<>));
}
}
public class EntityBase
{
[Key]
public int Id { get; set; }
}
public interface IRepository<TEntity>
{
IEnumerable<TEntity> GetAll();
}
public class GenericEntityRepository<TEntity> : EntityRepositoryBase<DbContext, TEntity>
where TEntity : EntityBase, new()
{
public GenericEntityRepository() : base(null)
{ }
}
public abstract class EntityRepositoryBase<TContext, TEntity> : RepositoryBase<TContext>, IRepository<TEntity>
where TContext : DbContext where TEntity : EntityBase, new()
{
protected EntityRepositoryBase(TContext context) : base(context)
{ }
public virtual IEnumerable<TEntity> GetAll()
{
return this.Context.Set<TEntity>().AsEnumerable();
}
}
public abstract class RepositoryBase<TContext> where TContext : DbContext
{
protected TContext Context { get; private set; }
protected RepositoryBase( TContext context)
{
this.Context = context;
}
}
The Problem
Because you are passing a null here:
public GenericEntityRepository() : base(null) // <-- passing null
This goes to the parent EntityRepositoryBase
protected EntityRepositoryBase(TContext context) : base(context) // <--(1) context = null
Then goes to the parent RepositoryBase
protected TContext Context { get; private set; } // <--(4) context = null
protected RepositoryBase(TContext context) // <--(2) context = null
{
this.Context = context; // <--(3) context = null
}
So when you call
return this.Context.Set<TEntity>().AsEnumerable();
this.Context // <-- this is null
The Solution
You need to change your GenericEntityRepository from sending a null to sending the dbContext
public class GenericEntityRepository<TEntity> : EntityRepositoryBase<DbContext, TEntity>
where TEntity : EntityBase
{
public GenericEntityRepository(ApplicationDbContext dbContext) : base(dbContext) { }
}
Alternatively, you can do this too (preferred):
public class GenericEntityRepository<TContext, TEntity> : EntityRepositoryBase<TContext, TEntity>
where TContext: DbContext, new()
where TEntity : EntityBase
{
public GenericEntityRepository() : base(new TContext()) { }
}
Note: With the code you have provided, you could probably do away with a couple of these generic classes, since they serve no function other than just inheriting unnecessarily.
I have a simple generic repository.
In this Repository, I have a generic DBContext and thats means its not specific to my appliktion.
The generic DbContext are still able to access my DBcontexts Entites, such
Context.Set<TEntity>().Add(entity)
Do the method Set() here geting reference to my webapplikationens DBContext, or what is happening ?
Full Code:
public interface IRepository<TEntity> where TEntity : class
{
TEntity Get(int id);
void Add(TEntity entity);
}
...
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
protected readonly DbContext Context;
public Repository(DbContext context)
{
Context = context;
}
public TEntity Get(int id)
{
// Here we are working with a DbContext, not PlutoContext. So we don't have DbSets
// such as Courses or Authors, and we need to use the generic Set() method to access them.
//Returns a non-generic DbSet instance for access to entities of the given type in the context and the underlying store .
return Context.Set<TEntity>().Find(id);
}
public void Add(TEntity entity)
{
Context.Set<TEntity>().Add(entity);
}
}
.................................
public interface IAuthorRepository : IRepository<Author>
{
Author GetAuthorWithCourses(int id);
}
..
public class AuthorRepository : Repository<Author>, IAuthorRepository
{
public AuthorRepository(PlutoContext context) : base(context)
{
}
public Author GetAuthorWithCourses(int id)
{
return PlutoContext.Authors.Include(a => a.Courses).SingleOrDefault(a => a.Id == id);
}
public PlutoContext PlutoContext
{
get { return Context as PlutoContext; }
}
}
..
public interface IUnitOfWork : IDisposable
{
IAuthorRepository Authors { get; }
int Complete();
}
...
public class UnitOfWork : IUnitOfWork
{
private readonly PlutoContext _context;
public UnitOfWork(PlutoContext context)
{
_context = context;
Authors = new AuthorRepository(_context);
}
public IAuthorRepository Authors { get; private set; }
public int Complete()
{
return _context.SaveChanges();
}
public void Dispose()
{
_context.Dispose();
}
}
...
public class Repository<Entity> : IRepository<Entity> where Entity : class
{
protected readonly DbContext Context;
public Repository(DbContext context)
{
Context = context;
}
public void Add(Entity entity)
{
Context.Set<TEntity>().Add(entity);
}
}
The argument DbContext context is just a compile-time type, the actual type of the parameter isn't changed.
Please help me correct this code.
I am having a compiler error in the class
public interface IGenericSaveRepository
{
void Save<TEntity>(int id, ICollection<TEntity> entities) where TEntity : class;
}
public class GenericSaveRepository<TEntity> where TEntity : class,IGenericSaveRepository
{
private IUnitofWork<TEntity> _unitofWork;
private NaijaSchoolsContext _context;
public GenericSaveRepository(NaijaSchoolsContext context)
{
_context = context;
_unitofWork = new UnitofWork<TEntity>(_context);
}
public void Save(int id, ICollection<TEntity> entities)
{
foreach (var entity1 in entities)
{
//entity.Insert(entity1);
_unitofWork.Entity.Insert(entity1);
}
}
}
public class RatingRepo : GenericRepository<Rating>
{
private IGenericSaveRepository gen;
private readonly NaijaSchoolsContext _context;
public RatingRepo(NaijaSchoolsContext context)
: base(context)
{
_context = context;
}
public void Save(School school,Rating rating)
{
List<Rating> ratings = new List<Rating>();
ratings.Add(rating);
gen = new GenericSaveRepository<Rating>(_context);
gen.Save(23, ratings);
}
}
This line gen = new GenericSaveRepository<Rating>(_context); doesn't allow me to have Rating specified as a concrete type.
How can I do this?
Thanks for the help.
This should remove your compile error .. see gen implementation if you don't want Rating must implement IGenericSaveRepository
public class RatingRepo : GenericRepository<Rating>
{
private GenericSaveRepository<Rating> gen;
private readonly NaijaSchoolsContext _context;
public RatingRepo(NaijaSchoolsContext context)
: base(context)
{
_context = context;
}
public void Save(School school, Rating rating)
{
List<Rating> ratings = new List<Rating>();
ratings.Add(rating);
gen = new GenericSaveRepository<Rating>(_context);
gen.Save(23, ratings);
}
}
Update : Complete implementation
public interface IGenericSaveRepository
{
void Save<TEntity>(int id, ICollection<TEntity> entities) where TEntity : class;
}
public class GenericSaveRepository<TEntity> where TEntity : class
{
private UnitofWork<TEntity> _unitofWork;
private NaijaSchoolsContext _context;
public GenericSaveRepository(NaijaSchoolsContext context)
{
_context = context;
_unitofWork = new UnitofWork<TEntity>(_context);
}
public void Save(int id, ICollection<TEntity> entities)
{
foreach (var entity1 in entities)
{
}
}
}
public class UnitofWork<T>
{
public UnitofWork(NaijaSchoolsContext context)
{
throw new NotImplementedException();
}
}
internal interface IUnitofWork<T>
{
}
public class NaijaSchoolsContext
{
}
public class GenericRepository<T>
{
protected GenericRepository(NaijaSchoolsContext context)
{
throw new NotImplementedException();
}
}
public class Rating
{
}
public class School
{
}
Rating must implement IGenericSaveRepository in order for that to compile.
public class GenericSaveRepository<TEntity> where TEntity : class,IGenericSaveRepository