I have a base library which includes some basic entities (logs, settings, ...) in a DbContext class. In my specific project I am inheriting the Context from this class and do the project specific stuff.
Base Context
public class BaseContext : DbContext {
public BaseContext(DbContextOptions<BaseContext> options)
: base(options)
{
}
}
Project Specific Context
public class ProjectContext: BaseContext {
public ProjectContext(DbContextOptions<BaseContext> options)
: base(options)
{
}
public ProjectContext(DbContextOptions options)
: base(options)
{
}
}
Context will be added in Startup:
services.AddDbContext<Context>(options =>
{
if (Helpers.IsDevelopment())
{
options.EnableSensitiveDataLogging();
options.EnableDetailedErrors();
}
options.UseNpgsql(Configuration.GetConnectionString("Context"), b =>
{
b.MigrationsAssembly("App.Project.Specific");
b.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
b.EnableRetryOnFailure(5);
});
}
);
So some services and controlers implemented in the base library work with BaseContext, which is a child of Context.
public AssetsService(BaseContext db,...
So this results in the following error message:
Unable to resolve service for type 'App.Shared.DataModel.BaseContext' while attempting to activate 'App.Shared.Services.AssetsService
So how to inject the contexts correctly to get access from both ways?
A DbContext is a multi-entity Repository and Unit-of-Work. It makes little sense to have a "base" repository and no sense to register the base instead of concrete repositories.
Furthermore, the DI container can't guess by itself which concrete service to create when only a base class is registered. .NET can't guess which specific Repository classes like AssetsService or CustomersService want. If both SalesContext and MarketingContext inherit from some base Context class, why not send MarketingContext to CustomersService and SalesContext to WarehouseService?
DI registration
For starters, all the DbContext types that are going to be used need to be registered. If a service expects a BaseContext, then BaseContext should be registered. Same with ProjectContext, TimeSheetContext, CustomerContext etc. Registering a base class doesn't register its derived classes.
Common registration and configuration
It seems that the real question is how to register multiple DbContexts using the same code. This can be done with an extension method that accepts a DbContext type parameter and calls AddDbContext :
public static IServiceCollection AddMyContext<T>(
this IServiceCollection services,
IConfiguration configuration) where T:DbContext
{
services.AddDbContext<T>(options =>
{
if (Helpers.IsDevelopment())
{
options.EnableSensitiveDataLogging();
options.EnableDetailedErrors();
}
options.UseNpgsql(configuration.GetConnectionString("Context"), b =>
{
b.MigrationsAssembly("App.Project.Specific");
b.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);
b.EnableRetryOnFailure(5);
});
}
);
return services;
}
In the application's startup, call AddMyContext instead of AddDbContext.
Related
In the ValuesController, I'm calling method GetUsers of a class TestRepo to get list of all the users from the database. To do that I had to pass in the _context to TestRepo() like
var testRepo = new TestRepo(_context);
return testRepo.GetUsers();
How do I avoid passing _context to class TestRepo. Ideally, I want _context to be available to me in TestRepo and I don't want to pass _context in TestRepo. What is the right way of doing this?
ValuesController.cs
public class ValuesController : ControllerBase
{
private readonly ApplicationDBContext _context;
public ValuesController(ApplicationDBContext context)
{
_context = context;
}
// GET: api/<ValuesController>
[HttpGet]
public IEnumerable<User> Get()
{
var testRepo = new TestRepo(_context);
return testRepo.GetUsers();
}
}
TestRepo.cs
public class TestRepo
{
private readonly ApplicationDBContext _context;
public TestRepo(ApplicationDBContext context)
{
_context = context;
}
public IEnumerable<User> GetUsers()
{
return _context.Users.ToList(); ;
}
}
You can use dependency injection. .NET supports the dependency injection (DI) software design pattern, which is a technique for achieving Inversion of Control (IoC) between classes and their dependencies. Dependency injection in .NET is a built-in part of the framework, along with configuration, logging, and the options pattern.
A dependency is an object that another object depends on.
When you register your DbContext in the app you add it to the DI-container and then your controller can resolve a new instance of ApplicationDBContext for you. But you can add other dependencies to the DI-container and inject it as a dependency via constructor parameter.
For example in your case if you use Startup.cs you can add TestRepo as scoped dependency:
public void ConfigureServices(IServiceCollection services)
{
// you already has this 2 lines of code somewhere
services.AddDbContext<ApplicationDBContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddControllers();
// use AddScoped to add TestRepo to DI-container
services.AddScoped<TestRepo>();
}
or if you use modern style application builder use next syntax:
// you already has this 2 lines of code somewhere
builder.Services.AddDbContext<ApplicationDBContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddControllers();
// use AddScoped to add TestRepo to DI-container
builder.Services.AddScoped<TestRepo>();
After registering the DbContext and the TestRepo class in container, you can use the TestRepo in the ValuesController to get an instance TestRepo class with injected DbContext:
public class ValuesController : ControllerBase
{
private TestRepo _testRepo;
public ValuesController(TestRepo testRepo)
{
_testRepo = testRepo;
}
[HttpGet]
public IEnumerable<User> Get()
{
return _testRepo.GetUsers();
}
}
Also you can inject your dependency directly to the action method with FromServices attribute:
public IEnumerable<User> Get([FromServices] TestRepo testRepo)
{
return testRepo.GetUsers();
}
Note that dependency injection is related to interfaces and abstractions, so it is important to use it when you use DI. It's a part of SOLID principles. Interfaces provide a level of abstraction from the concrete implementation, making it easier to swap out implementations and allowing for greater flexibility and reusability and easier testing with mocks.
Finally understanding the life cycle of dependency injection is very important in ASP.Net (Core) applications. Transient objects are always different; a new instance is provided to every controller and every service. Scoped objects are the same within a request, but different across different requests. Singleton objects are the same for every object and every request. You can learn more in .net tutorial "Use dependency injection in .NET".
I have this piece of code in the startup.cs file of my ASP.Net Core Web API project:
public void ConfigureServices(IServiceCollection services)
{
// Retrieve the database connection string
string connectionString = "Do something to retrieve a connection string";
services.AddDbContext<MyContext>(options => options.UseSqlServer(connectionString));
}
I have reasons to do this here and not in MyContext's OnConfiguring() method, but now I am getting this error:
System.InvalidOperationException: 'No database provider has been
configured for this DbContext. A provider can be configured by
overriding the DbContext.OnConfiguring method or by using AddDbContext
on the application service provider. If AddDbContext is used, then
also ensure that your DbContext type accepts a
DbContextOptions object in its constructor and passes it to
the base constructor for DbContext.'
This is my MyContext class's:
public partial class MyContext : DbContext
{
public MyContext()
{
}
public MyContext(DbContextOptions<MyContext> options)
: base(options)
{
}
...
}
I found somewhere that I also need to do this:
public class MyController : Controller
{
private MyContext _context;
public MyController(MyContext context)
{
_context = context;
}
}
but this is very inconvenient since I am currently not instantiating MyContext in the controllers, but in a different layer, for example:
public ActionResult MyMethod(...)
{
MyManager.DoSomething(); // MyManager instantiates the context
return Ok();
}
This is how I'm currently instantiating the context:
private static readonly MyContext myContext = new MyContext();
I'm guessing I need to somehow inject the options into the context, but I don't know how.
Dependency injection works that way, by injecting via constructor (recommended approach which leads to easier to test code and ensure invariants). You should just use that.
but this is very inconvenient since I am currently not instantiating MyContext in the controllers, but in a different layer, for example:
This seems to be a wrong assumption on your side that injection only works in constructor.
Any service registered with the DI can have stuff injected into it, when resolved. So if you use your MyContext in a service class, inject it there and inject the service into your controller.
Mind the lifetimes though. AddDbContext adds the context with scoped life time, means it will get disposed at the end of the request. This is by design (can be override with one of the AddDbContext overloads), since EF Core is tracking changes in memory and unless disposed can lead to memory leakage and high memory consumption.
It seems that you are registering your context to services collection and it should work if your get your context from there.
But you're simply creating a new unconfigured DbContext instance and so you get an error that it's not configued:
private static readonly MyContext myContext = new MyContext();
Solution: Let the context be injected via DI: https://learn.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-3.1
public class MyServiceThatNeedsDbContext {
private readonly MyContext _myContext;
MyServiceThatNeedsDbContext(MyContext myContext) {
_myContext = myContext;
}
}
You'll have to register your service class to services collection and get instances from there to make it work.
A simple registration could look like this (Startup.cs):
services.AddTransient<IMyServiceThatNeedsDbContext, MyServiceThatNeedsDbContext>();
.. and in your controller
public class MyController : Controller
{
private IMyServiceThatNeedsDbContext _myService;
public MyController(IMyServiceThatNeedsDbContext myService)
{
_myService = myService;
}
}
I would suggest removing parametherless constructor because there is known issue where that's called instead of one with options in it.
Add in yourDbContext
// overload constructer
public MyContext(DbConfig dbconfig)
{
_dbconfig = dbconfig
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
if (!optionsBuilder.IsConfigured)
{
switch (_dbConfig.DbType)
{
case DbTypes.MsSql:
optionsBuilder.UseSqlServer(_dbConfig.ConnectionString);
break;
case DbTypes.PostgreSql:
optionsBuilder.UseNpgsql(_dbConfig.ConnectionString);
break;
case DbTypes....
.....
}
optionsBuilder.EnableSensitiveDataLogging();
}
}
and Create
public class DbConfig
{
public DbTypes DbType { get; set; }
public string ConnectionString { get; set; }
}
public enum DbTypes
{
MsSql,
PostgreSql,
.....,
....
}
then create global _dbConfig in MyManager and configure dbtype and constring
and then
private yourDbContext GetNewDbContext()
{
return new yourDbContext(_dbConfig);
}
inject in MyManager
Try Unity containers to access your instance when your controller is called.
https://www.tutorialsteacher.com/ioc/unity-container
The context will be provided automatically through dependency injection after registering from your UnityConfig. (The nuget puts in the code.)
public static void Register(HttpConfiguration httpConfiguration)
{
httpConfiguration.DependencyResolver = new UnityDependencyResolver(Container);
}
I have started learning project in .net core. It is SPA application.
I have created base repository in which i want to inintialize ApplicationDbContext which derives by ApiAuthorizationDbContext.
Database context intitialization somehow works by dependency injection but will not work for base repository class where injected objects are by constructor. I had to inject context for every repository which in my point of view is useless. I'd prefer to instantiate such context manually.
My problem is that ApiAuthorizationDbContext require operationalStoreOptions. I can not find how to configure these options. In below code fragment there is constructor which i have to use.
public class ApplicationDbContext : ApiAuthorizationDbContext<ApplicationUser>
{
public ApplicationDbContext(
DbContextOptions options,
IOptions<OperationalStoreOptions> operationalStoreOptions) : base(options, operationalStoreOptions)
{
}
public DbSet<City> Cities { get; set; }
}
I have problem with set connection string in my DBContext class. I know I can inject IConfiguration in SqlLiteDbcontext construtor or use IOption pattern but in CRUD.cs I already use parameterless constructor. I'm looking for solution that doesn't require CRUD.cs modification.
public class SqliteDbContext : DbContext
{
public SqliteDbContext() : base()
{
}
public SqliteDbContext(DbContextOptions<SqliteDbContext> options) : base(options)
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlite("Data Source=App_Data/sqlite.db");
optionsBuilder.EnableSensitiveDataLogging(true);
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(IISDefaults.AuthenticationScheme);
services.AddMemoryCache();
services.AddMvc();
// Adds services required for using options.
//services.AddOptions();
services.Configure<MvcOptions>(options =>
{
});
services.AddDbContext<SqliteDbContext>(options =>
options.UseSqlite(Configuration.GetConnectionString("Sqlite")));
CRUD.cs
public partial class CRUD<T> where T : class, ICreatedDate, IId
{
private SqliteDbContext db;
private DbSet<T> DbSet => db.Set<T>();
public List<T> Read()
{
using (db = new SqliteDbContext())
{
return DbSet.ToList();
}
}
//...
You should not use Entity Framework like that within ASP.NET Core. You have dependency injection and a properly configured EF context, so you should utilize that. This essentially means:
Never create a database context manually using new. Always inject the context as a dependency.
Don’t override the OnConfiguring method in the database to configure the context. Configuration is expected to be passed as DbContextOptions, so that the context itself is not responsible of setting up the configuration.
Avoid an empty constructor for your database context to avoid misuse where the context stays unconfigured.
So your code should look like this:
public class SqliteDbContext : DbContext
{
public SqliteDbContext(DbContextOptions<SqliteDbContext> options) : base(options)
{ }
// define your database sets
public DbSet<Entity> Entities { get; set; }
}
public class CRUDService<T>
{
private readonly SqliteDbContext db;
CRUDService(SqliteDbContext database)
{
db = database;
}
public List<T> Read()
{
return db.Set<T>().ToList();
}
}
The SqliteDbContext will be automatically provided by the dependency injection container. You just need to inject the dependency for it to be resolved properly.
Btw. I would generally suggest you to avoid a (generic) repository pattern. The database context in Entity Framework is already the unit of work and each database set is already a repository. So you can just use that directly. There is little you gain from adding another abstraction on top of it, especially a generic one, as that will always limit you.
Also, you should rename your SqliteDbContext to something that actually describes what data the context manages. The context should not care about what underlying database provider is being used.
I have a scenario where in the StartUp.cs I need to setup EF with a DbContext like the following:
public void ConfigureServices(IServiceCollection services)
{
// Add framework services.
services.AddDbContext<TestProjDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddMvc();
}
However, my solution is split out into multiple layers.. For example: Api, Data, Domain, Services...
I am trying to make it so the Api project only references the Domain and Services projects. However, DbContext is messing me all up. TestProjDbContext sits within the Data project along with my repositories, migrations, unit of work class, etc..
In my Domain project I have a bunch of interfaces such as IUnitOfWork, ICustomerRepository, etc.. I also created an ITestProjDbContext interface in this project thinking I would somehow be able to pass it into the services.AddDbContext in StartUp.cs. However, this doesn't seem possible.
How can I keep my TestProjDbContext.cs file inside of my Data layer without the Api knowing about the Data layer and only the Domain layer? Or should the DbContext not be in the Data layer and just sit in the Domain layer without using an Interface?
Yes, it's possible provide an interface for your DbContext and inject it in your startup.cs and use the interface (ex: ITestDbContext ) in your Api Controllers.
Here's a walk-through that details this with code examples. I don't want to copy-paste and plagarize it, so I've laid out the steps for you with relevant code w.r.t your situation. Do read the original blog post to get a full understanding of how this works.
Jerrie Pelser - Resolve your DbContext as an interface using the ASP.NET 5 dependency injection framework
Step 1 - Declare your ITestDbContext interface and add your DbSets
public interface ITestDbContext
{
DbSet<Episode> Episodes { get; set; }
DbSet<ApplicationUser> Users { get; set; }
// ....
int SaveChanges();
Task<int> SaveChangesAsync(CancellationToken cancellationToken);
}
Step 2 - Implement the ITestDbContext as concrete class TestDbContext
public class TestDbContext : IdentityDbContext<ApplicationUser>, ITestDbContext
{
public virtual DbSet<Episode> Episodes { get; set; }
public ApplicationDbContext()
{
// ...
}
}
Step 3 - Setup the Dependency Injection on the Interface in the ConfigureServices method.
public void ConfigureServices(IServiceCollection services)
{
...
// Add EF services to the services container.
services.AddEntityFramework()
.AddSqlServer()
.AddDbContext<TestDbContext>(options =>
options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
// Register the service and implementation for the database context
services.AddScoped<ITestDbContext>(provider => provider.GetService<TestDbContext>());
// ...
}
The key line to note is the following
services.AddScoped<ITestDbContext>(provider => provider.GetService<TestDbContext>());
NOTE: Do read up on how the author Jerrie Pelser came to use this approach in his blog post.
Step 4 - Use the ITestDbContext Interface in your Api Controllers
public class EpisodesController : Controller
{
private readonly ITestDbContext dbContext;
public EpisodesController(ITestDbContext dbContext)
{
this.dbContext = dbContext;
}
...
}