Schema independent Entity Framework Code First Migrations - c#

I have troubles using Entity Framework migrations targeting Oracle databases since schema name is included in migrations code and for Oracle, schema name is also user name. My goal is to have schema-independent Code First Migrations (to be able to have one set of migrations for testing and production enviroments).
I have already tried this approach (using Entity Framework 6.1.3):
1) I have schema name in Web.config:
<add key="SchemaName" value="IPR_TEST" />
2) My DbContext takes schema name as a constructor parameter:
public EdistributionDbContext(string schemaName)
: base("EdistributionConnection")
{
_schemaName = schemaName;
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema(_schemaName);
}
3) I had to implement IDbContextFactory for Entity Framework Migrations to be able to create my DbContext which does not have parameterless constructor:
public class MigrationsContextFactory : IDbContextFactory<EdistributionDbContext>
{
public EdistributionDbContext Create()
{
return new EdistributionDbContext(GetSchemaName());
}
}
4) I also configured Migration History Table to be placed within correct schema:
public class EdistributionDbConfiguration : DbConfiguration
{
public EdistributionDbConfiguration()
{
SetDefaultHistoryContext((connection, defaultSchema)
=> new HistoryContext(connection, GetSchemaName()));
}
}
5) I modified code generated for migrations to replace hardcoded schema name. Eg. I replaced CreateTable("IPR_TEST.Users") with CreateTable($"{_schema}.Users"). (_schema field is set according to the value in Web.config).
6) I use MigrateDatabaseToLatestVersion<EdistributionDbContext, MigrationsConfiguration>() database initializer.
Having all this set up, I still have problems when I switch to different schema (eg. via web.config transformation) - an exception is thrown telling me that database does not match my model and AutomaticMigrations are disabled (which is desired). When I try to execute add-migration a new migration is generated where all object should be moved to different schema (eg: MoveTable(name: "IPR_TEST.DistSetGroups", newSchema: "IPR");, which is definitely not desired.
For me it seems that schema name is hard-wired somewhere in model string-hash in migration class (eg. 201509080802305_InitialCreate.resx), ie:
<data name="Target" xml:space="preserve">
<value>H4sIAAAAAAAEAO09227jO... </value>
</data>
It there a way how to tell Code First Migrations to ignore schema name?

You can create a derived DbContext and "override" modelBuilder.HasDefaultSchema(...) in OnModelCreating:
public class TestDbContext : ProductionDbContext
{
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema("TestSchema");
}
}
Then you can create migrations for both contexts. See this question on how to create two migrations in one project.
The downside of this approach is that you have to maintain two seperate migrations. But it gives you the opportunity to adjust the configuration of your TestDbContext.

I was faced to same problem and thanks to your aproach I finally found a solution that seems to work pretty well:
1) I have the schema name in Web.config app settings:
<add key="Schema" value="TEST" />
2) I have a history context:
public class HistoryDbContext : HistoryContext
{
internal static readonly string SCHEMA;
static HistoryDbContext()
{
SCHEMA = ConfigurationManager.AppSettings["Schema"];
}
public HistoryDbContext(DbConnection dbConnection, string defaultSchema)
: base(dbConnection, defaultSchema)
{ }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.HasDefaultSchema(SCHEMA);
}
}
3) I have a db configuration that reference my history db context:
public class MyDbConfiguration : DbConfiguration
{
public MyDbConfiguration()
{
SetDefaultHistoryContext((connection, defaultSchema) => new HistoryDbContext(connection, defaultSchema));
}
}
4) And this is my db context:
public partial class MyDbContext : DbContext
{
public MyDbContext()
: base("name=MyOracleDbContext")
{ }
public static void Initialize()
{
DbConfiguration.SetConfiguration(new MyDbConfiguration());
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDbContext, Migrations.Configuration>());
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.HasDefaultSchema(string.Empty);
}
}
5) Finally I call the Initialize method from the global.asax
protected void Application_Start()
{
MyDbContext.Initialize();
}
The key is to set the default schema of the db context to String.Empty and the schema of the history context to the correct one.
So when you create your migrations they are schema independant: the DefaultSchema variable of the resx of the migration will be blank. But the history db context schema is still correct to allow migrations checks to pass.
I am using the following nugets packages:
<package id="EntityFramework" version="6.2.0" targetFramework="net452" />
<package id="Oracle.ManagedDataAccess" version="12.2.1100" targetFramework="net452" />
<package id="Oracle.ManagedDataAccess.EntityFramework" version="12.2.1100" targetFramework="net452" />
You can then use Oracle migrations with success on different databases.

Related

Entity Framework 6 How to get SQL code of migration

I have a migration set up in a code first EF6 project that simply adds a single column to a table:
public partial class AddHeightToBuildings : DbMigration
{
public override void Up()
{
AddColumn("dbo.buildings", "height", c => c.Int());
}
public override void Down()
{
DropColumn("dbo.buildings", "height");
}
}
And I am running the migration using DbMigrator.Update()
Configuration is the configuration file created after running Enable-Migrations.
TestModel is the DbContext that was created when I reverse engineered the database.
Configuration _configuration = new Configuration();
TestModel _testModel = new TestModel();
var migrator = new DbMigrator(_configuration, _testModel);
migrator.Update("AddHeightToBuildings");
Is it possible in my code to get the SQL code that will be run by "AddHeightToBuildings" before the migration occurs?

Entity Framwork Core Add-Migration fails

I'm trying to use Ef Core in my project.
The structure is a little different, in the sense that I'm not using EfCore insite the WebApi.csproj. In fact I have a different dll. and a DependenciesResolver.dll that handles all my dependency injection.
In my EfCore.dll I've installed both
Microsoft.EntityFrameworkCore.Tools
Microsoft.EntityFrameworkCore.SqlServer
Now when I try to run the command (the dll in which I'm running is the EfCore.dll)
Add-Migration Name
I get this :
An error occurred while accessing the IWebHost on class 'Program'.
Continuing without the application service provider. Error: Object
reference not set to an instance of an object. Unable to create an
object of type 'StoreContext'. Add an implementation of
'IDesignTimeDbContextFactory' to the project, or see
https://go.microsoft.com/fwlink/?linkid=851728 for additional patterns
supported at design time.
The structure of the sln is like this
WebApi | EfCore.dll | DependencyResolver.dll and I want to keep it this way, don't want to permit using EfCore in my WebApi.
What is the resolution for this issue ?
If this helps within the EfCore.dll I have this.
public sealed partial class StoreContext : DbContext, IStoreContext
{
private string _connectionString;
public StoreContext(string connectionString) : base()
{
_connectionString = connectionString;
Database.EnsureCreated();
}
/// db.tbls
public DbSet<Order> Orders { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.AddOrderConfiguration();
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connectionString);
}
}
which is called by DependencyResolver like this
private DependenciesResolver RegisterInfrastructure()
{
_serviceCollection.AddScoped<StoreContext>(factory => new StoreContext(_connectionString));
return this;
}
and the DependencyResolver is then called by the WebApi
Please have a look at this: https://learn.microsoft.com/en-us/ef/core/miscellaneous/cli/dbcontext-creation
The error message clearly specifies the EF Core tools can't create an instance of your Context at design-time.
If you can define a constructor with no parameters for your StoreContext that would work, otherwise you need tell the tools how to create an instance of your context at design-time by defining a class that implements the IDesignTimeDbContextFactory interface.

EF 6 Code First __MigrationHistory in dbo schema by default

I am new to Code first Entity framework, when logging into the database after running my app for the first time I got a little confused when I saw the "__MigrationHistory" table.
I now understand the need for this table, but do not like it being in the standard dbo schema within the user table, I think its obtrusive and a risk.
My first thought was to move it to the system folder. When researching how to achieve this within the EF context all I could find is how to move it from system to dbo.
I now get the feeling __MigrationHistory should by default be created within the system folder... is this the case?
How can I configure my context to manage/reference the migration history table within the system folder by default?
Here is my context, am I doing something wrong or missing some configuration?
public class MyContext : DbContext, IDataContext
{
public IDbSet<Entity> Entities { get; set; }
public MyContext()
: base("ConnectionString")
{
}
public new IDbSet<TEntity> Set<TEntity>() where TEntity : class
{
return base.Set<TEntity>();
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
}
}
There is a technique for moving __MigrationHistory. That table has it's own context (System.Data.Entity.Migrations.History.HistoryContext) that you can override:
public class MyHistoryContext : HistoryContext
{
public MyHistoryContext(DbConnection dbConnection, string defaultSchema)
: base(dbConnection, defaultSchema)
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<HistoryRow>().ToTable(tableName: "MigrationHistory", schemaName: "admin");
modelBuilder.Entity<HistoryRow>().Property(p => p.MigrationId).HasColumnName("Migration_ID");
}
}
Then you need to register it:
public class ModelConfiguration : DbConfiguration
{
public ModelConfiguration()
{
this.SetHistoryContext("System.Data.SqlClient",
(connection, defaultSchema) => new MyHistoryContext(connection, defaultSchema));
}
}
You could try executing EXEC sys.sp_MS_marksystemobject __MigrationHistory in your seed method using context.Database.ExecuteSqlCommand();

How to customize the pluralization EF6

I'm trying to use the interface IPluralizationService to customize the pluralization of my entities without success!
Necessary that all entities are pluralized using the Inflector library.
Attempts
class Config : DbConfiguration
{
public Config()
{
SetPluralizationService(new CustomPluralization());
}
}
class CustomPluralization : IPluralizationService
{
public string Pluralize(string word)
{
return word.Pluralize();
}
public string Singularize(string word)
{
return word.Singularize();
}
}
In my context;
modelBuilder.Configurations.Add<Config>(.. ?? ..)
According to msdn's article Code-Based Configuration (EF6 onwards) section Using DbConfiguration, it is sufficient to simply place your DbConfiguration class in the same assembly as your DbContext class.
Nevertheless you can specify it manually, as explained in the article by using either the config file or an annotation in your DbContext.
Config file:
<entityFramework codeConfigurationType="MyNamespace.MyDbConfiguration, MyAssembly">
<!-- Your EF config -->
</entityFramework>
Annotation:
[DbConfigurationType("MyNamespace.MyDbConfiguration, MyAssembly")]
public class MyContextContext : DbContext
{
}
Or
[DbConfigurationType(typeof(MyDbConfiguration))]
public class MyContextContext : DbContext
{
}
Note:
These examples are directly from the article I linked

EF code first: inherited dbcontext creates two databases

I'm trying to create a base dbcontext that contains all the common entities that will always be reused in multiple projects, like pages, users, roles, navigation etc.
In doing so I have a ContextBase class that inherits DbContext and defines all the DbSets that I want. Then I have a Context class that inherits ContextBase where I define project specific DbSets. The classes are defined as follows:
public class ContextBase : DbContext
{
public virtual DbSet<User> Users { get; set; }
//more sets
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Configurations.Add(new UsersConfiguration());
//add more configurations
}
}
public class Context : ContextBase
{
public DbSet<Building> Buildings { get; set; }
//some more project specific sets
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Configurations.Add(new BuildingsConfiguration());
//add more project specific configs
}
}
In my global.asax:
Database.SetInitializer(new MigrateDatabaseToLatestVersion<Context, Configuration>());
where Configuration referes to a class inheriting DbMigrationsConfiguration and overriding the Seed method.
The two context classes are defined in the same namespace, but cross assembly (in order that I may update the base project in multiple existing projects without touching the project specific code) - not sure if this is relevant.
MY PROBLEM:
When running this code, it works fine, but when looking in the Database, it actually creates two different databases!! One containing all the base entity tables and one containing BOTH base and custom tables. CRUD operations are only performed on the custom version (which is obviousely what I want), but why does it create the schema of the other one as well?
Any help is appreciated, thanks!
UPDATE:
The following code is what I ended up with. It isn't ideal, but it works. I would still love to get feedback on ways to improve this, but in the meantime I hope this helps further the process. I REALLY DO NOT RECOMMEND DOING THIS! It is extremely error prone and very frustrating to debug. I'm merely posting this to see if there is any better ideas or implementations to achieve this.
One (but not the only) issue still existing is that the MVC views have to be manually added to projects. I've added it to the Nuget package, but it takes 2 to 3 hours to apply a nuget package with so many files when VS is connected to TFS. With some more work and a custom View engine the views can be precompiled (http://blog.davidebbo.com/2011/06/precompile-your-mvc-views-using.html).
The solution is split into the Base Framework projects and the Custom projects (each category includes its own models and repository pattern). The framework projects are packaged up in a Nuget package and then installed in any custom projects allowing the common functionality of any project like user, role and permission management, content management, etc (often referred to as the Boiler Plate) to be easily added to any new projects. This allows any improvements of the boilerplate to be migrated in any existing custom projects.
Custom Database Initializer:
public class MyMigrateDatabaseToLatestVersion : IDatabaseInitializer<Context>
{
public void InitializeDatabase(Context context)
{
//create the base migrator
var baseConfig = new FrameworkConfiguration();
var migratorBase = new DbMigrator(baseConfig);
//create the custom migrator
var customConfig = new Configuration();
var migratorCustom = new DbMigrator(customConfig);
//now I need to check what migrations have not yet been applied
//and then run them in the correct order
if (migratorBase.GetPendingMigrations().Count() > 0)
{
try
{
migratorBase.Update();
}
catch (System.Data.Entity.Migrations.Infrastructure.AutomaticMigrationsDisabledException)
{
//if an error occured, the seed would not have run, so we run it again.
baseConfig.RunSeed(context);
}
}
if (migratorCustom.GetPendingMigrations().Count() > 0)
{
try
{
migratorCustom.Update();
}
catch (System.Data.Entity.Migrations.Infrastructure.AutomaticMigrationsDisabledException)
{
//if an error occured, the seed would not have run, so we run it again.
customConfig.RunSeed(context);
}
}
}
}
Framework's DB Migrations Configuration:
public class FrameworkConfiguration: DbMigrationsConfiguration<Repository.ContextBase>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
public void RunSeed(Repository.ContextBase context)
{
Seed(context);
}
protected override void Seed(Repository.ContextBase context)
{
// This method will be called at every app start so it should use the AddOrUpdate method rather than just Add.
FrameworkDatabaseSeed.Seed(context);
}
}
Custom Project's DB Migrations Configuration:
public class Configuration : DbMigrationsConfiguration<Repository.Context>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
public void RunSeed(Repository.Context context)
{
Seed(context);
}
protected override void Seed(Repository.Context context)
{
// This method will be called at every app start so it should use the AddOrUpdate method rather than just Add.
CustomDatabaseSeed.Seed(context);
}
}
The custom DbContext
//nothing special here, simply inherit ContextBase, IContext interface is purely for DI
public class Context : ContextBase, IContext
{
//Add the custom DBsets, i.e.
public DbSet<Chart> Charts { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
//Assign the model configs, i.e.
modelBuilder.Configurations.Add(new ChartConfiguration());
}
}
Framework DbContext:
//again nothing special
public class ContextBase: DbContext
{
//example DbSet's
public virtual DbSet<Models.User> Users { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder);
}
In the global.asax AppStart:
//first remove the base context initialiser
Database.SetInitializer<ContextBase>(null);
//set the inherited context initializer
Database.SetInitializer(new MyMigrateDatabaseToLatestVersion());
In the web.config:
<connectionStrings>
<!--put the exact same connection string twice here and name it the same as the base and overridden context. That way they point to the same database. -->
<add name="Context" connectionString="Data Source=.\SQLEXPRESS; Initial Catalog=CMS2013; Integrated Security=SSPI;MultipleActiveResultSets=true;" providerName="System.Data.SqlClient"/>
<add name="ContextBase" connectionString="Data Source=.\SQLEXPRESS; Initial Catalog=CMS2013; Integrated Security=SSPI;MultipleActiveResultSets=true;" providerName="System.Data.SqlClient"/>
</connectionStrings>
(from the comments)
You're creating ContextBase objects directly, apparently as new T() in a generic method with ContextBase as a generic type argument, so any initialisers for ContextBase also run. To prevent creating ContextBase objects (if it should never be instantiated directly, if the derived context should always be used), you can mark the class as abstract.
Your ContextBase seems to have an initializer as well.. You can remove this by
Database.SetInitializer<ContextBase>(null);

Categories