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; }
}
Related
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.
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 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'm working in a project using EntityFramework Core and .net core 2.0, which I need to connect to multiple databases to get data to execute a cron, I injected DbContext.cs in startup.cs like above:
services.AddDbContext<DbContext.cs>();
I use it in my UnitOfWork.cs like this:
public class UnitOfWork<Context> : IUnitOfWork where Context : DbContext
{
public DbContext _context { get; set; }
public DbContext getContext()
{
return _context;
}
public UnitOfWork(DbContext context)
{
_context = context;
}
}
which is also managed by dependency injection.
My question is, is it possible to work with a one injected instance of dbcontext, and change the connection at runtime in need? I really didn't find a clear solution for that. I tried to use a setter and to instantiate a new dbContext every time I need to connect to a new database, but it doesn't seem so beautiful:
public void SetContext(DbContext context)
{
_context = context;
}
public DbContext _context { get; set; }
is it possible to work with a one injected instance of dbcontext, and change the connection at runtime in need,
No. If your Unit Of Work needs to connect to multiple databases, you need multiple DbContext instances.
I have some problem with DI (Dependancy Injection). My project is on netcore 2.0 and has few layers (standard N-Tier Architecture). I'm trying to move EF Core 2 from Presentation Layer to Data Access Layer and I have created the following classes in DAL:
namespace MyProject.Infrastructure.Implementation.MySql.Contexts
{
public class ApplicationDbContext : DbContext
{
private readonly IConfiguration _configuration;
public ApplicationDbContext(IConfiguration configuration)
{
_configuration = configuration;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseMySql(
Configuration.GetConnectionString("MySql")
);
}
public DbSet<Test> Test { get; set; }
}
}
Then I prepared base class for all DAL engines:
namespace MyProject.Infrastructure.Implementation.MySql
{
public class BaseEngine : IBaseEngine
{
private readonly ApplicationDbContext _context;
protected ApplicationDbContext Db => _context;
public BaseEngine(ApplicationDbContext context)
{
_context = context;
}
}
}
So, my common engine should look like this:
namespace MyProject.Infrastructure.Implementation.MySql
{
public class TestEngine : BaseEngine
{
public List<Test> GetTestList()
{
return Db.Test.ToList();
}
}
}
The problem is that I get error, BaseEngine needs parameter to be passed in constructor and I don't want to create all instances manually, I need somehow use Dependancy Injection that automatically creates instances of ApplicationDbContext and IConfiguration when BaseEngine and ApplicationDbContext will be created..
Any ideas?
Create a public interface for ApplicationDbContext, like IApplicationDbContext. Put that in the constructor for BaseEngine instead of the concrete class. Make the BaseEngine constructor protected. The BaseEngine constructor should look like:
protected BaseEngine(IApplicationDbContext context)
Then, since TestEngine is derived from BaseEngine, and BaseEngine requires a constructor argument, you have to pass that in from the TestEngine constructor like:
public TestEngine(IApplicationDbContext context) : base(context)