14

I am on a project where we are using Code First on Entity Framework for our database.

We want to change all of our continuous integration to consume a generated MSI package downstream, but with EF that presents a few complications.

I have tried various things from the web, but most seem to require AutomaticMigrations to be set to true as well as AutomaticMigrationDataLossAllowed (see: http://romiller.com/2012/02/09/running-scripting-migrations-from-code/).

I have tried to replicate what Add-Migration does by looking through .NET reflector but I can't seem to find a way of invoking the command System.Data.Entity.Migrations.AddMigrationCommand that is called through Powershell.

Anyone have any ideas at all on how I can get close to achieving this without doing something extremely messy? It's something I think a lot of people will want to do/have done...

Many thanks in advance!

3
  • Do you want this tool to actually create migration files and add them to the project? Or simply update the database to what your current code-schema is? If it's the latter, why is automatic migrations not an option? If it's the former, can you not just add a build-event which runs the Add-Migration command? Commented Dec 9, 2015 at 9:37
  • @Rob We are trying to create a tool whereby our CI can consume this and generate our migrations for us on the back of a commit. This will enable us to create an MSI automatically from the commit so that the model remains in sync with the migrations and the MSI will not be broken. Due to the scale of our database, automatic migrations will almost certainly lead to automatic data loss. Add-Migration doesn't seem to be accessible from outside the VS shell Commented Dec 9, 2015 at 9:56
  • Could you clarify on "automatic migrations will almost certainly lead to automatic data loss". Why do you think there is any difference between your script running Add-Migration and EF running Add-Migration? Or is this just you want to sometimes run manual migrations and want to avoid mixing auto/manual? Commented Dec 11, 2015 at 12:57

3 Answers 3

9
+75

First of all, no way to run Nuget powershell outside visual studio (it uses DTE). Also, everything you write without Visual Studio need to be inserted in the csproj manually (but not an hard task).

Just to show how it works I send you some line of codes. To test them, create MyDll dll (a project test with context and entities) and then manually enabled migrations on MyDll with Enable-Migrations (just to create Configuration.cs).

After that you can use this piece of code to generate the source code

DbConnectionInfo connectionStringInfo = new DbConnectionInfo(
    "Server=.;Database=MigrationTest;User=sa;Password=dacambiare", "System.Data.SqlClient"); // We shoud retrieve this from App.config

ToolingFacade toolingFacade =  new ToolingFacade(
    "MyDll",   // MigrationAssemblyName. In this case dll should be located in "C:\\Temp\\MigrationTest" dir
    "MyDll",  // ContextAssemblyName. Same as above
    null,
    "C:\\Temp\\MigrationTest",   // Where the dlls are located
    "C:\\Temp\\MigrationTest\\App.config", // Insert the right directory and change with Web.config if required
    "C:\\Temp\\App_Data",
    connectionStringInfo)
{
    LogInfoDelegate = s => {Console.WriteLine(s);},
    LogWarningDelegate = s => { Console.WriteLine("WARNING: " + s); },
    LogVerboseDelegate = s => { Console.WriteLine("VERBOSE: " + s); }
};


ScaffoldedMigration scaffoldedMigration = toolingFacade.Scaffold("MyMigName", "C#", "MyAppNameSpace", false);

Console.WriteLine(scaffoldedMigration.DesignerCode);
Console.WriteLine("==================");
Console.WriteLine(scaffoldedMigration.UserCode);

// Don't forget the resource file that is in the scaffoldedMigration

EDIT
I forgot the namespaces and are not used often so here you are

using System;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Migrations.Design;
Sign up to request clarification or add additional context in comments.

2 Comments

This worked well for me, thanks! I got a bit confused with the MyDll bit, but realised it's simply the name of the project which contains entity framework.Using the above example, I would have put MigrationTest in place of MyDll to match my situation. Interestingly to create connectionStringInfo I had to provide the name of the connection string rather than the connection string itself. So my parameter was mysql which came from <add name="mysql" connectionString="obfuscated details" providerName="MySql.Data.MySqlClient" /> in my config file.
do you have similar code for ef core? i am trying to use dbContext.Database.GetService<IMigrationsScaffolder>(); to remove selectedmigration instead of all migrations that this code does dbContext.Database.GetService<IMigrator>("0");
4

Not a real answer to your question, rather share of my experience: I'd be very careful with migrations generated for you this way. A lot of times when I create migrations (in VS) I review them. I check if the proposed changes are what I want them to be and EF is not trying to do something stupid (like column/table renaming by dropping table and creating a new one).

Also sometimes EF is missing some critical changes - recently I had troubles generating correct migrations when I changed the lengths of nvarchar fields. This also required careful review of the migrations.

And your migrations are code as well. We are doing peer-reviews of migrations before they can go to production. Creating migrations automatically will make these changes less visible and can potentially cause a data loss in production.

And if you are struggling with migrations in team-environment - we've solved this by communication: every time when a developer checks in a new migration - emails is sent to all other devs on this project. Fixed all our problems.

Update Just had a discussion with a colleague and another issue came up - how would you do your local development? Would you create migrations on dev's machine, make sure everything works? Then delete the migration and check-in the code? and then your CI will re-generate your migration? I think this will blow dev's minds when working with EF.

5 Comments

Yup - you will have to prevent checking in migrations from the developers or that will cause problems
@trailmax Our developers (currently) work with automatic migrations, we drop and re-create the database whenever we do model changes in the development environment to prevent the problems that occur when working with code based migrations, within a team and within source control. But you raise some interesting points that I didn't think of, we are in the infancy of our live release using EF and code based migrations and if we had our time again, we probably wouldn't have gone down the EF route. Out of interest how do you handle code based migrations within your team?
@LukeHennerley Automatic migrations never worked for us - too much voodoo. Our team makes changes to the code-first model, issues add-migration command, gets the migration scaffolded - double-checks if it is a correct migration. Applies it to the local dev-database, makes sure everything works as expected. And it is rare that scaffolded migration is correct from the first time. Then executes the integration tests that re-scaffold the DB from scratch. And when everything is fine, this is getting checked-in, followed up by developer emailing the team about the new migration.
@LukeHennerley and if somebody else had a migration in the progress - they will have to re-scaffold their migration. Also our CI runs integration tests that re-build the database from all the migrations and runs integration tests on that database.
@LukeHennerley - Even Rowan Miller of EF team has mentioned that automatic migrations work great in demos not in reality. EF7 has no automatic migrations.
1

You can use the System.Data.Entity.Migrations.Design.MigrationScaffolder class to programmatically generate migrations like so:

[TestMethod]
public void GenerateTestMigration()
{
    var config = new MyDbMigrationsConfiguration();
    var scaffolder = new MigrationScaffolder(config);
    var pendingMigration = scaffolder.Scaffold("TestMigration");
    Trace.WriteLine(pendingMigration.UserCode);
}

I use this and other techniques on our build server to fail the build if there are pending migrations or if a migration needs to be created like so:

using Microsoft.VisualStudio.TestTools.UnitTesting;
using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Design;
using System.Diagnostics;
using System.Linq;

namespace YabbaDabbaDoo
{
    [TestClass]
    public class MigrationTests
    {
        [TestMethod]
        [TestCategory("RunOnBuild")]
        public void VerifyThereAreNoPendingMigrations()
        {
            // Arrange
            var config = new MyDbMigrationsConfiguration();
            var dbMigrator = new DbMigrator(config);

            // Act
            var pendingMigrations = dbMigrator.GetPendingMigrations().ToList();

            // Visual Assertion
            Trace.WriteLine(pendingMigrations);

            // Assert
            Assert.AreEqual(0, pendingMigrations.Count(), "There are pending EF migrations that need to be ran.");
        }

        [TestMethod]
        [TestCategory("RunOnBuild")]
        public void VerifyDatabaseIsCompatibleWithModel()
        {
            // Arrange
            var context = new MyDbContext();

            // Act
            var isCompatible = context.Database.CompatibleWithModel(false);

            // Visual Assertion
            if (!isCompatible)
            {
                var config = new MyDbMigrationsConfiguration();
                var scaffolder = new MigrationScaffolder(config);
                var pendingMigration = scaffolder.Scaffold("MissingMigration");

                Trace.WriteLine("Missing Migration:");
                Trace.WriteLine("");
                Trace.WriteLine(pendingMigration.UserCode);
            }

            // Assert
            Assert.IsTrue(isCompatible, "The EF model is not compatible with the database. An EF migration needs to be created. See output for sample of missing migration.");
        }
    }
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.