Is there an object model for automating migrations? - entity-framework-4

Using EF migrations I wish to automate running a migration against an environment. I know I can do this via powershell call to Update-Database, but is there a way of doing this via the framework without having to launch an external process?

Yes, in your DbContext:
public static void SetInitializer()
{
Database.SetInitializer(new MigrateDatabaseToLatestVersion<DataContext, Configuration>());
}
Where Configuration is:
internal sealed class Configuration : DbMigrationsConfiguration<DataContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
}
}
You'll still need to release a Migration file for migrations that will make changes that will result in dataloss (unless you set AutomaticMigrationDataLossAllowed to true in the Configuration class - danger!) but you can make these files using the Add-Migration command in PMC.

Related

Running init script on oracle test container with system privileges

I am struggling with org.testcontainers:oracle-xe:1.14.3.
I am trying to run a test intended to verify schema creation and migration, however I'm getting stuck at the InitScript, when trying to initialize the users for the test with the users 'sys as sysdba'.
#Before
public void setUp() {
oracleContainer = new OracleContainer("oracleinanutshell/oracle-xe-11g")
.withUsername("sys as sysdba")
.withInitScript("oracle-initscript.sql");
oracleContainer.start();
}
The above seems to be able to connect, but execution of the init script fails with a
ORA-01109: database not open
Using the 'system' user in the above does not provide the InitScript connection with sysdba privileges, but result in an open database.
I'm looking for a solution that will allow me to initialize multiple users prior to a test. This initialization has grants that requires sysdba privileges. The test, in which some SQL scripts are executed, requires that both users are created in the database and can connect to the database.
In my case I'm using
oracleContainer = new OracleContainer("gvenzl/oracle-xe:18.4.0-slim")
.withUsername("test")
.withPassword("test")
.addEnv("ORACLE_PASSWORD", "s") // Sys password is required
.withCopyFileToContainer(MountableFile.forHostPath("oracle-initscript.sql"), "/container-entrypoint-initdb.d/init.sql")
gvenzl/oracle-xe is the default image used by the org.testcontainers.oracle-xe library.
The documentation for this image describes how to call initialization SQL on DB start and it works great.
Hard to say what is the issue but here are some tricks:
maybe "sys as sysdba" is not valid in your code, documentation is not clear about the usage
maybe withLogConsumer can provide some clues what's wrong
I recommend the image gvenzl/oracle-xe,
in some cases withInitScript may not work properly.
it is useful to test the init script on the container started manually
I finished on end with this approach:
as sys admin created two different schema/user)
#SpringBootTest(classes = Main.class)
#Import(DbConfiguration.class)
#Testcontainers
public class ServiceIntegrationTest {
#Container
public static final OracleContainer oracleContainer =
new OracleContainer("gvenzl/oracle-xe:21-slim-faststart");
}
import static com.integrationtests.local_test.service.IntegrationTest.oracleContainer;
#TestConfiguration
public class DbConfiguration {
static final String DEFAULT_SYS_USER = "sys as sysdba";
private static final String ENTITY_MANAGER_FACTORY = "entityManagerFactory";
#Bean
public DataSource getDataSource() {
DataSourceBuilder<?> dataSourceBuilder = DataSourceBuilder.create();
dataSourceBuilder.driverClassName("oracle.jdbc.OracleDriver");
dataSourceBuilder.url(oracleContainer.getJdbcUrl());
dataSourceBuilder.username(DEFAULT_SYS_USER);
dataSourceBuilder.password(oracleContainer.getPassword());
return dataSourceBuilder.build();
}
Also in application.yaml put scripts
spring:
datasource:
initialization-mode: always
schema:
- classpath:/sql/init_schemas/USER_ONE.sql
- classpath:/sql/init_schemas/USER_TWOT.sql

Cannot connect to my edmx with my data context

I've not used VS MVC for a while but I'm writing a project which requires connecting to a Sql database which I've installed as an edmx file SwitchDB.edmx in my DAL folder. In the past I've set up my data context file which I then use to reference the data in my controller, the model help me to order the data in the correct way.
This is how my data context file looks
namespace Switches.DAL
{
public class SwitchContext : DbContext
{
public SwitchContext()
: base("DefaultConnection")
{ }
public DbSet<Switch_List> SwitchList { get; set; }
}
}
I've set up the "DefaultConnection" in my Web.config under connectionStrings and my model Switch_List.cs has the file settings. When I declare the DB context in my controller as below
private SwitchContext db = new SwitchContext();
Then I would expect to reference the SwitchContext to get my data, like this
var switches= db.SwitchList .ToList();
However, when I run the project and reference db in debug I get the following error message 'the function evaluation requires all threads to run'. The DB context SwitchContext is clearly not getting access to the Switch.edmx so what am I forgetting?
I had a similar problem, but you should see the connection properties using an IDE button (to re-evaluate the expression).
However, when you get to the part of db.SwitchList.ToList() does it generate any exceptions?

Enity Framework 6.0.0.0 : Unable to generate an explicit migration... with Code-based migration

The situation is :
Per tenant db.
Non-automatic Code-based based migration. The initial creation of the dbs is through Code First too. There are no preexisting dbs in the current scenario.
The exact db is not known during the generation of migration script becase there are many. The only thing I have is code - the Initial migration script based on the model in the beginning and the model with some modifications.
Existing dbs generated with initial script - everything works fine. Here is how I reproduce the problem :
Run PS command : Add-Migration -Name Initial -StartUpProjectName MyApp.Web -ProjectName MyApp.Migrations -ConfigurationTypeName "MyApp.Migrations.MyMigrationConfiguration" -ConnectionString "Data Source=.\SQLEXPRESS;Database=demo-db;Trusted_Connection=False;User ID=x;Password=x" -ConnectionProviderName "System.Data.SqlClient" - Works Fine
Run the app and the db is created. Stop the app. Make modification to the model.
Run PS command : Add-Migration -Name MySecondMigration -StartUpProjectName MyApp.Web -ProjectName MyApp.Migrations -ConfigurationTypeName "MyApp.Migrations.MyMigrationConfiguration" -ConnectionString "Data Source=.\SQLEXPRESS;Database=demo-db;Trusted_Connection=False;User ID=x;Password=x" -ConnectionProviderName "System.Data.SqlClient" - Boom.
Subtlety - The migrator does not even connect to MSSQL to check if the db even exists or has dbo.__MigrationHistory! I have goen further and turned off the MSSQL service! - It does not matter if I enter false db name or not or I turn off the entire MSSQL server - the error is the same and no connection is made. So how does it know a migration is pending when it does not check the database? I assume a bug.
I have __dbo.MigrationHistory created already with the Initial migration in it.
Error : Unable to generate an explicit migration because the following explicit migrations are pending: [201402121953301_Initial]. Apply the pending explicit migrations before attempting to generate a new explicit migration.
Here is my Configuration - nothing special :
public class MyMigrationConfiguration : DbMigrationsConfiguration<MyMigrationContext>
{
public MyMigrationConfiguration()
{
AutomaticMigrationsEnabled = false;
AutomaticMigrationDataLossAllowed = false;
MigrationsNamespace = "---";
MigrationsDirectory = "---";
}
}
Here is the method with wich I create the dbs :
public void CreateOrUpdateDb(string DbName)
{
try
{
string connectionString = _connectionStringProvider.GetConnectionString(DbName);
DbMigrationsConfiguration cfg = CreateMigrationsConfig(connectionString);
cfg.AutomaticMigrationsEnabled = false;
cfg.AutomaticMigrationDataLossAllowed = false;
DbMigrator dbMigrator = new DbMigrator(cfg);
dbMigrator.Update();
}
catch (MigrationsException exception)
{
_logger.Error(string.Format("Error creating database '{0}'",DbName), exception);
}
}
I have already searched and read whatever I can find on the web but most examples include the standart Configuration.cs and the like. Nobody explains why should I run Enable-Migrations because it creates Configuration.cs but I provide the Configuration class myself and do not need this other class.
Here is the exception stack :
> System.Data.Entity.Migrations.Infrastructure.MigrationsPendingException: Unable to generate an explicit migration because the following explicit migrations are pending: [201402121953301_Initial]. Apply the pending explicit migrations before attempting to generate a new explicit migration.
at System.Data.Entity.Migrations.DbMigrator.Scaffold(String migrationName, String namespace, Boolean ignoreChanges)
at System.Data.Entity.Migrations.Design.MigrationScaffolder.Scaffold(String migrationName, Boolean ignoreChanges)
at System.Data.Entity.Migrations.Design.ToolingFacade.ScaffoldRunner.Scaffold(MigrationScaffolder scaffolder)
at System.Data.Entity.Migrations.Design.ToolingFacade.ScaffoldRunner.Run()
at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)
at System.AppDomain.DoCallBack(CrossAppDomainDelegate callBackDelegate)
at System.Data.Entity.Migrations.Design.ToolingFacade.Run(BaseRunner runner)
at System.Data.Entity.Migrations.Design.ToolingFacade.Scaffold(String migrationName, String language, String rootNamespace, Boolean ignoreChanges)
at System.Data.Entity.Migrations.AddMigrationCommand.Execute(String name, Boolean force, Boolean ignoreChanges)
at System.Data.Entity.Migrations.AddMigrationCommand.<>c__DisplayClass2.<.ctor>b__0()
Is there any way to force the migrator to make the script without telling me that there are some pending migrations even though there are not? This is 100% bug in EF but I do not know how to get around it.
We have found the answer - a smart colleague of mine and with the help of Reflector.
Inside EntityFramework.dll there is the class
System.Data.Entity.Migrations.DbMigrator with a method
Scaffold(string migrationName, string #namespace, bool ignoreChanges)
Which makes a call this.GetPendingMigrations
Which method calls another method on another class HistoryRepository.CreateHistoryQuery(HistoryContext context, string contextKey = null) with the parameter contextKey == NULL
But there inside this method this happens
contextKey = !string.IsNullOrWhiteSpace(contextKey) ? contextKey.RestrictTo(this._contextKeyMaxLength) : this._contextKey;
and the contextKey is not null anymore. IT actually becomes the type(YourInheritedDbMigrationConfiguration).ToString()
Where is the problem? The problem lies in the fact that
I use two different DbMigrationConfiguration classes
because the migration assembly is separated from the core of the application which makes
them two differently 'fully qualified named classes' which at the end
result in two different ContextKeys in the dbo._MigrationHistory table for the DbMigrationConfiguration which is used for the generation of the migration script.
So when I make the initial migration script and start the site and the site applies the script it is ok but in the dbo._MigrationHistory the ContextKey is "MyDbConigClass1". After that when I try Add-Migration which uses another DbMigrationConfiguration class the ContextKeys does not match - BOOM!
The solution : Added the same ContextKey = "MyTenantDb" in both of my configuration classes and everything started to work again because the type names no longer got involved but the DbMigrator used my custom ContextKey :)

ASP.NET Entity Framework Code First Migrarion

This a ASP.NET MVC with Entity Framework 6.1 Code First application.
I need my application to CREATE the database if it doesn't exists and seed the tables with some data. Also I need to Migrate the database on application start if the model has changed using MigrateDatabaseToLatestVersion.
On the Application_Start i have:
protected void Application_Start()
{
Database.SetInitializer(new MyAppDataInitializer());
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyAppDataContext, MyApp.Model.Migrations.Configuration>());
}
The initializer:
public class MyAppDataInitializer: CreateDatabaseIfNotExists<MyAppDataContext>
{
protected override void Seed(MyAppDataContext context)
{
Code to add some "CREATION DATA"
}
}
Here is the Migration Configuration class:
public class Configuration : DbMigrationsConfiguration<MyApp.Model.DataContext.MyAppDataContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = true;
AutomaticMigrationDataLossAllowed = true;
ContextKey = "MyApp.Model.DataContext.MyAppDataContext";
}
protected override void Seed(MyApp.Model.DataContext.MyAppDataContext context)
{
Code to add some "MIGRATION DATA"
}
}
When my application starts it works as expected except for one thing:
If the database exists, it works perfect, it changes the database according to the new model and it seeds the database with the "MIGRATION DATA".
If the database doesn't exists it creates the database but it only seeds the database with the "MIGRATION DATA" but not with the "CREATION DATA". The SEED method on the inicializer is not called.
I'm sure there is something I'm doing wrong here, but I can't figure out how can I create the database when it doesn't exists and seed some "init" data that it only insert for the first time.
Then when changing the model it only changes the database and add the data I setup in the migration seed method in case I need it.
Could you help me with this?
Thanks in advance.
You're overwriting the initializer. It's a "set" not an "add" initializer and there can only be one..
protected void Application_Start()
{
Database.SetInitializer(new MyAppDataInitializer());
// Now the initializer gets "re-set" again and MyAppDataInitializer is not used at all
Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyAppDataContext, MyApp.Model.Migrations.Configuration>());
}

Entity Framework - Code First - Multiple projects

I have one solution with three projects in it:
ToDo.Web
ToDo.Core
ToDo.Data
ToDo.Web is my startup project and is an MVC 4 solution.
ToDo.Core is a Class Library solution and it contains all my classes like User, Task etc.
ToDo.Data is my data repository and contains my ToDoContext model.
My ToDoModel model looks like this:
namespace ToDo.Data
{
public class ToDoModel : DbContext
{
public DbSet<Task> Tasks { get; set; }
public DbSet<User> Users { get; set; }
}
}
I am trying to get the project to create my database on my locally installed SQL Server 2012. So in my web.config (in the ToDo.Web project) I have the following connectionstring:
<add name="ToDo.Data.ToDoModel" connectionString="Data Source=XXX\YYY;Integrated Security=SSPI;initial catalog=ToDo" providerName="System.Data.SqlClient" />
I then try to execute the following command from Package Manager Console:
Enable-Migrations -projectname ToDo.Data -StartUpProjectName ToDo.Web
The command executes nicely with no errors. And I can see a Migrations folder in my ToDo.Data project. But it only contains a Configuration.cs file and no information about my model. And I cannot see the database being created on my SQL server either.
I have tried creating a simple Console application, using the same connection string where it works nicely.
I have tried adding the connection string to my app.config in the ToDo.Data project without luck.
Anyone who can guide me in the right direction?

Resources