shkiper
shkiper

Reputation: 297

EntityFramework 7 (EF7) Migrations. DbContext and StartUp Project are in different assemblies

I'm trying to use migrations in EF7 with entityframework.commands. But my DbContext is in different assembly with Start-up project (asp.net mvc is an start-up project and Core.Implementation has a DbContex).

dnx . ef migration add MyMigration -c MyContext

System.InvalidOperationException: No DbContext named 'MyContext' was found.

I've tried to use namespace to point to other assembly but it didn't work either. Is it possible at all? Or I just have to put my context in assembly where ef7 command is?

Upvotes: 18

Views: 9353

Answers (4)

Guillermo Rdguez Glez
Guillermo Rdguez Glez

Reputation: 228

I have a workaround, but i'm using EFCore 1.0.0 so you must migrate. Below the codesnippets you need to make it work:

The idea is to have a central datacontext: MigrationsDataContext to handle whole database migrations. So you can place this migration in a separated project dedicated only for migrating database. You only must make MigrationsDataContext see others projects containing required datacontexts. This way, you can instantiate each other datacontext and call OnModelCreating method manually passing current DbContext's ModelBuilder as parameter.

public class MigrationsDataContext : DbContext
{
    private readonly Dictionary<Type, DbContextOptions> dbCtxOpts;
    private readonly IEnumerable<Type> dbCtxTypes = new[] {
        typeof(CoreDataContext),
        typeof(DbContext1),
        typeof(DbContext2),
    };

    public MigrationsDataContext(DbContextOptions<MigrationsDataContext> options,
        IEnumerable<DbContextOptions> dbCtxOpts)
        :base(options)
    {
        this.dbCtxOpts = dbCtxOpts
            .GroupBy(o => o.ContextType)
            .ToDictionary(g => g.Key, g => g.First());
    }

    public MigrationsDataContext()
    { }

    protected override void OnModelCreating(ModelBuilder builder)
    {
        base.OnModelCreating(builder);

        foreach (var db in dbCtxTypes.Select(t => new { Type = t, Instance = Activator.CreateInstance(t, dbCtxOpts[t]) }))
        {
            var configurator = db.Type.GetMethod(nameof(OnModelCreating), BindingFlags.NonPublic | BindingFlags.Instance);

            configurator.Invoke(db.Instance, new[] { builder });
        }
    }
}

In Order to Receive all DbContextOptions from constructor, you meed to register the whole bundle of DbContextOptions<T> as DbContextOptions this is achieved by calling the following method in ConfigureServices in Startup.cs:

    public static IServiceCollection AddBundle<T>(this IServiceCollection services)
    {
        var baseType = typeof(T);

        var bundle = services
            .Where(d => baseType.GetTypeInfo().IsAssignableFrom(d.ServiceType))
            .Select(d => d.ServiceType)
            .ToList();

        foreach (var ctxType in bundle)
            services.AddScoped(baseType, s => s.GetService(ctxType));

        return services;
    }

Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        services
            .AddEntityFrameworkSqlServer()
            .AddDbContext<CoreDataContext>(ConfigEf)
            .AddDbContext<DbContext1>(ConfigEf)
            .AddDbContext<DbContext2>(ConfigEf)
            .AddDbContext<MigrationsDataContext>(ConfigEf)
            .AddBundle<DbContextOptions>()
            .AddBundle<DbContext>();
...

That's all. Now you manage migrations this way:

move to you Migrations project, adjacent to MyWebApp project,

`$> dotnet ef --startup-project ../MyWebApp migrations add Initial --context Migrations.MigrationsDataContext`

Hope this help.

Upvotes: 0

VahidN
VahidN

Reputation: 19156

For ASP.NET Core 1.0 and EF Core 1.0

Suppose we want to move our context to Core1RtmEmptyTest.DataLayer project.

First enable EF tools for this sub project by modifying its project.json file:

{
     // same as before

    "tools": {
        "Microsoft.EntityFrameworkCore.Tools": {
            "version": "1.0.0-preview2-final",
            "imports": [
                "portable-net45+win8"
            ]
        }
    },

     // same as before
}

Then run the command from the root of this new project (and not from the root of the main project)

D:\path\Core1RtmEmptyTest\src\Core1RtmEmptyTest.DataLayer>dotnet ef --startup-project ../Core1RtmEmptyTest/ migrations add InitialDatabase

Here we should specify the startup-project too.

And finally to update the database

D:\path\Core1RtmEmptyTest\src\Core1RtmEmptyTest.DataLayer>dotnet ef --startup-project ../Core1RtmEmptyTest/ database update

Upvotes: 2

Andrzej Gis
Andrzej Gis

Reputation: 14306

EDIT

Since 1.0.0-rc1-final (maybe earlier) this workaroud is unnecessary

  • Now you don't need App.DataAccess/Startup.cs (just delete it if you used the workaround below)
  • You create/execure migrations form your main project (in this case App.Web)
  • However you have to specify the project (-p paramater) containing migrations:

cd App.Web
dnx ef migrations add NewMigration -p App.DataAccess

If you have multiple database contexts you also have to specify which one to use (-c parameter)

cd App.Web
dnx ef migrations add NewMigration -p App.DataAccess -c YourDbContext

END OF EDIT

I found out a workaround for that

Suppose you have 2 projects: App.Web and App.DataAccess

You can add a very basic Startup class to your App.DataAccess:

>App.Web
-->Startup.cs // your main startup
>App.DataAccess
-->path/to/ApplicationDbContext.cs
-->different/path/to/YourModels.cs
-->Startup.cs // add a simple startup class
-->project.json

The simple Startup class (App.DataAccess\Startup.cs):

using Microsoft.AspNet.Builder;
using Microsoft.AspNet.Hosting;
using Microsoft.Data.Entity;
using Microsoft.Dnx.Runtime;
using Microsoft.Framework.Configuration;
using Microsoft.Framework.DependencyInjection;
using Microsoft.Framework.Logging;

namespace App.DataAccess
{
    public class Startup
    {
        public Startup(IHostingEnvironment env, IApplicationEnvironment appEnv)
        {
            var builder = new ConfigurationBuilder(appEnv.ApplicationBasePath)
                .AddJsonFile("../App.Web/config.json"); // path to your original configuration in Web project

            Configuration = builder.Build();
        }

        public IConfiguration Configuration { get; set; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddEntityFramework()
                .AddSqlServer()
                .AddDbContext<ApplicationDbContext>(options =>
                    options.UseSqlServer(Configuration["Data:DefaultConnection:ConnectionString"]));
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
        }
    }
}

Than modify your project.json in App.DataAccess (App.DataAccess/project.json):

{
  "version": "1.0.0-*",
  "description": "App.DataAccess Class Library",
  "authors": [ "Author" ],
  "tags": [ "" ],
  "projectUrl": "",
  "licenseUrl": "",
  "frameworks": {
    "dnx451": { }
  },

  "dependencies": {
    "EntityFramework.Commands": "7.0.0-beta7",
    "Microsoft.Framework.Configuration.Json": "1.0.0-beta7",
  },

  "commands": {
    "ef": "EntityFramework.Commands" // this line is important, it will give you access to 'dnx ef' in command line
  }
}

Tha all you have to do is go to App.DataAccess and use dnx ef:

cd App.DataAccess
dnx ef migrations add NewMigration
dnx ef migrations remove
dnx ef database update
dnx ef ...

Upvotes: 23

bricelam
bricelam

Reputation: 30375

Per issues #639, #2256, #2293, #2294, #2357, #2553 & #2748, we have a bit of work to do in that area. :-)

Upvotes: 10

Related Questions