Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Net core generic repository pattern how to inject DbContext without knowing its type at compile time?

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.

like image 607
David Noreña Avatar asked Dec 28 '17 17:12

David Noreña


1 Answers

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>();
like image 169
Nkosi Avatar answered Oct 07 '22 05:10

Nkosi