ASP.NET core EF ambiguous constructor - entity-framework

I use ASP.NET Core with .NET 5 and recently wanted to change from local development to Azure Web production mode.
Locally I use SQLite and everything works fine, on production I want to use Azure SQL.
However when I want to migrate my database, I get a rather long exception:
System.Exception: Could not resolve a service of type 'Server.Calendars.CalendarDataContext' for the parameter 'calendarDataContext' of method 'Configure' on type 'Server.Startup'.
---> System.InvalidOperationException: Unable to activate type 'Server.Calendars.CalendarDataContext'. The following constructors are ambiguous:
Void .ctor(Microsoft.Extensions.Configuration.IConfiguration)
Void .ctor(Microsoft.EntityFrameworkCore.DbContextOptions`1[Server.Calendars.CalendarDataContext])
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(ResultCache lifetime, Type serviceType, Type implementationType, CallSiteChain callSiteChain)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain, Int32 slot)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, CallSiteChain callSiteChain)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.<>c__DisplayClass7_0.<GetCallSite>b__0(Type type)
at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.GetCallSite(Type serviceType, CallSiteChain callSiteChain)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.CreateServiceAccessor(Type serviceType)
at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
at Microsoft.AspNetCore.Hosting.ConfigureBuilder.Invoke(Object instance, IApplicationBuilder builder)
--- End of inner exception stack trace ---
at Microsoft.AspNetCore.Hosting.ConfigureBuilder.Invoke(Object instance, IApplicationBuilder builder)
at Microsoft.AspNetCore.Hosting.ConfigureBuilder.<>c__DisplayClass4_0.<Build>b__0(IApplicationBuilder builder)
at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.<>c__DisplayClass15_0.<UseStartup>b__1(IApplicationBuilder app)
at Microsoft.Extensions.DependencyInjection.AutoRegisterMiddleware.<>c__DisplayClass4_0.<Configure>b__0(IApplicationBuilder app)
at Microsoft.AspNetCore.Mvc.Filters.MiddlewareFilterBuilderStartupFilter.<>c__DisplayClass0_0.<Configure>g__MiddlewareFilterBuilder|0(IApplicationBuilder builder)
at Microsoft.AspNetCore.Server.IIS.Core.IISServerSetupFilter.<>c__DisplayClass2_0.<Configure>b__0(IApplicationBuilder app)
at Microsoft.AspNetCore.HostFilteringStartupFilter.<>c__DisplayClass0_0.<Configure>b__0(IApplicationBuilder app)
at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
My class CalendarDataContext .cs for Azure SQL
public class CalendarDataContext : DbContext
{
public DbSet<CalendarEntry> CalendarEntries { get; set; }
protected readonly IConfiguration Configuration;
public CalendarDataContext(IConfiguration configuration)
{
Configuration = configuration;
}
public CalendarDataContext(DbContextOptions<CalendarDataContext> options)
: base(options)
{ }
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
if (!options.IsConfigured)
{
options.UseSqlServer(Configuration.GetConnectionString("CalendarDatabase"));
}
}
}
and CalendarDataContextSqlite.cs for SQLite
public class CalendarDataContextSqlite : CalendarDataContext
{
public CalendarDataContextSqlite(IConfiguration configuration) : base(configuration) { }
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
if (!options.IsConfigured)
{
var databaseName = Configuration.GetConnectionString("CalendarDatabase");
var databasePath = PathHelper.DataPath(databaseName);
options.UseSqlite("Data Source=" + databasePath);
}
}
}
I think the issue is with the line CalendarDataContext(DbContextOptions<CalendarDataContext> options) that I need for creating a temporary InMemory-Database for my tests.
How can I make this ambiguous constructor less ambiguous?
Edit: Add startup.cs
public class Startup
{
public IWebHostEnvironment Environment { get; }
public Startup(IConfiguration configuration, IWebHostEnvironment environment)
{
Environment = environment;
}
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
if (Environment.IsProduction())
{
services.AddDbContext<CalendarDataContext>();
}
else if (Environment.IsDevelopment())
{
services.AddDbContext<CalendarDataContext, CalendarDataContextSqlite>();
}
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app,
IWebHostEnvironment env,
CalendarDataContext calendarDataContext)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
calendarDataContext.Database.Migrate();
}
}

Firstly, Add IConfiguration as a local member in your Startup.cs
IConfiguration Configuration;
public IWebHostEnvironment Environment { get; }
public Startup(IConfiguration configuration, IWebHostEnvironment environment)
{
Configuration = configuration;
Environment = environment;
}
then Register a configured CalendarDataContext or CalendarDataContextSqlite in Startup
public void ConfigureServices(IServiceCollection services)
{
if (Environment.IsProduction())
{
services.AddDbContext<CalendarDataContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("CalendarDatabase"));
}
else if (Environment.IsDevelopment())
{
services.AddDbContext<CalendarDataContext, CalendarDataContextSqlite>(options => {
var databaseName = Configuration.GetConnectionString("CalendarDatabase");
var databasePath = PathHelper.DataPath(databaseName);
options.UseSqlite("Data Source=" + databasePath);
});
}
}
Then, CalendarDataContext:
public class CalendarDataContext : DbContext
{
public DbSet<CalendarEntry> CalendarEntries { get; set; }
public CalendarDataContext(DbContextOptions<CalendarDataContext> options)
: base(options) { }
protected CalendarDataContext(DbContextOptions options)
: base(options) { }
}
And, CalendarDataContextSqlite:
public class CalendarDataContextSqlite : CalendarDataContext
{
public CalendarDataContextSqlite(DbContextOptions<CalendarDataContextSqlite> options)
: base(options) { }
}
Now,
No need for the OnConfiguring in the context classes.
In production you'll have a configured CalendarDataContext to be injected wherever the constructor asks for a CalendarDataContext.
And for developerment you'll have a configured CalendarDataContextSqlite to be injected wherever the constructor asks for a CalendarDataContext.
That configured context will also be injected into Startup.Configure so you can migrate your database.

Related

Blazor Server (EF Core) and database connection

Sorry for the rudimentary question.
I'm currently studying application development with Blazor Server and am having trouble connecting to a database.
I'm trying to use DI to connect to the database.
I created a code that uses the factory pattern as shown below, but an error occurs in the part that gets the connection string.
public void ConfigureServices(IServiceCollection services)
{
//error:CS0121 Inappropriate call between the following methods or properties: 'Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.AddDbContextFactory<TContext>(Microsoft.Extensions.DependencyInjection.IServiceCollection, System.Action<Microsoft.EntityFrameworkCore.DbContextOptionsBuilder>, Microsoft.Extensions.DependencyInjection.ServiceLifetime)' と 'BlazorSv.Models.FactoryExtensions.AddDbContextFactory<TContext>(Microsoft.Extensions.DependencyInjection.IServiceCollection, System.Action<Microsoft.EntityFrameworkCore.DbContextOptionsBuilder>, Microsoft.Extensions.DependencyInjection.ServiceLifetime)'
services.AddDbContextFactory<BlazorSv.Models.SQLbeginnerContext>(options => options.UseNpgsql(Configuration.GetConnectionString("DBConnection")));
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
}
I thought that the definition of the <Models.SQLbeginnerContext> part was ambiguous, so I wrote the hierarchy, but it didn't work.
What should I do about this error?
I want some advice
Below is SQLbegginerContext.cs that describes StartUp.cs and the factory pattern.
StartUp.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
//error:CS0121 Inappropriate call between the following methods or properties: 'Microsoft.Extensions.DependencyInjection.EntityFrameworkServiceCollectionExtensions.AddDbContextFactory<TContext>(Microsoft.Extensions.DependencyInjection.IServiceCollection, System.Action<Microsoft.EntityFrameworkCore.DbContextOptionsBuilder>, Microsoft.Extensions.DependencyInjection.ServiceLifetime)' と 'BlazorSv.Models.FactoryExtensions.AddDbContextFactory<TContext>(Microsoft.Extensions.DependencyInjection.IServiceCollection, System.Action<Microsoft.EntityFrameworkCore.DbContextOptionsBuilder>, Microsoft.Extensions.DependencyInjection.ServiceLifetime)'
services.AddDbContextFactory<Models.SQLbeginnerContext>(options => options.UseNpgsql(Configuration.GetConnectionString("DBConnection")));
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
SQLbegginerContext.cs
public interface IDbContextFactory<TContext> where TContext : DbContext
{
TContext CreateDbContext();
}
public class blazordbFactory<TContext> : IDbContextFactory<TContext> where TContext : DbContext
{
public blazordbFactory(IServiceProvider provider)
{
this.provider = provider;
}
private readonly IServiceProvider provider;
public TContext CreateDbContext()
{
return ActivatorUtilities.CreateInstance<TContext>(provider);
}
}
public static class FactoryExtensions
{
public static IServiceCollection AddDbContextFactory<TContext>(
this IServiceCollection collection,
Action<DbContextOptionsBuilder> optionsAction = null,
ServiceLifetime contextAndOptionsLifetime = ServiceLifetime.Singleton)
where TContext : DbContext
{
collection.Add(new ServiceDescriptor(
typeof(IDbContextFactory<TContext>),
sp => new blazordbFactory<TContext>(sp),
contextAndOptionsLifetime));
collection.Add(new ServiceDescriptor(
typeof(DbContextOptions<TContext>),
sp => GetOptions<TContext>(optionsAction, sp),
contextAndOptionsLifetime));
return collection;
}
private static DbContextOptions<TContext> GetOptions<TContext>(
Action<DbContextOptionsBuilder> action,
IServiceProvider sp = null) where TContext : DbContext
{
var optionsBuilder = new DbContextOptionsBuilder<TContext>();
if (sp != null)
{
optionsBuilder.UseApplicationServiceProvider(sp);
}
action?.Invoke(optionsBuilder);
return optionsBuilder.Options;
}
}
public partial class SQLbeginnerContext : DbContext
{
public SQLbeginnerContext()
{
}
public SQLbeginnerContext(DbContextOptions<SQLbeginnerContext> options)
: base(options)
{
}
追記:SQLbegginer.cs
//Comment out this part
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseNpgsql("Host=localhost; Database=SQLbeginner; Username=user; Password=****")
}
}

ASP.NET Core 2.2 How to add two constructors to the same class using dependency injection?

I have a class "ConnectorManagement" in which I need to use both SignalR services as well as querying a db table using EF CORE.
I cant work out how to load both dbcontext and hubcontext into the same class using a constructor and dependancy injection. The current result is visual studio fails to load the project when run in debug. Tried researching this but not understanding what needs to be done.
Current code below:
namespace myNamespace.Controller
{
public class ConnectorManagement : IHostedService
{
private static readonly log4net.ILog log = log4net.LogManager.GetLogger(typeof(Logger));
private readonly IHubContext<MessageHub> _hubContext;
public readonly ApplicationDbContext _context;
public ConnectorManagement(IHubContext<MessageHub> hubContext, ApplicationDbContext context)
{
_hubContext = hubContext;
_context = context;
}
public Task StartAsync(CancellationToken cancellationToken)
{
log.Info("Initial Test");
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
throw new NotImplementedException();
}
dbcontext class:
namespace myNamespace.Data
{
public class ApplicationDbContext : IdentityDbContext
{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}
public DbSet<myProject.Models.ConnectorInbound> ConnectorInbound { get; set; }
public DbSet<myProject.Models.ConnectorOutbound> ConnectorOutbound { get; set; }
public DbSet<myProject.Models.SystemMapping> SystemMapping { get; set; }
}
}
startup class:
namespace myProjectNamespace
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDefaultIdentity<IdentityUser>()
.AddDefaultUI(UIFramework.Bootstrap4)
.AddEntityFrameworkStores<ApplicationDbContext>();
// Start up the TcpServerTcpServer engine
services.AddHostedService<ConnectorManagement>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddSignalR();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseSignalR(routes =>
{
routes.MapHub<MessageHub>("/messageHub");
});
loggerFactory.AddLog4Net();
app.UseMvc();
}
}
}
I wasn't paying attention to the fact that you're injecting this into a hosted service. Hosted services are singletons and both the hub context and database context are scoped services. You need to inject IServiceProvider instead and then create a scope. This will need to be done for every usage; you cannot persist it on an ivar, for example. You can only use it within the using statement.
using (var scope = _serviceProvider.CreateScope())
{
var context = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();
// Do something
}

Cannot construct DbContext without using OnConfiguring(DbContextOptionsBuilder optionsBuilder)

I am developing a .NET CORE MVC 2.1 Web Application with a DbContext declared in a DLL (EF Core 2.1).
I would like to configure the context using IServiceCollection.AddContext<GladContext> but if I do not ALSO configures it DbContext.OnConfiguring(DbContextOptionsBuilder optionsBuilder) I am told that No database provider has been configured for this DbContext despite having a constructor taking a DbContextOptions<GladContext>
public GladContext(DbContextOptions<GladContext> options, IGladConnectionStringProvider connectionStringProvider) : base(options)
{
_connectionStringProvider = connectionStringProvider;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
GladOptionsBuilderHelper.ConfigureDefaultOptionsBuilder(optionsBuilder, _connectionStringProvider.ConnectionString);
base.OnConfiguring(optionsBuilder);
}
The IGladConnectionStringProvider is my current workaround and that is acceptable if it wasn't because I now need to configure both DbContextOptionsBuilder and DbContextOptionsBuilder<GladContext>
public static class GladOptionsBuilderHelper
{
public const string GladMigrationsHistory = "__GladMigrationsHistory";
public static DbContextOptionsBuilder<GladContext> CreateDefaultTypedOptionsBuilder(string connectionString)
{
var optionsBuilder = new DbContextOptionsBuilder<GladContext>();
optionsBuilder
.UseSqlServer(connectionString, options =>
{
options.EnableRetryOnFailure();
options.MigrationsHistoryTable(GladMigrationsHistory, EntityBase.SchemaName);
})
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
return optionsBuilder;
}
public static void ConfigureDefaultOptionsBuilder(DbContextOptionsBuilder optionsBuilder, string connectionString)
{
optionsBuilder
.UseSqlServer(connectionString, options =>
{
options.EnableRetryOnFailure();
options.MigrationsHistoryTable(GladMigrationsHistory, EntityBase.SchemaName);
})
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}
}
The DbContextOptionsBuilder<GladContext> is used in IDesignTimeDbContextFactory<GladContext>
Can you can tell me how to use AddContext to configure GladContext or how to construct a DbContextOptionsBuilder from a DbContextOptionsBuilder<GladContext> or the other way around?
The configuration part of it is an override of the IServiceCollection.AddDbContext().
So, when you call AddDbContext, just add your options into the parentheses like so:
var connectionString = "CONNECTION-STRING-HERE";
services.AddDbContext<MyContext>(o => o
.UseSqlServer(connectionString)
.UseQueryTrackingBehavior(true)
.EnableSensitiveDataLogging(true));
public class MyContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public MyContext(DbContextOptions<MyContext> options) : base(options)
{
}
}

How to Resolve multiple DBContext call using generic UnitOfWork<TContext> in Autofac

Hi I have created my UnitOfWork as generic and at runtime it should create new instance of DB context with DBContextOption Builder on the basis of TContext passing I have registered Mention DB Context in autofac but how to resolve this at DB Context Constructor Level
DB Context 1 Implemetation
public class DBContext1 : DbContext
{
public DBContext1(DbContextOptions<DBContext1> options1) : base(options1)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
DB Context 2 Implemetation
public class DBContext2 : DbContext
{
public DBContext2(DbContextOptions<DBContext2> options2) : base(options2)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
IUnitOfWork interface Implemetation
public interface IUnitOfWork<TContext> where TContext : DbContext, IDisposable
{
}
UnitOfWork class Implemetation
public class UnitOfWork<TContext> : IDisposable, IUnitOfWork<TContext> where TContext : DbContext, new()
{
private DbContext _context;
public UnitOfWork()
{
_context = new TContext();
}
}
StartUp Class Implemetation
public class Startup
{
protected IConfiguration _configuration { get; set; }
public Startup(IConfiguration configuration)
{
_configuration = configuration;
}
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddEntityFrameworkSqlServer()
.AddDbContext<DBContext1>(options =>
options.UseSqlServer(_configuration.GetConnectionString("DBContext1")))
.AddDbContext<DBContext2>(options =>
options.UseSqlServer(_configuration.GetConnectionString("DBContext2")));
/* Autofac DI Configuration with registering DBContext/DataModule/ServiceModule to it */
var containerBuilder = new ContainerBuilder();
containerBuilder.RegisterInstance(_configuration).AsImplementedInterfaces().ExternallyOwned();
var autoFacOptions1 = new DbContextOptionsBuilder<DBContext1>().UseSqlServer(_configuration.GetConnectionString("DBContext1")).Options;
var autoFacOptions2 = new DbContextOptionsBuilder<DBContext2>().UseSqlServer(_configuration.GetConnectionString("DBContext2")).Options;
containerBuilder.Register(c => new DBContext1(autoFacOptions1)).As<DbContext>();
containerBuilder.Register(c => new DBContext2(autoFacOptions2)).As<DbContext>();
containerBuilder.RegisterModule<DataModule>();
containerBuilder.RegisterModule<ServiceModule>();
containerBuilder.Register<String>(c => Guid.NewGuid().ToString())
.Named<String>("correlationId")
.InstancePerLifetimeScope();
containerBuilder.Populate(services);
var container = containerBuilder.Build();
return new AutofacServiceProvider(container);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Account}/{action=Login}/{id?}");
});
app.Run(async (context) =>
{
await context.Response.WriteAsync("Hello World!");
});
}
}
I am able to achieve multiple DBContext Call as required but I have to create Default constructor & connection string in DB context like mention below
DB Context 1 Implemetation
public class DBContext1 : DbContext
{
public DBContext1()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Data Source=Server;Database=DB;User Id=UserID;Password=Password;Integrated Security=False;MultipleActiveResultSets=true;");
}
public DBContext1(DbContextOptions<DBContext1> options1) : base(options1)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
DB Context 2 Implemetation
public class DBContext2 : DbContext
{
public DBContext2()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(#"Data Source=Server;Database=DB;User Id=UserID;Password=Password;Integrated Security=False;MultipleActiveResultSets=true;");
}
public DBContext2(DbContextOptions<DBContext2> options2) : base(options2)
{
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
}
}
Please help me to call parameterised constructor of DBContext1 & DBContext2 using autofac dependency resolver
Well, if you're using autofac to resolve dependencies then why are you trying to do its job for it? :) That's the main problem with your code.
First of all, you don't need to register IConfiguration explicitly. It is already registered in the IServiceCollection that's passed to ConfigureServices() method and will be automatically picked up by autofac during containerBuilder.Populate(services) call. You can just remove this registration and nothing will change.
Further, you're registering both your DbContexts twice - in the service collection and in the autofac container builder. This is not necessary as the latter will effectively replace the former. Also, it creates confusion about what is registered where and how this whole this is going to work. It's better to pick one method of registration and stick with it.
Next problem: how are you going to unit test your unit of work? It has hard dependency on DbContext whose lifecycle you cannot control in tests. This is exactly what you need autofac for: manage component's dependencies for you allowing you to concentrate on the component's purpose and not on the secondary stuff.
Next confusion point is here:
containerBuilder.Register(c => new DBContext1(autoFacOptions1)).As<DbContext>();
containerBuilder.Register(c => new DBContext2(autoFacOptions2)).As<DbContext>();
By doing this you are effectively replacing first db context registration with the second. From this point there is no way to inject DBContext1 anywhere in your application. EDITED: You still can inject collection of DbContext derivative implementations and find DBContext1 among them... but that would look very weird.
All in all, this can be done in much more clean and straightforward way.
Startup
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public IServiceProvider ConfigureServices(IServiceCollection services)
{
services.AddMvc();
var builder = new ContainerBuilder();
builder.Register(c => c.CreateDbContextOptionsFor<DBContext1>("DBContext1")).As<DbContextOptions<DBContext1>>().SingleInstance();
builder.Register(c => c.CreateDbContextOptionsFor<DBContext2>("DBContext2")).As<DbContextOptions<DBContext2>>().SingleInstance();
builder.RegisterType<DBContext1>().AsSelf().InstancePerLifetimeScope();
builder.RegisterType<DBContext2>().AsSelf().InstancePerLifetimeScope();
builder.RegisterType<SomeComponent>().As<ISomeComponent>().InstancePerLifetimeScope();
builder.RegisterGeneric(typeof(UnitOfWork<>)).As(typeof(IUnitOfWork<>)).InstancePerLifetimeScope();
builder.Populate(services);
var container = builder.Build();
return new AutofacServiceProvider(container);
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
....
}
}
CreateDbContextOptionsFor helper implementation. It is introduced in order to make Startup code concise and more readable. It can probably be improved even further by making use of autofac's parameterized factory instead of new DbContextOptionsBuilder<TContext>(), but I'm not sure if there's a point in it in this case.
public static class DBExtentions
{
public static DbContextOptions<TContext> CreateDbContextOptionsFor<TContext>(this IComponentContext ctx,
string connectionName) where TContext : DbContext
{
var connectionString = ctx.Resolve<IConfiguration>().GetConnectionString(connectionName);
return new DbContextOptionsBuilder<TContext>().UseSqlServer(connectionString).Options;
}
}
UnitOfWork
public class UnitOfWork<TContext> : IUnitOfWork<TContext> where TContext : DbContext
{
private TContext _context;
public UnitOfWork(TContext context)
{
_context = context;
}
}
Injecting and using unit of work
public class SomeComponent : ISomeComponent
{
private readonly IUnitOfWork<DBContext1> _uow;
public SomeComponent(IUnitOfWork<DBContext1> uow)
{
_uow = uow;
}
public void DoSomething()
{
_uow.DoWhatever();
}
....

MsTest - Mocking a DbContext with DbContextOption and configuration

I have 2 project, Data and Data.test, I use ef core and .net core for both of them, for Data project I have ExpenseDb like this:
public class ExpenseDb: DbContext
{
private IConfigurationRoot _config;
public ExpenseDb(DbContextOptions<ExpenseDb> options, IConfigurationRoot config) : base(options)
{
_config = config;
}
public DbSet<Account> Accounts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
base.OnConfiguring(optionsBuilder);
optionsBuilder.UseSqlServer(_config["Data:ConnectionString"]);
}
}
And I have a repository for Account like this:
private ExpenseDb _db;
public AccountRepository(ExpenseDb db)
{
_db = db;
}
public IEnumerable<Account> All(Guid userId)
{
return (_db.Accounts.AsNoTracking().Where(a => a.UserId == userId).ToList());
}
I use ms IOC for injectiong dependencies like this :
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json");
_config = builder.Build();
}
IConfigurationRoot _config;
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton(_config);
services.AddDbContext<ExpenseDb>(ServiceLifetime.Scoped);
}
These all are in my Data project, and for Data.Test I would like to test All method, I realized I must Mock my ExpenseDb so I got Moq from Nuget Package and now I have test class like this :
[TestClass]
public class AccountRepositoryTest
{
private readonly Mock<ExpenseDb> _dbMock = new Mock<ExpenseDb>();
private readonly AccountRepository _repo;
public AccountRepositoryTest()
{
_repo = new AccountRepository(_dbMock.Object);
}
[TestMethod]
public void AllForInvalidUser()
{
var fakeaccount = new Account() { Name="cat2",OpenDate=DateTime.Now,StartBalance=100};
Mock < DbSet < Account >> acMock = DbSetMock.Create(fakeaccount);
var results = _repo.All(Guid.Parse("cf15c6c9-f688-47ee-892e-297e530be053"));
Assert.IsNotNull(results);
}
}
Obviously my test is failed, because I must pass config and options to my ExpenseDb somehow, but I don't know How?!
I searched and I found out all answer are saying "You must have an inteface for your service" but i don't want to create an unnecessary interface.
Since DbContextOptions and config are not being used in the actual test code. You could create a constructor in your db context marked as protected to allow the instantiation of the ExpenseDb object without any params.