I have registered my DbContext with the standard container in ServiceStack, the DbContext is injected into the service but strangely the IDbSet property is null, all the other properties are as expected.
If I new up the DbContext in the Service constructor the IDbSet property is instantiated as you would expect, just to prove the config for EF has been setup correctly. All the other registered services are injected correctly without this strange behaviour this way too.
What am I missing?
public class AppHost : AppHostBase
{
public override void Configure(Container container)
{
container.AddScoped<IDbContext, MyDataContext>();
// Other registrations here omitted for brevity.
}
}
public interface IDbContext : IDisposable
{
IDbSet<MyEntity> MyEntity { get; set; }
DbChangeTracker ChangeTracker { get; }
DbContextConfiguration Configuration { get; }
Database Database { get; }
int SaveChanges();
Task<int> SaveChangesAsync();
Task<int> SaveChangesAsync(CancellationToken cancellationToken);
DbEntityEntry<T> Entry<T>(T entity) where T : class;
}
I have fixed this by changing the registration like so;
container.Register<IDbContext>(i => new MyDataContext()).ReusedWithin(ReuseScope.Request);
My original registration container.AddScoped<IDbContext, MyDataContext>(); is shorthand for an autowired registration type; container.RegisterAutoWiredAs<MyDataContext,IDbContext>().ReusedWithin(ReuseScope.Request);
I wonder if the Func container is trying to resolve the iDbSet Properties when using AutoWired. Which would explain why they end up null.
Related
I have project A which is a class library and project B that uses A. Project A is meant to be a generic helper library that can be used across projects (such as B).
The EF Core datacontext and the data entities need to be defined in project B (as they can vary by project) but I need to inject the datacontext in to constructors of service classes in Project A (which handle everything in a generic way).
In Project B I have the datacontext
public class MyDataContext : DbContext
{
public MyDataContext(DbContextOptions<MyDataContext> options): base(options)
{
}
public DbSet<Test> Tests { get; set; }
}
In project A I have class UnitOfWork that implements IUnitOfWork. In it's constructor I need to inject the datacontext. However since project A cannot reference project B (project A is meant to be generic), I cannot use the actual name of the datacontext in the parameter list. Since the datacontext inherits from DbContext, I tried
public UnitOfWork(DbContext dc){...}
In the startup of Project B, I have
services.AddDbContext<MyDataContext>(options =>
{
options.UseSqlServer("...<the connection string> ...");
});
services.AddScoped<IUnitOfWork, UnitOfWork>();
Everything compiles but in runtime when the UnitOfWork needs to be created, I get the error
System.AggregateException: Some services are not able to be constructed (Error while validating the service descriptor 'ServiceType: IUnitOfWork Lifetime: Scoped ImplementationType: UnitOfWork': Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContext' while attempting to activate 'UnitOfWork'.)
The Inner Exception is
InvalidOperationException: Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContext' while attempting to activate 'UnitOfWork'.
Any help is deeply appreciated.
EDIT
I was asked for the UnitOfWork class details in the comments. So here it is
public class UnitOfWork : IUnitOfWork
{
private readonly DbContext dc;
private readonly IServiceProvider serviceProvider;
public UnitOfWork(DbContext dc, IServiceProvider serviceProvider)
{
this.dc = dc;
this.serviceProvider = serviceProvider;
}
public void BeginTransaction()
{
dc.Database.BeginTransaction();
}
public void BeginTransaction(IsolationLevel isolationLevel)
{
dc.Database.BeginTransaction(isolationLevel);
}
public void CommitTransaction()
{
dc.Database.CommitTransaction();
}
public void RollbackTransaction()
{
dc.Database.RollbackTransaction();
}
public bool IsTransactionActive()
{
return dc.Database.CurrentTransaction != null;
}
public async Task<bool> SaveAsync()
{
return await dc.SaveChangesAsync() > 0;
}
public bool Save()
{
return dc.SaveChanges() > 0;
}
}
Your UnitOfWork service depends on a DbContext type, not the derived MyDataContext type which is registered into DI.
So you have two options:
You can modify the UnitOfWork registration like this (tell the IoC container to instantiate UnitOfWork with MyDataContext):
services.AddScoped<IUnitOfWork>(srp => new UnitOfWork(srp.GetRequiredService<MyDataContext>(), srp));
Or you can register DbContext into DI as well, so the DI container knows that when someone asks for a DbContext it should return MyDbContext:
services.AddScoped<DbContext, MyDataContext>();
Note that the ServiceProvider field seems to be unused in your UnitOfWork class.
The solution was to make two changes. First was to explicitly register the service as suggested by #fbede
services.AddScoped<DbContext, MyDataContext>();
Now when we do this, we lose the convenience of setting the DbContextOptionsBuilder options via the AddDbContext Extension method.
So we need to override the OnConfiguring method in the datacontext to set the configurations options we need. For example:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(configuration.GetConnectionString(<key>));
}
Of course, IConfiguration is injected in the MyDataContext constructor
I'm trying to run an .NET Core Web application with EF Core. In order to test the repository I've added an MyDbContext that inherits the EF DbContext and interface IMyDbContext.
public interface IMyDbContext
{
DbSet<MyModel> Models { get; set; }
}
public class MyDbContext : DbContext, IMyDbContext
{
public MyDbContext(DbContextOptions<MyDbContext> options) : base(options)
{
}
public virtual DbSet<MyModel> Models { get; set; }
}
The context interface is injected to my generic repository:
public class GenericRepository<TEntity> : IGenericRepository<TEntity>
{
private readonly IMyDbContext _context = null;
public GenericRepository(IMyDbContext context)
{
this._context = context;
}
}
When I use this code (without the interface) on startup.cs:
services.AddDbContext<MyDbContext>(options =>
options.UseSqlServer(...));
I'm getting a run-time error of:
InvalidOperationException: Unable to resolve service for type
'IMyDbContext' while attempting to activate 'GenericRepository`1[MyModel]'
And when using this line of code:
services.AddDbContext<IMyDbContext>(options =>
options.UseSqlServer(...));
I'm getting this compiled time error code of:
Cannot convert lambda expression to type 'ServiceLifetime' because it
is not a delegate type
My question is how to properly configure the services.AddDbContext of ConfigureServices method?
(Is there any changes needed inside Configure method?)
If needed I'm willing to modify the IMyDbContext
Use one of the overloads having 2 generic type arguments, which allow you to specify both the service interface/class you want to register as well as the DbContext derived class implementing it.
For instance:
services.AddDbContext<IMyDbContext, MyDbContext>(options =>
options.UseSqlServer(...));
Just found the answer:
I was missing the adding of the scope between IMyDbContext and MyDbContext.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyDbContext>(options => options.UseSqlServer(...));
services.AddScoped<IGenericRepository<MyModel>, GenericRepository<MyModel>>();
services.AddScoped<IMyDbContext, MyDbContext>();
}
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!
I'm trying to register IDbContext in Unity but I'm getting this
error
"The type IDbContext does not have an accessible constructor."
UnityConfig.cs
public static void RegisterTypes(IUnityContainer container)
{
container.RegisterType<IProductServices, ProductServices>();
container.RegisterType(typeof(IRepository<>), typeof(Repository<>));
//---all ERROR below---
//container.RegisterType(typeof(IDbContext), typeof(DbContext));
//container.RegisterType<IDbContext>();
var connectionString = System.Configuration.ConfigurationManager.ConnectionStrings["ApplicationContext"].ConnectionString;
container.RegisterType(typeof(IDbContext), typeof(DbContext), new ContainerControlledLifetimeManager(), new InjectionConstructor(connectionString));
}
IDbContext.cs
public interface IDbContext
{
IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity;
int SaveChanges();
}
ApplicationContext.cs
public partial class ApplicationContext : DbContext, IDbContext
{
public ApplicationContext()
: this("name=ApplicationContext")
{
}
public ApplicationContext(string name)
: base(name)
{
}
public DbSet<Product> Products { get; set; }
public new IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity
{
return base.Set<TEntity>();
}
}
You have 2 constructor in your DbContext therefore Unity can not initialize it simply remove ApplicationContext(string name) constructor an let default exist.
public partial class ApplicationContext : DbContext, IDbContext
{
public ApplicationContext()
: this("name=ApplicationContext")
{
}
public DbSet<Product> Products { get; set; }
public new IDbSet<TEntity> Set<TEntity>() where TEntity : BaseEntity
{
return base.Set<TEntity>();
}
}
An in unity side write:
container.RegisterType<IDbContext, ApplicationContext>();
But if you want keep your constructors try this:
container.RegisterType<IDbContext, ApplicationContext>(
new InjectionConstructor("YourConnectionStringYouProvidedSomewhere"));
So you could do something like this:
public class MyContext : DbContext
{
public MyContext() : base("connectionstringproperty") { }
}
public interface IMyContextFactory : IDbContextFactory<MyContext>
{
}
public class MyContextFactory : IMyContextFactory
{
public MyContext Create()
{
return new MyContext();
}
}
Register Like this:
container.RegisterType<IMyContextFactory, MyContextFactory>(new PerResolveLifetime());
And use in class like so:
public class UserService
{
IMyContextFactory contextFactory
public UserService(IMyContextFactory contextFactory)
{
contextFactory = contextFactory;
}
public List<User> GetUsers()
{
using(var context = this.contextFactory.Create())
{
return context.Users.ToList();
}
}
}
I don't use repositories until I really have to (which is nearly never :) ). But if UserService was a repository class instead (it kinda is at the moment) the implementation would be the same using constructor injection.
DbContext
If you want testability, EF 6 did a good job in making all methods and properties virtual so you could override them rather than creating an interface to back it.
For dependency injection, EF created IDbContextFactory<T> where T : DbContext
You should be inheriting from DbContext so that you can create some tables in the database. When you inherit you can create different constructors depending on your needs, the easiest I find is to create a parameterless one and call the base constructor overload which takes the web.config connection string property.
DbContext does not have a public, default constructor. The most minimial public constructor is one which takes a string value containing the name of the connection string to use (defined in your app.config or web.config file) or the actual connection string to the database itself.
If you have such a connection string defined in your app.config or web.config (or you have a suitable default connection string to be used throughout your application), try the following Unity registration:
// In the code below, "connection_string" is either the full database
// connection string or the name of the connection string as defined
// in the app.config or web.config files
//
// NOTE: This method requires a LifetimeManager.
// I used the default "singleton container" provided by unity.
// This may not be appropriate for your application--please use an
// appropriate container lifetime manager.
container.RegisterType(typeof(IDbContext), typeof(DbContext),
new ContainerControlledLifetimeManager(),
new InjectionConstructor("connection_string"));
If you have multiple databases you will need to connect to, then you may want to subclass DbContext (e.g. MyDB1DbContext, MyDB2DbContext) or use named registrations. If you subclass DbContext, then you could have the subclasses implement a public default constructor, passing a suitable connection string to the base DbContext class--at which point, your context registrations become simpler.
For one application-wide DbContext though, I'd probably just use the code snippet above.
Update after OP's Update
Unless you'll need to use multiple connection strings with your application-defined DbContext, I would simply define as follows:
public class ApplicationDbContext() : System.Data.Entity.DbContext
{
public ApplicationDbContext() : base("my_connection_string_name")
{ }
... // Rest of context class definition
}
Then, to register:
// NOTE: Can also use the generic version--may need to also change the
// lifetime management of the context depending on your application.
container.Register(typeof(IDbContext), typeof(ApplicationDbContext),
new ContainerControlledLifetimeManager());