I have a simple DbContext looking like:
public class MyDbContext : DbContext
{
private readonly IUserContext _userContext;
public MyDbContext(IUserContext userContext) : base("DefaultConnectionString")
{
_userContext = userContext;
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDbContext, Configuration>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// ... Here I need to creates some filters using the IUserContext dependency
base.OnModelCreating(modelBuilder);
}
}
This DbContext is wired using Func<T> factory, using the guidelines in the Simple Injector documentation: container.RegisterFuncFactory<DbContext, MyDbContext>(Lifestyle.Scoped);
public static void RegisterFuncFactory<TService, TImpl>(
this Container container, Lifestyle lifestyle = null)
where TService : class
where TImpl : class, TService
{
lifestyle = lifestyle ?? Lifestyle.Transient;
var producer = lifestyle.CreateProducer<TService, TImpl>(container);
container.RegisterSingleton<Func<TService>>(producer.GetInstance);
}
But apperently, such simple case is not possible with the DbContext because of this message:
The target context 'MyDbContext' is not constructible. Add a default
constructor or provide an implementation of IDbContextFactory.
I dont really like the idea of the IDbContextFactory, so the only solution I can come up with is to remove the dependency on the MyDbContext, set it as a property, modify the RegisterFuncFactory method and manually initialize the context:
internal static void RegisterFuncFactory<TService, TImpl>(this Container container, Func<TImpl> instanceProducer, Lifestyle lifestyle = null) where TService : class where TImpl : class, TService
{
lifestyle = lifestyle ?? Lifestyle.Transient;
var producer = lifestyle.CreateProducer<TService>(instanceProducer, container);
container.Register<Func<TService>>(() => producer.GetInstance, Lifestyle.Singleton);
}
container.RegisterFuncFactory<DbContext, MyDbContext>(() => new MyDbContext
{
UserContext = container.GetInstance<IUserContext>()
}, Lifestyle.Scoped);
While not elegant it works, but is there another and "better" way of doing what I need? I like the explicitly of the dependency on the context, but seem not possible.
UPDATE
The error is coming from:
'System.Data.Entity.Migrations.Infrastructure.MigrationsException'
occurred in EntityFramework.dll but was not handled in user code
On this code the return statement of the Query method here:
internal sealed class EntityFrameworkRepository<TEntity> : IEntityWriter<TEntity>, IEntityReader<TEntity> where TEntity : Entity
{
private readonly Func<DbContext> _contextProvider;
public EntityFrameworkRepository(Func<DbContext> contextProvider)
{
_contextProvider = contextProvider;
}
public IQueryable<TEntity> Query()
{
var context = _contextProvider();
return context.Set<TEntity>().AsNoTracking();
}
// Methods removed for brevity
}
Add a second (default) constructor. This way EF migrations can use this constructor when run from the command line, while you can let your application use the second constructor.
You loose Simple Injector's auto-wiring capabilities on your DbContext when you add this second constructor, but this shouldn't be a problem; you can simply wire your context as follows:
IUserContext userContext = new AspNetUserContext();
container.RegisterSingleton<IUserContext>(userContext);
var contextProducer = Lifestyle.Scoped.CreateProducer<DbContext>(
() => new MyDbContext(userContext),
container);
container.RegisterSingleton<Func<DbContext>>(contextProducer.GetInstance);
This answer is only to display for further users what I ended up with. #Steven answer is the right answer.
In order to be able to inject dependencies into the the DbContext while supporting migrations, we have to use two constructors. One for the migrations and one for the application.
public class MyDbContext : DbContext
{
private readonly IUserContext _userContext;
// For migrations
public MyDbContext() : base("DefaultConnectionString")
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDbContext, Configuration>());
}
// For applications
public MyDbContext(IUserContext userContext) : base("DefaultConnectionString")
{
_userContext = userContext;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
// ... Code removed for brevity
base.OnModelCreating(modelBuilder);
}
}
This is then wired in the composition root like:
public static void RegisterEntityFramework<TContext>(this Container container, Func<TContext> context) where TContext : DbContext
{
if (container == null) throw new ArgumentNullException(nameof(container));
var contextProducer = Lifestyle.Scoped.CreateProducer<DbContext>(context, container);
container.RegisterSingleton<Func<DbContext>>(() => contextProducer.GetInstance);
}
var userContext = new AspNetHttpUserContext();
var container = new Container();
container.Options.DefaultScopedLifestyle = new WebApiRequestLifestyle();
container.RegisterSingleton<IUserContext>(userContext);
container.RegisterEntityFramework(() => new WayFinderDbContext(userContext));
container.Verify();
Related
I want to create an ASP.Net Core web service which will select rows from one SQL Server database and insert them into X SQL server database(s). All databases have the same structure (same model).
I don't want to inject DbContext because I don't know how many context I will have to use and it will be difficult to maintain.
Is it possible to manually create a DbContext in a controller or a manager like :
MyContextClass dbContext = new MyContextClass("myConnectionString");
Thank you
Yes, it is possible to just create a new DbContext.
However when using DI, you should write and inject something like a DbContextFactory class that you can use to create a new context and that itself gets the DbContextOptions from your configuration.
public class ContextFactory<TContext>
where TContext : DbContext
{
private readonly Func<TContext> _createContext;
public ContextFactory(Func<TContext> createContext)
{
_createContext = createContext ?? throw new ArgumentNullException(nameof(createContext));
}
TContext CreateForRead()
{
var context = Create();
context.ChangeTracker.AutoDetectChangesEnabled = false;
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
return context;
}
TContext CreateForWrite() => Create();
private TContext Create()
{
var context = _createContext();
if (context == null)
throw new NullReferenceException($"{nameof(_createContext)} must not return null.");
return context;
}
}
For easier use, create an extension class:
public static class ServiceCollectionDataExtensions
{
public static void AddDatabase<TDbContext>(this IServiceCollection services, string connectionString)
where TDbContext : DbContext
{
if (services == null)
throw new ArgumentNullException(nameof(services));
if (string.IsNullOrEmpty(connectionString))
throw new ArgumentNullException(nameof(connectionString));
services.AddDbContext<TDbContext>(c => c.UseSqlServer(connectionString), ServiceLifetime.Transient);
services.AddScoped(provider => new ContextFactory<TDbContext>(() => ActivatorUtilities.CreateInstance<TDbContext>(provider, provider.GetRequiredService<DbContextOptions<TDbContext>>())));
}
}
And then inside your public void ConfigureServices(IServiceCollection services) add your connection string from your configuration:
services.AddDatabase<MyDbContext>(Configuration.GetConnectionString("MyDatabase"));
I have an .net core web api application where I'm using entity framework core with service layer, unit of work and repository layer pattern. For DI I'm using Autofac.
The application has multiple clients and each client has its own database and the schema for all these databases is same. With each API call I'll get the client specific connection string, using which I have to create a DbContext and use it for all its operations.
On Startup class I have registered my dbcontext ClientDbContext and all other classes. When the unit-of-work class is called I am creating my new DbContext based on the connection string. I want the repository to use this instance, but the repository is still using the initial ClientDbContext instance which was created at startup.
How can I make the repository use the new DbContext instance?
Unit of Work:
public class UnitOfWork : IUnitOfWork
{
public ClientDbContext ClientDbContext { get; private set; }
public UnitOfWork ()
{
}
public void SetDbContext(string connectionString)
{
if(ClientDbContext == null)
{
//creating new db context instance here
ClientDbContext = MembershipRepository.CreateDbContext(connectionString);
}
}
//property injection
public IGenericRepository<SomeEntity, ClientDbContext> SomeEntityGenericRepository { get; }
}
Generic Repository:
public class GenericRepository<TEntity, TDbContext> : IGenericRepository<TEntity, TDbContext> where TEntity : class
where TDbContext : DbContext
{
private readonly TDbContext _context;
private readonly DbSet<TEntity> _dbset;
public GenericRepository(TDbContext context)
{
// need to get updated context here, but getting the initial one
_context = context;
_dbset = _context.Set<TEntity>();
}
}
Autofac module called in Startup.cs:
builder.Register(a => new ClientDbContext()).InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(GenericRepository<,>)).As(typeof(IGenericRepository<,>)).InstancePerLifetimeScope();
//Register Unit of Work here
builder.RegisterType<UnitOfWork>().As<IUnitOfWork>().InstancePerLifetimeScope().PropertiesAutowired();
//Register Services here
builder.RegisterType<SomeService>().As<ISomeService>().InstancePerLifetimeScope();
Can anyone please help me out on how to achieve the above requirement?
Is there any way I can make Autofac use my new created dbcontext object?
Instead of
builder.Register(a => new ClientDbContext()).InstancePerLifetimeScope();
you could use
builder.Register(c => c.Resolve<IUnitOfWork>().ClientDbContext)
.InstancePerLifetimeScope();
By the way I'm not sure what is the responsibility of your IUnitOfWork. Another way of doing this would be to have a class that would provide information about the current user :
public interface IClientContext
{
public String ClientIdentifier { get; }
}
Then a DbContextFactory that would create the DbContext based on the IClientContext
public interface IDbContextFactory
{
IDbContext CreateDbContext();
}
public class DbContextFactory
{
public DbContextFactory(IClientContext clientContext)
{
this._clientContext = clientContext;
}
private readonly IClientContext _clientContext;
public IDbContext CreateDbContext()
{
// get the connectionstring from IClientContext and return the IDbContext
}
}
The concrete implementation of IClientContext depends on the way you can get this information, it could be from current HttpContext or any other way it's up to you.
It seems that at some point you call SetDbContext you can keep this way by creating a XXXClientContextProvider where XXX is relative to the way you get this information.
public class XXXClientContextProvider
{
private IClientContext _clientContext;
public IClientContext GetClientContext()
{
if(this._clientContext == null)
{
throw new Exception("client context is null. You should do X or Y");
}
return this._clientContext;
}
public void SetClientContext(String clientId)
{
if(this._clientContext != null)
{
throw new Exception("client context has already been set");
}
this._clientContext = new StaticClientContext(clientId);
}
}
and then register everything like this :
builder.Register(c => c.Resolve<IClientContextProvider>().GetClientContext())
.As<IClientContext>()
.InstancePerLifetime();
builder.Register(c => c.Resolve<IDbContextFactory>().CreateDbContext())
.As<IDbContext>()
.InstancePerLifetime();
I have a situation where I need to instantiate my DBContext after my solution has started up. I asked this question which indicated that I could do this with a constructor argument.
It was suggested that I implement as an example this:
var connection = #"Server=(localdb)\mssqllocaldb;Database=JobsLedgerDB;Trusted_Connection=True;ConnectRetryCount=0";
var optionsBuilder = new DbContextOptionsBuilder<BloggingContext>();
optionsBuilder.UseSqlServer(connection);
using (var context = new BloggingContext(optionsBuilder.Options))
{
// do stuff
}
However I have implemented the repository pattern (for better or worst) and given my changed circumstances - not having a connection string until after the solution has run startup - I need to implement this into the base repository class and I am at a bit of a loss..
Currently I have this:
public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IEntityBase, new()
{
public JobsLedgerAPIContext _context;
#region Properties
public EntityBaseRepository(JobsLedgerAPIContext context)
{
_context = context;
}
#endregion
public virtual IQueryable<T> GetAll()
{
return _context.Set<T>().AsQueryable();
}
public virtual int Count()
{
return _context.Set<T>().Count();
}
......
How do I implement this change both instantiating the DBContext in the constructor (there by bypassing the need to add the context as a service in startup) and then with the wrapping each of the virtual methods with "using" etc
EDIT.. Camilo indicated I had not identified when I have the database name.
The basic situation is that the system starts up (This is an Aurelia SPA project which is irrelevant to this issue) sends the package to the browser which shows a login screen. User logs in.. User is verified via a JWT controller.. Once verified in the controller (using a catalog database that has one table with 3 fields - username, password, database name) I use the database name to create a connection string and then instantiate my DBContext at that point.. so via a constructor.
The answers below need to be modified as the one with the factory answer (promising) has errors as discovered by this question.. Nkosi responded with an great answer to the error.
EDIT 2..
This is a response to the edited question below:
Here is my original Client Repository with :base(context) on the constructor.
using JobsLedger.DATA.Abstract;
using JobsLedger.MODEL.Entities;
namespace JobsLedger.DATA.Repositories
{
public class ClientRepository : EntityBaseRepository<Client>, IClientRepository
{
private new JobsLedgerAPIContext _context;
public ClientRepository(JobsLedgerAPIContext context) : base(context)
{
_context = context;
}
public void RelatedSuburbEntities(Suburb _suburb)
{
_context.Entry(_suburb).Reference<State>(a => a.State).Load();
}
}
}
It has a reference to the base class "context". I am not sure how to modify this given that I believe I still need that ":base(context)" at the end. As well, I have a method in this that accesses _context as well which is part of the constructor...
Further I assume that I can no longer inject the service into the controller but instead new it up once I have secured the connection string and then pass that connection string to service.
Also, Given I have now added a singleton on the startup do I need to remove the original entry? :
services.AddDbContext<JobsLedgerAPIContext>(options => options.
UseSqlServer(Configuration.GetConnectionString("DefaultConnection"), b => b.MigrationsAssembly("JobsLedger.API")));
effectively replacing it with my singleton reference as per below:
services.AddSingleton(typeof(IContextFactory<>), typeof(ContextFactory<>));
Edited
The answer has been edited to rectify the mistake spotted and
fixed by Nkosi. Thanks, #Nkosi.
Implement a factory pattern. You can create a factory, call it ContextFactory as below:
First, define the interface. Further modified, removed the connectionString parameter
public interface IContextFactory<T> where T : DbContext
{
T CreateDbContext();
}
Create a factory class that implements this interface (edited as per Nkosi answer). Further modified to inject IHttpContextAccessor
public class ContextFactory<T> : IContextFactory<T> where T : DbContext
{
private readonly HttpContext _httpContext;
public ContextFactory(IHttpContextAccessor contextAccessor)
{
_httpContext = contextAccessor.HttpContext;
}
public T CreateDbContext()
{
// retreive the connectionString from the _httpContext.Items
// this is saved in the controller action method
var connectionString = (string)_httpContext.Items["connection-string"];
var optionsBuilder = new DbContextOptionsBuilder<T>();
optionsBuilder.UseSqlServer(connectionString);
return (T)Activator.CreateInstance(typeof(T), optionsBuilder.Options);
}
}
Then modify your base repository and make the JobsLedgerAPIContext protected. This context is going to be set by the derived class. Further modified to remove the constructor. It will use the parameterless constructor.
public class EntityBaseRepository<T> : IEntityBaseRepository<T> where T : class, IEntityBase, new()
{
protected JobsLedgerApiContext Context { get; set; }
public virtual IQueryable<T> GetAll()
{
return Context.Set<T>().AsQueryable();
}
public virtual int Count()
{
return Context.Set<T>().Count();
}
}
Change your derived class to use IContextFactory. Further modified to use the _contextFactory.CreateDbContext() parameter less method
The IClientRepository should have SetContext method defined.
public class ClientRepository : EntityBaseRepository<Client>, IClientRepository
{
private readonly IContextFactory<JobsLedgerApiContext> _contextFactory;
public ClientRepository(IContextFactory<JobsLedgerApiContext> factory)
{
_contextFactory = factory;
}
// this method will set the protected Context property using the context
// created by the factory
public void SetContext()
{
Context = _contextFactory.CreateDbContext();
}
public void RelatedSuburbEntities(Suburb suburb)
{
Context.Entry(suburb).Reference<State>(a => a.State).Load();
}
}
In the controller, that receives IClientRepository instance, you can set the connection in the HttpContext.Items, which will be valid for the request. This value will then be retrieved by the ContextFactory using IHttpContextAccessor. Then you simply call the _repository.SetContext(); method on the repository.
public class HomeController : Controller
{
private readonly IClientRepository _repository;
public HomeController(IClientRepository repository)
{
_repository = repository;
}
public IActionResult Index()
{
// save the connectionString in the HttpContext.Items
HttpContext.Items["connection-string"] = "test-connection";
// set the context
_repository.SetContext();
return View();
}
}
Make sure you register the IContextFactory in ConfigureServices as open generics and Singleton as below, also register the HttpContextAccessor and IClientRepository
services.AddHttpContextAccessor();
services.AddSingleton(typeof(IContextFactory<>), typeof(ContextFactory<>));
services.AddTransient<IClientRepository, ClientRepository>();
You may define your JobsLedgerAPIContext like this:
public class JobsLedgerAPIContext : DbContext
{
// public DbSet<Job> Jobs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=dotnetcore;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// may need to reflect entity classes and register them here.
base.OnModelCreating(modelBuilder);
}
}
I have a custom SiteRole class that inherits RoleProvider. I inject the IUserService in the constructor in order to do a query to get all Roles.
It kept on throwing an error that the SiteRole class needed a parameterless constructor. So I ended up injecting the IUserService like this:
public SiteRole()
{
_userService = DependencyResolver.Current.GetService<IUserService>();
}
Inside the class I override the GetRolesForUser function
public override string[] GetRolesForUser(string nickname)
{
return new string[] { _userService.GetRoleForUser(nickname) };
}
The UserService calls this LINQ query in the UserRepository (This line throws System.InvalidOperationException: 'The operation cannot be completed because the DbContext has been disposed.')
public string GetRoleForUser(string nickname)
{
return DbContext.Users.Where(u => u.Nickname == nickname).FirstOrDefault().Role.Name;
}
Any hints on how I can resolve this? You cannot inject dependencies to RoleProvider via constructor so I have to use DependencyResolver.Current.GetService().
Looks like your SiteRole class has a longer lifetime than the injected IUserService. You should check if it's really the source of issue and tune lifetimes of registered services with use of your DI container API. Here are some links for different containers: Autofac, NInject, Unity, Simple injector.
And, probably, change your SiteRole type a bit — use a property to get alive UserService instead of field, with which the only instance is created at time of SiteRole instantiation.
public SiteRole()
{
// this field should be removed
// _userService = DependencyResolver.Current.GetService<IUserService>();
}
// this property should be used instead of field
private IUserService UserService
{
get { DependencyResolver.Current.GetService<IUserService>(); }
}
Or in modern syntax
private IUserService UserService => DependencyResolver.Current.GetService<IUserService>();
But be careful and check that you won't get uncontrolled count of UserService instances created, if it's for example configured as instance per call in your DI container.
I solved the problem by doing this:
virtual Owned<IUserService> ResolveUserService()
=> DependencyResolver.Current.GetService<Owned<IUserService>>();
And call the service in the GetRolesForUser Method with a Using statement:
public override string[] GetRolesForUser(string nickname)
{
using(var userService = ResolveUserService())
{
return new string[] {userService.Value.GetRoleForUser(nickname) };
}
}
On a sidenote, DbContext is constructed in the RepositoryBase. I'm using the Generic Repository Pattern for my project.
public abstract class RepositoryBase<T> where T : class
{
#region properties
private StoreEntities dataContext;
private readonly IDbSet<T> dbSet;
protected IDbFactory DbFactory
{
get;
private set;
}
protected StoreEntities DbContext
{
get { return dataContext ?? (dataContext = DbFactory.Init()); }
}
#endregion
protected RepositoryBase(IDbFactory dbFactory)
{
DbFactory = dbFactory;
dbSet = DbContext.Set<T>();
}
#region Implementation of defaults
public virtual void Add(T entity)...........
Thanks for the help!
Registering DbContext in ASP.NET MVC Application as InstancePerRequest. (IoC Autofac)
builder.RegisterType<ADbContext>().As<IADbContext>().InstancePerRequest();
Using inside BService
public class BService : IBService
{
readonly IADbContext _dbContext;
public BService(IADbContext dbContext)
{
_dbContext = dbContext;
}
}
Trying to register IBService as Singleton.
builder.RegisterType<BService>().As<IBService>().SingleInstance();
Obviously, this gives me an error
No scope with a tag matching 'AutofacWebRequest' is visible from the scope in which the instance was requested.
Simplest solution is to register IBService as InstancePerRequest, but there is no reason having PerRequest IBService rather than error message mentioned above.
How can i use PerRequest DbContext inside Singleton service ?
First attempt, you can inject IContainer into BService. But this will look like Service locator pattern, which is not good. Otherwise, you can define factory interface
public interface IFactory<T>
{
T GetInstance();
}
Then implement it and register
public class SimpleFactory<T> : IFactory<T>
{
private IContainer _container;
public SimpleFactory(IContainer container)
{
_container = container;
}
public T GetInstance()
{
return _container.Resolve<T>();
}
}
public class DbContextFactory : SimpleFactory<IADbContext>
{
public DbContextFactory(IContainer container):base(container)
{
}
}
Finally, use this factory in your singletone
public class BService : IBService
{
IADbContext _dbContext => _dbContextFactory.GetInstance();
IFactory<IADbContext> _dbContextFactory
public BService(IFactory<IADbContext> dbContextFactory)
{
_dbContextFactory = dbContextFactory;
}
}
Each time, when you want to acess to context inside singletone, it will pass this request to IoC container, which able to return context per request.