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.
Related
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.
I am trying to achieve a design in c# like below.
void Main()
{
var serviceCollection = new ServiceCollection();
serviceCollection.AddScoped(typeof(RedisRepository<>));
serviceCollection.AddScoped(typeof(CommitterBase<IDto>), typeof(ACommitter));
serviceCollection.AddScoped(typeof(CommitterBase<IDto>), typeof(BCommitter));
serviceCollection.AddScoped<Client>();
var services = serviceCollection.BuildServiceProvider();
var client = services.GetRequiredService<Client>();
client.Dump();
}
public class RedisRepository<T> where T: IDto
{
public void Add(T dto)
{
Console.WriteLine("Added data");
}
}
public interface IDto
{
}
public class ADto: IDto
{
}
public class BDto : IDto
{
}
and :
public abstract class CommitterBase<T> where T: IDto
{
public CommitterBase(RedisRepository<T> repo)
{ }
public void Commit()
{
var dto = GenerateDto();
//do something with dto here
}
protected abstract T GenerateDto();
}
and its implementations:
public class ACommitter : CommitterBase<ADto>
{
public ACommitter(RedisRepository<ADto> repo): base(repo)
{ }
protected override ADto GenerateDto()
{
return new ADto();
}
}
public class BCommitter : CommitterBase<BDto>
{
public BCommitter(RedisRepository<BDto> repo) : base(repo)
{
}
protected override BDto GenerateDto()
{
return new BDto();
}
}
public class Client
{
public Client(IEnumerable<CommitterBase<IDto>> committers)
{ }
}
error that I get
Implementation type 'BCommitter' can't be converted to
service type 'UserQuery+CommitterBase`1[IDto]'
I understand from this stackoverflow post that this error is expected. Just wondering how to achieve similar effect without encountering the error. My aim is to extract reusable code into an Abstract Base Class and let the implementations do bare minimum.
Thanks in advance!
Interface cannot be instantiated and IDto is interface. So you can register specific implementation to your interface.
I little bit refactored code to use generic parameters.
This is yor base abstract class:
public abstract class CommitterBase<T> where T : IDto
{
public CommitterBase(RedisRepository<T> repo)
{ }
public void Commit()
{
var dto = GenerateDto();
//do something with dto here
}
protected abstract T GenerateDto();
}
And its concrete implementations such as ACommitter:
public class ACommitter<T> : CommitterBase<T> where T : IDto, new()
{
public ACommitter(RedisRepository<T> repo) : base(repo)
{ }
protected override T GenerateDto()
{
return new T();
}
}
and BCommitter:
public class BCommitter<T> : CommitterBase<T> where T: IDto, new()
{
public T FooBar { get; set; }
public BCommitter(RedisRepository<T> repo) : base(repo)
{
}
protected override T GenerateDto()
{
return new T();
}
}
and RedisRepository:
public class RedisRepository<T> where T : IDto
{
public void Add(T dto)
{
Console.WriteLine("Added data");
}
}
and Client class:
public class Client<T> where T : IDto, new()
{
public CommitterBase<T> CommitterBaseProperty { get; set; }
public Client(CommitterBase<T> committer) // if you want all instances of committers,
// then you need to create a factory
// and inject it through DI
{
CommitterBaseProperty = committer;
}
}
And you can call it like this:
static void Main(string[] args)
{
ServiceCollection serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<RedisRepository<ADto>>();
serviceCollection.AddScoped<RedisRepository<BDto>>();
serviceCollection.AddScoped<CommitterBase<ADto>, ACommitter<ADto>>();
serviceCollection.AddScoped<CommitterBase<BDto>, BCommitter<BDto>>();
serviceCollection.AddScoped<Client<ADto>>();
ServiceProvider serviceProvider = serviceCollection.BuildServiceProvider();
CommitterBase<ADto> committerBase = serviceProvider.GetRequiredService<CommitterBase<ADto>>();
CommitterBase<BDto> committerBase_B =
serviceProvider.GetRequiredService<CommitterBase<BDto>>();
committerBase.Commit();
Client<ADto> client = serviceProvider.GetRequiredService<Client<ADto>>();
}
I am trying to create DBContect object by passing connection string at run time.
Following is the structure of my NiNject Repository implementation.
public class HomeController : ApiController
{
MyService _service{ get; set; }
public HomeController(MyService service)
{
_service= service;
}
}
public class MyService
{
IRepository _repo { get; set; }
public MyService(IRepository repo)
{
_repo = repo;
}
}
Repository implementation is as follows :
public interface IRepository
{
TenantDbContext _db { get; set; }
void Add<T>(T entity) where T : class;
void Delete<T>(int id) where T : class;
T Find<T>(int id) where T : class;
IQueryable<T> Query<T>() where T : class;
void SaveChanges();
MasterDbContext _db_master { get; set; }
void Add_Master<T>(T entity) where T : class;
void Delete_Master<T>(int id) where T : class;
T Find_Master<T>(int id) where T : class;
IQueryable<T> Query_Master<T>() where T : class;
void SaveChanges_Master();
}
public class Repository : IRepository
{
public TenantDbContext _db { get; set; }
public MasterDbContext _db_master { get; set; }
public Repository(TenantDbContext db)
{
_db = db;
}
public Repository(MasterDbContext db_master)
{
_db_master = db_master;
}
public IQueryable<T> Query<T>() where T : class
{
return _db.Set<T>().AsQueryable();
}
public IQueryable<T> Query_Master<T>() where T : class
{
return _db_master.Set<T>().AsQueryable();
}
//.....Rest of the implemetation
}
Here goes my TenantDBContext class which takes an argument as a database string. No default constructor
public class TenantDbContext : DbContext
{
public TenantDbContext(string connString)
: base(connString)
{
//Configuration.AutoDetectChangesEnabled = true;
//Configuration.LazyLoadingEnabled = false;
//Configuration.ProxyCreationEnabled = false; //change tracking
}
public static TenantDbContext Create(string DbString)
{
// Some logic to get the tenant database string.
// Presently i am just passing it hard coded as follows.
return new TenantDbContext(DbString);
}
}
public class MasterDbContext : IdentityDbContext<ApplicationUser>
{
public MasterDbContext() : base("MasterDBConnection", throwIfV1Schema: false)
{
// dbmigration.AutomaticMigrationsEnabled = true;
Configuration.ProxyCreationEnabled = false;
Configuration.LazyLoadingEnabled = false;
}
public static MasterDbContext Create()
{
return new MasterDbContext();
}
//public DbSet<ApplicationUser> ApplicationUsers { get; set; }
public DbSet<Tenant> Tenants { get; set; }
public DbSet<TenantUserMap> TenantUserMaps { get; set; } }
Finally, RegisterServices which i have in my NinjectWebCommons.cs looks as follows :
Each Tenant have its different database. We are fetching out the Tenant name from the access token on every request and caching that requested Tenant object so we can pass the correct Tenant Database string in order to do the operations on the requested Tenant Database.
Below snippet, We are fetching the Tenant object from the current request cache which will provide us the Tenant Database string of the requested client.
public Tenant Tenant
{
get
{
object multiTenant;
if (!HttpContext.Current.GetOwinContext().Environment.TryGetValue("MultiTenant", out multiTenant))
{
throw new ApplicationException("Could Not Find Tenant");
}
return (Tenant)multiTenant;
}
}
private static void RegisterServices(IKernel kernel)
{
kernel.Bind<IRepository>().To<Repository>();
kernel.Bind<TenantDbContext>().ToMethod(_ =>
TenantDbContext.Create(Tenant.DBString));
kernel.Bind<MasterDbContext>().ToMethod(__ => MasterDbContext.Create());
}
Problem : When i add second binding in my NinjectWebCommons.cs "kernel.Bind()" , i start getting exception saying that "No default constructor found". It is simply not taking two bindings with the kernal. Request you to please have a look at the code above and point me out where i am going wrong.
I will appreciate your help. Thanks in Advance.
You may add a binding for the database context and point Ninject to use your factory method:
kernel.Bind<TenantDbContext>().ToMethod(_ => TenantDbContext.Create());
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