15

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?

Jan Palas
  • 1,865
  • 1
  • 23
  • 35
  • I'm sure you have tried this, but with SQL server, if you simply do not specify the schema in your model bindings it just uses whichever schema is the default for the user. – Ben Robinson Sep 08 '15 at 11:56
  • 2
    Thank you @Ben, your comment might lead to a solution of my problem. When you omit `modelBuilder.HasDefaultSchema("SCHEMA_NAME");` Entity Framework sets "dbo" as the default schema. However, now I tried `modelBuilder.HasDefaultSchema(string.Empty);` and it seems that now the default schema for the user is (correctly) used. Sadly, `Oracle.ManagedDataAccess.EntityFramework.OracleMigrationSqlGenerator` which is used for generating SQL for migrations has problem with `string.Empty`-schema-name and throws exceptions when generating migration's SQL... But that is another problem... – Jan Palas Sep 08 '15 at 12:23
  • Hi Jan Palas, do you have found any solutions about this problem ? I have exaclty the same issue, I use EF Migration with Oracle and when I changes my schema all migration script contains MoveTable... – Vincenzo Dec 24 '15 at 13:17
  • @toregua: I did not find a solution, but a workaround: I use migrations with a particular schema name that applies to our testing environment. That automatically updates our DB schema after a deploy to a testing environment when a new migration(s) exist. For a production environment, I use migrations to generate a DB-update script (`update-database -script -sourceMigration Migration13 -targetMigration Migration18`) where I change schema-name afterwards. When deploying to production environment, I execute DB update script manually. (sorry for the late answer, was on vacation) – Jan Palas Jan 04 '16 at 09:49
  • @toregua: Optionally, you may use some alternative `OracleMigrationSqlGenerator` that can work with `string.Empty`-schema-name. I recall that one from Devart (https://www.devart.com/dotconnect/oracle/) may do this, but my company decided not to buy this library, thus I did not test it. – Jan Palas Jan 04 '16 at 09:54
  • @JanPalas I assume no better solution has come up in the mean time :( I'm currently having exactly the same issue. I'd like to use the default connection schema rather than specifying one...as that causes issue in the case u want to do export/import from the DB Schema. – Juri Sep 26 '16 at 15:35
  • @Juri No, I still use workaround described in my comment above (the one from 4th Januray). – Jan Palas Sep 29 '16 at 08:35
  • Hi, this is 7months on from your last comment, are you still using a workaround? – Eniola May 01 '17 at 13:14
  • @Eniola Yes. But I have not done further research since then because I am no longer interested in the project where I was dealing this problem. – Jan Palas May 02 '17 at 08:00

2 Answers2

4

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.

Community
  • 1
  • 1
PeterB
  • 886
  • 10
  • 18
2

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.

Cédric
  • 21
  • 4
  • Confirmed to work. Setting the schema-name only on the HistoryDbContext is indeed the difference that makes the difference and it makes everything snap into place. I would like to underline the fact that if you use fluent configurations/data-annotations and you want to employ this approach you *must* ensure that you don't set the schema-name anywhere in your fluent-configs/data-annotations (just leave the schema name to the empty string). – XDS Feb 11 '18 at 18:29
  • Actually it doesn't work: EF throws 'seqOwner cannot be null' exception on db initialize. – lucacelenza Oct 15 '19 at 11:25