John C
John C

Reputation: 238

How to automate Package Manager Console in Visual Studio 2013

My specific problem is how can I automate "add-migration" in a build process for the Entity Framework. In researching this, it seems the mostly likely approach is something along the lines of automating these steps

  1. Open a solution in Visual Studio 2013
  2. Execute "Add-Migration blahblah" in the Package Manager Console (most likely via an add-in vsextention)
  3. Close the solution

This initial approach is based on my own research and this question, the powershell script ultimately behind Add-Migration requires quite a bit of set-up to run. Visual Studio performs that setup automatically when creating the Package Manager Console and making the DTE object available. I would prefer not to attempt to duplicate that setup outside of Visual Studio.

One possible path to a solution is this unanswered stack overflow question

In researching the NuGet API, it does not appear to have a "send this text and it will be run like it was typed in the console". I am not clear on the lines between Visual Studio vs NuGet so I am not sure this is something that would be there.

I am able to find the "Pacakage Manager Console" ironically enough via "$dte.Windows" command in the Package Manager Console but in a VS 2013 window, that collection gives me objects which are "Microsoft.VisualStudio.Platform.WindowManagement.DTE.WindowBase". If there is a way stuff text into it, I think I need to get it to be a NuGetConsole.Implementation.PowerConsoleToolWindow" through reviewing the source code I am not clear how the text would stuffed but I am not at all familiar with what I am seeing.

Worst case, I will fall back to trying to stuff keys to it along the lines of this question but would prefer not to since that will substantially complicate the automation surrounding the build process.

All of that being said,

  1. Is it possible to stream commands via code to the Package Manager Console in Visual Studio which is fully initialized and able to support an Entity Framework "add-migration" command?

Thanks for any suggestions, advice, help, non-abuse in advance,

John

Upvotes: 6

Views: 4553

Answers (2)

Christoph Fink
Christoph Fink

Reputation: 23113

This is an update to John's answer whom I have to thank for the "hard part", but here is a complete example which creates a migration and adds that migration to the supplied project (project must be built before) the same way as Add-Migration InitialBase -IgnoreChanges would:

public void ScaffoldedMigration(EnvDTE.Project project)
{
    var migrationsNamespace = project.Properties.Cast<Property>()
         .First(p => p.Name == "RootNamespace").Value.ToString() + ".Migrations";

    var assemblyName = project.Properties.Cast<Property>()
                           .First(p => p.Name == "AssemblyName").Value.ToString();
    var rootPath = Path.GetDirectoryName(project.FullName);
    var assemblyPath = Path.Combine(rootPath, "bin", assemblyName + ".dll");
    var migrationAssembly = Assembly.Load(File.ReadAllBytes(assemblyPath));
    Type dbContext = null;
    foreach(var type in migrationAssembly.GetTypes())
    {
        if(type.IsSubclassOf(typeof(DbContext)))
        {
            dbContext = type;
            break;
        }
    }

    var migrationsConfiguration = new DbMigrationsConfiguration()
        {
            AutomaticMigrationDataLossAllowed = false,
            AutomaticMigrationsEnabled = false,
            CodeGenerator = new CSharpMigrationCodeGenerator(),
            ContextType = dbContext,
            ContextKey = migrationsNamespace + ".Configuration",
            MigrationsAssembly = migrationAssembly,
            MigrationsDirectory = "Migrations",
            MigrationsNamespace = migrationsNamespace
        };

    var dbi = new System.Data.Entity.Infrastructure
                     .DbConnectionInfo("ConnectionString", "System.Data.SqlClient");
    migrationsConfiguration.TargetDatabase = dbi;

    var scaffolder = new MigrationScaffolder(migrationsConfiguration);
    ScaffoldedMigration migration = scaffolder.Scaffold("InitialBase", true);

    var migrationFile = Path.Combine(rootPath, migration.Directory,
                            migration.MigrationId + ".cs");
    File.WriteAllText(migrationFile, migration.UserCode);
    var migrationItem = project.ProjectItems.AddFromFile(migrationFile);

    var designerFile = Path.Combine(rootPath, migration.Directory,
                           migration.MigrationId + ".Designer.cs");
    File.WriteAllText(designerFile, migration.DesignerCode);
    var designerItem = project.ProjectItems.AddFromFile(migrationFile);
    foreach(Property prop in designerItem.Properties)
    {
        if (prop.Name == "DependentUpon")
            prop.Value = Path.GetFileName(migrationFile);
    }

    var resxFile = Path.Combine(rootPath, migration.Directory,
                       migration.MigrationId + ".resx");
    using (ResXResourceWriter resx = new ResXResourceWriter(resxFile))
    {
        foreach (var kvp in migration.Resources)
            resx.AddResource(kvp.Key, kvp.Value);
    }
    var resxItem = project.ProjectItems.AddFromFile(resxFile);
    foreach (Property prop in resxItem.Properties)
    {
        if (prop.Name == "DependentUpon")
            prop.Value = Path.GetFileName(migrationFile);
    }
}

I execute this in my project template's IWizard implementation where I run a migration with IgnoreChanges, because of shared entites with the base project. Change scaffolder.Scaffold("InitialBase", true) to scaffolder.Scaffold("InitialBase", false) if you want to include the changes.

Upvotes: 0

John C
John C

Reputation: 238

The approach that worked for me was to trace into the entity framework code starting in with the AddMigrationCommand.cs in the EntityFramework.Powershell project and find the hooks into the EntityFramework project and then make those hooks work so there is no Powershell dependency.

You can get something like...

    public static void RunIt(EnvDTE.Project project, Type dbContext, Assembly migrationAssembly, string migrationDirectory,
        string migrationsNamespace, string contextKey, string migrationName)
    {
        DbMigrationsConfiguration migrationsConfiguration = new DbMigrationsConfiguration();
        migrationsConfiguration.AutomaticMigrationDataLossAllowed = false;
        migrationsConfiguration.AutomaticMigrationsEnabled = false;
        migrationsConfiguration.CodeGenerator = new CSharpMigrationCodeGenerator(); //same as default
        migrationsConfiguration.ContextType = dbContext; //data
        migrationsConfiguration.ContextKey = contextKey;
        migrationsConfiguration.MigrationsAssembly = migrationAssembly;
        migrationsConfiguration.MigrationsDirectory = migrationDirectory;
        migrationsConfiguration.MigrationsNamespace = migrationsNamespace;

        System.Data.Entity.Infrastructure.DbConnectionInfo dbi = new System.Data.Entity.Infrastructure.DbConnectionInfo("DataContext");
        migrationsConfiguration.TargetDatabase = dbi;

        MigrationScaffolder ms = new MigrationScaffolder(migrationsConfiguration);

        ScaffoldedMigration sf = ms.Scaffold(migrationName, false);

    }

You can use this question to get to the dte object and from there to find the project object to pass into the call.

Upvotes: 5

Related Questions