Database Migration on Startup - entity-framework-core

I've read a lot of articles regarding database migration on startup and no matter what approach I use my efforts aren't going anywhere. My main problem that i'm getting is no parameterless constructor defined for type startup
I have my DataContext class
public class DataContext : DbContext
{
public DataContext(DbContextOptions options) : base(options)
{
}
public DataContext()
{
}
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
if (options.IsConfigured)
{
//means that context has been added during dependency injection and no further action required.
}
else
{
//means context is being accessed during Add-Migration therefore we need to set the options. The whole DI/Configuration process won't have run yet, so need some other way to get connection string.
//probably below is a bit too fancy, just hardcoding would be fine. But anyway it seems to work and transfers to different developers machines
//you must have {Values: { SqlConnectionString : xyz}} in local.settings.json in Unite.FunctionApp project dir
var localSettingsJson =
Path.Combine(local.settings.json");
var config = new ConfigurationBuilder()
.AddJsonFile(localSettingsJson, false)
.Build();
options.UseSqlServer(config["Values:SqlConnectionString"]);
}
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{... }
My Startup Class
// register assembly
[assembly: FunctionsStartup(typeof(Startup))]
{
// inherit FunctionsStartup
public class Startup : FunctionsStartup
{
private DataContext _context;
public Startup(DataContext context)
{
_context = context;
}
public override void Configure(IFunctionsHostBuilder builder)
{
var executionContextOptions = builder.Services.BuildServiceProvider()
.GetService<IOptions<ExecutionContextOptions>>().Value;
var config = new ConfigurationBuilder()
.SetBasePath(executionContextOptions.AppDirectory)
.AddJsonFile("local.settings.json", true)
.AddUserSecrets(Assembly.GetExecutingAssembly(), false)
.AddEnvironmentVariables()
.Build();
builder.Services.AddSingleton<IConfiguration>(config);
var sqlConnection = config["SqlConnectionString"] ??
throw new Exception("SQL Connection String Not Defined");
builder.Services.AddDbContext<DataContext>(options => options.UseSqlServer(sqlConnection));
_context.Database.MigrateAsync();
}
}
}
If I have my paramaterless DataContext method in my class why am i still getting this issue that it isn't defined?

Add your parameterless constructor before the other constructor in your DataContext class.

Related

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.

How to change database schema on runtime in EF7 or EF core

My database have different schema depending on user selections on runtime.
My code is below:
public partial class FashionContext : DbContext
{
private string _schema;
public FashionContext(string schema) : base()
{
_schema = schema;
}
public virtual DbSet<Style> Styles { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlServer(#"Server=.\sqlexpress;Database=inforfashionplm;Trusted_Connection=True;");
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Style>()
.ToTable("Style", schema: _schema);
}
}
Upon testing. I created a context instance with 'schema1'.
So far so good.
But when I create another context instance with different schema 'schema2', the resulting data in which the schema is still on 'schema1'.
Here is the implementation:
using (var db = new FashionContext("schema1"))
{
foreach (var style in db.Styles)
{
Console.WriteLine(style.Name);
}
}
Console.ReadLine();
Console.Clear();
using (var db = new FashionContext("schema2"))
{
foreach (var style in db.Styles)
{
Console.WriteLine(style.Name);
}
}
Console.ReadLine();
Later I noticed that the OnModelCreating is called only one time, so it is never called again when you create a new context instance of the same connection string.
Is it possible to have dynamic schema on runtime? Note: this is possible in EF6
One of possible way was mentioned above, but briefly, so I will try to explain with examples.
You ought to override default ModelCacheKeyFactory and ModelCacheKey.
ModelCachekeyFactory.cs
internal sealed class CustomModelCacheKeyFactory<TContext> : ModelCacheKeyFactory
where TContext : TenantDbContext<TContext>
{
public override object Create(DbContext context)
{
return new CustomModelCacheKey<TContext>(context);
}
public CustomModelCacheKeyFactory([NotNull] ModelCacheKeyFactoryDependencies dependencies) : base(dependencies)
{
}
}
ModelCacheKey.cs, please review Equals and GetHashCode overridden methods, they are not best one and should be improved.
internal sealed class ModelCacheKey<TContext> : ModelCacheKey where TContext : TenantDbContext<TContext>
{
private readonly string _schema;
public ModelCacheKey(DbContext context) : base(context)
{
_schema = (context as TContext)?.Schema;
}
protected override bool Equals(ModelCacheKey other)
{
return base.Equals(other) && (other as ModelCacheKey<TContext>)?._schema == _schema;
}
public override int GetHashCode()
{
var hashCode = base.GetHashCode();
if (_schema != null)
{
hashCode ^= _schema.GetHashCode();
}
return hashCode;
}
}
Register in DI.
builder.UseSqlServer(dbConfiguration.Connection)
.ReplaceService<IModelCacheKeyFactory, CustomModelCacheKeyFactory<CustomContext>>();
Context sample.
public sealed class CustomContext : TenantDbContext<CustomContext>
{
public CustomContext(DbContextOptions<CustomContext> options, string schema) : base(options, schema)
{
}
}
You can build the model externally and pass it into the DbContext using DbContextOptionsBuilder.UseModel()
Another (more advanced) alternative is to replace the IModelCacheKeyFactory to take schema into account.
I found a way to recreate the compiled model on each context creation.
public partial class MyModel : DbContext {
private static DbConnection _connection
{
get
{
//return a new db connection
}
}
private static DbCompiledModel _model
{
get
{
return CreateModel("schema name");
}
}
public MyModel()
: base(_connection, _model, false)
{
}
private static DbCompiledModel CreateModel(string schema)
{
var modelBuilder = new DbModelBuilder();
modelBuilder.HasDefaultSchema(schema);
modelBuilder.Entity<entity1>().ToTable(schema + ".entity1");
var builtModel = modelBuilder.Build(_connection);
return builtModel.Compile();
}
}

Passing connection string to Entity framework at runt time for each call

My Entity framework context is as following
public partial class MyContext : DbContext, IMyContext
{
static MyContext()
{
System.Data.Entity.Database.SetInitializer<MyContext>(null);
}
public MyContext()
: base("Name=MyContext")
{
}
I am resolving it through autofac in the following way
builder.RegisterType(typeof(MainContext)).As(typeof(DbContext)).InstancePerLifetimeScope();
builder.RegisterType<MainContext>().As<IMainContext>().InstancePerRequest();
This db context gets called in repository layer
#region Fields
private readonly IMyContext _context;
#endregion
#region Constructors and Destructors
public EmployeeRepository(IMyContext context)
{
_context = context;
}
#endregion
public void Create(Employee emp)
{
this._context.Employee.Add(emp);
}
Now my issue is , I want to set the connection string dynamically per call. The connection string will be passed through a webapi which i want to pass on to this context. Can anyone help me how can i do that? I am confused about autofac here. Secondly how can i make sure each call sets connection string and does not cache it.
You can use a factory that will build the context and set the connectionstring for you.
public interface IContextFactory
{
IContext GetInstance();
}
public class MyContextFactory : IContextFactory
{
public IContext GetInstance()
{
String connectionString = this.GetConnectionString(HttpContext.Current);
return new MyContext(connectionString);
}
private String GetConnectionString(HttpContext context)
{
// do what you want
}
}
builder.RegisterType<MyContextFactory>()
.As<IContextFactory>()
.InstancePerRequest();
builder.Register(c => c.Resolve<IContextFactory>().GetInstance())
.As<IContext>()
.InstancePerRequest();
If you can't get connectionstring based on HttpContext, you can change contextFactory implementation to expect initialization by WebAPI before creating the instance. For example :
public interface IContextFactory
{
IContext GetInstance();
void Initialize(String connectionString);
}
public class MyContextFactory : IContextFactory
{
private String _connectionString;
public void Initialize(String connectionString)
{
this._connectionString = connectionString;
}
public IContext GetInstance()
{
if (this._connectionString == null)
{
throw new Exception("connectionString not initialized");
}
return new MyContext(this._connectionString);
}
}
At the beginning of your web API call (through attribute for example), you can call the Initialize method. Because the factory is InstancePerRequest you will have one instance for the duration of the request.
By the way, I'm not sure to understand this registration
builder.RegisterType(typeof(MainContext)).As(typeof(DbContext)).InstancePerLifetimeScope();
builder.RegisterType<MainContext>().As<IMainContext>().InstancePerRequest();
It looks buggy because you will have 2 different registration of the same type and not for the same scope, is it intended ? Furthermore, it doesn't sound a good idea to register a DbContext, do you need this registration ?
The following registration looks better :
builder.RegisterType<MainContext>()
.As<IMainContext>()
.As<DbContext>()
.InstancePerRequest();

Code First Migrations without NuGet

I'm wondering how I can use Entity Framework's Code First Migrations without using NuGet at all (so no commands via the package manager console).
I tried the following:
Database-Context
public sealed class MyContext : DbContext
{
private const string ConnectionStringName = "MyDatabase";
public MyContext()
: base(ConnectionStringName)
{}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
SetupMyModel(modelBuilder);
}
}
Migration-Configuration
public class MyMigrationConfiguration : DbMigrationsConfiguration<MyContext>
{
public MyMigrationConfiguration()
{
AutomaticMigrationsEnabled = false;
}
}
Database-Initializer
System.Data.Entity.Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyContext, MyMigrationConfiguration>());
InitialDatabaseCreation Migration
public class InitialDatabaseCreation : DbMigration, IMigrationMetadata
{
public override void Up()
{
CreateTable("dbo.MyModel",
c => new
{
Id = c.Guid(false, true),
SomeProperty = c.Int(false)
})
.PrimaryKey(x => x.Id);
}
public override void Down()
{
DropTable("dbo.MyModel");
}
public string Id
{
get { return "0001_InitialDatabaseCreation"; }
}
public string Source
{
get { return null; }
}
public string Target
{
get { return Id; }
}
}
As you can see, I wrote a context, a migration configuration and the migration itself.
For the Migration, I'm not sure, if I implemented the IMigrationMetadata correcly. For the Target I just use the id, because I don't want to have any automatic migrations or the ability to use the package manager console. I think, this should be fine here?
I set a breakpoint at the Up method and debugged it, but it does not stop there, which means it doesn't get executed.
Therefor I want to know how to use the EF Code First migrations when writing everything manually.