How to properly configure the `services.AddDbContext` of `ConfigureServices` method - c#

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>();
}

Related

EF Core Data Context injection across Projects

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

Create one lazy and one eager DbContext via inheritance?

I am using EFCore 3.1.5 and I have a DbContext I want to be able to use in the same controller or service either lazy or eager. However, it seems like I cannot get it to load lazy properly. Eager seems to work fine.
Whenever I do something as simple as:
var users = await _lazyDbContext
.Users
.Take(10)
.ToListAsync();
each navigational property on each User is null. However, with eager loading, it works fine:
var users = await _dbContext
.Users
.Include(x => x.Contact)
.Take(10)
.ToListAsync();
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<LazyUserContext>((sp, opt) =>
{
var connectionString = "very secret";
opt.UseSqlServer(connectionString, x => x.CommandTimeout(300));
opt.UseLazyLoadingProxies();
});
services.AddDbContext<UserContext>((sp, opt) =>
{
var connectionString = "very secret";
opt.UseSqlServer(connectionString, x => x.CommandTimeout(300));
});
services.AddScoped<IUserContext, UserContext>();
services.AddScoped<ILazyUserContext, LazyUserContext>();
}
UserContext.cs
public interface IUserContext
{
DbSet<User> Users { get; set; }
DbSet<Contact> Contacts { get; set; }
}
public class UserContext : DbContext, IUserContext
{
public UserContext(DbContextOptions<UserContext> options) : base(options) {}
public DbSet<User> Users { get; set; }
public DbSet<Contact> Contacts { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Users>(e =>
{
e.HasOne(x => x.Contact).WithOne(x => x.User).HasForeignKey(x => x.ContactId);
}
}
}
LazyUserContext.cs
public interface ILazyUserContext : IContext {}
public class LazyUserContext : UserContext, ILazyUserContext
{
public LazyUserContext(DbContextOptions<UserContext> options) : base(options) {}
}
What could the issue be here? I have tried to IoC both the interface and the class in my controller/service. I have tried with and without the services.AddScoped<>().
All I want is be able to use a lazy dbContext or eager dbContext, where I want to default to the eager one.
The solution
All I want is be able to use a lazy dbContext or eager dbContext
You should be able to just set the configuration in the subclasses:
public class LazyContext : MyContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies();
}
}
public class EagerContext : MyContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
}
}
There are two options of setting db option configuration, via the the constructor (and thus the DI route), or via the class itself. Since this particular setting is class-specific and you don't want to juggle it in the DI registration, it makes sense to rely on the class-specific configuration method.
Why your solution didn't work
The reason your initial approach didn't work is because AddDbContext<T> registers that specific T as your dependency type. From the source:
The AddDbContext extension method registers DbContext types with a scoped lifetime by default.
Note that it registers the context type, not any interfaces/ancestors of that context type.
So when you do this:
services.AddDbContext<LazyUserContext>((sp, opt) =>
{
var connectionString = "very secret";
opt.UseSqlServer(connectionString, x => x.CommandTimeout(300));
opt.UseLazyLoadingProxies();
});
services.AddScoped<ILazyUserContext, LazyUserContext>();
If your class has a dependency of type ILazyUserContext, it only listens to the second registration and flatout ignores the first one.
Only when your class has a dependency of LazyUserContext will you actually get the db context options you specified in the first registration.
Note that you can use this type-specific registration behavior to your advantage when you want to register a default implementation:
// Specific interface => specific type
services.AddScoped<IUserContext, UserContext>();
services.AddScoped<ILazyUserContext, LazyUserContext>();
// General interface => explicitly chosen default type
services.AddScoped<IContext, UserContext>();
This allows you to have some classes that demand to specifically have the eager/lazy loading, and other classes which just take in whatever is the default (which could change over time).
The problem is caused by the fact that both context constructors use (depend on) one and the same options type - DbContextOptions<UserContext>.
AddDbContext<TContext> actually registers two types - the context itself TContext as well as factory for the context options dependency DbContextOptions<TContext>.
So you are registering two options factories (along with configuration action) - DbContextOptions<UserContext> and DbContextOptions<LazyUserContext>. However, as mentioned at the beginning LazyUserContext depends on DbContextOptions<UserContext>, so it's simply instantiated with the first options set-up, i.e. exactly like the other.
This is not specific for lazy loading, but for any scenario which requires different options (different database type/connection string etc.) and is the reason generic class DbContextOptions<TContext> exists.
The solution is to change the LazyUserContext dependency
public LazyUserContext(DbContextOptions<LazyUserContext> options) : base(options) { }
and since this won't compile because the base expects DbContextOptions<UserContext>, add second protected constructor to the base class accepting just DbContextOptions
protected UserContext(DbContextOptions options) : base(options) { }

How to have multiple DbContext of the same type?

I want to do some reporting in a ASP.NET Core web site that reads data from multiple databases using the same schema.
In Startup.cs I need to have something like:
public void ConfigureServices(IServiceCollection services)
{
// Some other stuff here.
services.AddDbContext<MyContext>(options => options.UseSqlServer(Configuration.GetConnectionString("FirstConnectionString")));
services.AddDbContext<MyContext>(options => options.UseSqlServer(Configuration.GetConnectionString("SecondConnectionString")));
}
But now the DbContext are of the same type and have no name, so how do I select the one I want to use in a controller?
public class HomeController : Controller
{
private readonly MyContext context;
public HomeController(MyContext context)
{
// Is that the one with FirstConnectionString or SecondConnectionString?
// How do I choose?
this.context = context;
}
}
EDIT:
I'm probably missing something but in MyContext I have:
public class MyContext : DbContext
{
public MyContext(DbContextOptions<MyContext> options) : base(options)
{
}
// Some more code here.
}
Then in MyContext1 I have:
public class MyContext1 : MyContext
{
// base in now MyContext and not DbContext !!!
// Error with: public MyContext1(DbContextOptions<MyContext1> options) : base(options)
public MyContext1(DbContextOptions<MyContext> options) : base(options)
{
}
}
If I add 2 derived types in startup and run it crashes and gives the following error message:
InvalidOperationException: Unable to resolve service for type 'Microsoft.EntityFrameworkCore.DbContextOptions`1[MyContext]' while attempting to activate 'MyContext1'.
If I also add the base type in startup (so 3 types with 3 different connection strings) then all 3 types use the connection string of the base type.
Why not just create two DbContexts? In theory, making 3 is probably cleaner .. keep the MyContext that you have set up, and then just create a Db1Context and Db2Context that inherit from it? means your registration ends up as
services.AddDbContext<Db1Context>(options => options.UseSqlServer(Configuration.GetConnectionString("FirstConnectionString")));
services.AddDbContext<Db2Context>(options => options.UseSqlServer(Configuration.GetConnectionString("SecondConnectionString")));
so then its easy to resolve, and due to inheritance you avoid some code duplication.. but I see no benefit from trying to keep 1 dbcontext that goes to multiple db in the same app
Edit:
If you are still having some troubles with DI working, there was a fairly old thread on the Github that looks like someone having this type of issue which they resolved by doing
public class EFDbContext : DbContext
{
public EFDbContext(DbContextOptions<EFDbContext> options) : base(options) { }
protected MainDbContext(DbContextOptions options) : base(options) { }
}
public class DimensionsDbContext : EFDbContext
{
public DimensionsDbContext(DbContextOptions<DimensionsDbContext> options) : base(options) { }
}
something along those lines, having a second protected constructor in the class that inherits from dbcontext, to allow for the further inherited classes to use that. I mean, I wasnt able to re-create the issue on my end but that solution still also works for me, so may help in terms of getting it working for you
I'm creating the multiple contexts in my reporting controllers in the end. It's not the DI way, but it works.
I have something like the following code in the controller constructor:
var firstOptionsBuilder = new DbContextOptionsBuilder<MyContext>();
firstOptionsBuilder.UseSqlServer("firstConnectionString");
var firstContext = new MyContext(firstOptionsBuilder.Options);

Dbcontext IDbset properties are null when injected in ServiceStack IoC

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.

ASP.NET Core resolving DbContext in Service

I have issue with resolving DbContext in my UserManager class.
Here is UserManager class:
public class UserManager : IUserManager
{
private readonly MyContext _context;
public UserManager(MyContext context)
{
_context = context;
}
}
My DbContext class:
public class MyContext: DbContext
{
public DbSet<User> Users { get; set; }
}
Startup:
services.AddDbContext<MyContext>(options =>
options.UseSqlServer(Configuration["database:connectionString"]));
//....
services.AddScoped<IUserManager, UserManager>();
There is errror:
Unable to resolve service for type
'MyContext' while
attempting to activate 'UserManager'.
Does anyone know where is the problem?
DI should resolve MyContext with code you provided. Resolved instance will not work (see #Nikosi answer about DbContextOptions), but it should be resolved/created.
Check your project for other/second MyContext class. I think you have two, possibly in different namespaces. "Go to declaration" (Ctrl+Click in VS) of MyContext from both Startup and UserManager files - are they referencing same class?
Documentation: Using DbContext with dependency injection
EF supports using DbContext with a dependency injection container.
Your DbContext type can be added to the service container by using
AddDbContext<TContext>.
AddDbContext will add make both your DbContext type,
TContext, and DbContextOptions<TContext> to the available for
injection from the service container.
Adding dbcontext to dependency injection
services.AddDbContext<MyContext>(options =>
options.UseSqlServer(Configuration["database:connectionString"]));
requires adding a constructor
argument to your DbContext type that accepts DbContextOptions.
You were missing configuration options in your DbContext
public class MyContext: DbContext {
public MyContext(DbContextOptions<MyContext> options) : base(options) { }
public DbSet<User> Users { get; set; }
}

Categories

Resources