dotnet / EntityFramework.Docs

Documentation for Entity Framework Core and Entity Framework 6
https://docs.microsoft.com/ef/
Creative Commons Attribution 4.0 International
1.62k stars 1.96k forks source link

Documentation on migrations with multiple projects is unclear #933

Open garyapps opened 6 years ago

garyapps commented 6 years ago

I agree with the other commenter that this article is not very clear. I'd go so far as to say it is pretty badly written.

  1. The code snippet refers to "MyApp.Migrations". What is MyApp? Is that the name of the original web project (with dbcontext), or is it the new class library?

  2. And what does "startup assembly" refer to? The web app or the class library?

  3. The fix for removing the circular reference does not work. If each project is supposed to reference the other, then we should expect a circular reference, which is what we get. The suggested fix does not appear to work (not sure if I am doing it wrong), and since no part of the article explains the reasoning behind any of the steps, one cannot even troubleshoot if these steps didn't work.

  4. It would also help to include a few screenshots of how to add a new class library, how to add a reference, and where one can find the XML file where the circular reference fix is supposed to go. After all, we are not all Visual Studio experts.

I have been unable to successfully put migrations in a separate assembly, so I would say this article has not helped me at all.


Document Details

Do not edit this section. It is required for docs.microsoft.com ➟ GitHub issue linking.

mloffer commented 6 years ago

Seconded.

Number 3 says Move the migrations and model snapshot files to the class library. Do I just move the files, or do I also need to change the namespace?

Number 4 says Configure the migrations assembly. But the code listed after it looks like code I would expect to see in the original DbContext assembly. Where does this go in the Migration assembly? How about a filename?

famoser commented 5 years ago

With the help of https://github.com/bricelam/Sample-SplitMigrations I could create a setup which allows to use the same migrations for both my App and my unit tests. It is what I wanted to accomplish when reading the documentation, hopefully this usecase helps someone else; and possibly when enhancing the documentation.

Setup:

Usecase: I want to use same migrations for both App & Test.

How to prepare:

Now you can

Unit Test example code:

[TestClass]
public class MyUnitTest
{
    [ClassInitialize]
    public static void SetUp(TestContext testContext)
    {
               //setup DI
        var services = new ServiceCollection();
        services.AddDbContext<MyDbContext>(options => options.UseSqlite("DataSource=test.sqlite", x => x.MigrationsAssembly("Migrations")));
        var serviceCollection = services.BuildServiceProvider();

        //migrate db
        var context = serviceCollection.GetService<MyDbContext>();
        context.Database.Migrate();
    }
}
fizxmike commented 5 years ago

... but how to migrate with continuous delivery (like in Azure DevOps)? Deployment tasks typically access build artifacts only. Is there a way to run migrations after build? Or do I need to include source in the artifacts and add dotnet SDK to task environment? Is dotnet ef database update equivalent to myDbContext.Database.Migrate();?

famoser commented 5 years ago

You can run any commands in the post deployment script of your release task. I did not need any special configuration (I am using a release pipeline which Visual Studio setup automatically). I do not know if its completely equivalent, but I've used both and did not run into any issues.

fizxmike commented 5 years ago

Azure DevOps is a bit different. The "release" pipelines begin with artifacts (from builds) only. Another complication is I'm doing a self-contained deployment (no dotnet core runtime on target machines), I essentially only have access to the compiled binaries (.exe and dll) at deploy time. I ended up adding a stage in my pipeline to call the .exe with a command line argument which triggers context.Database.Migrate(). I'm hoping this has same behavior as dotnet ef database update. What I'm seeing so far is it is "silent" (no output). Hmm...

asad-c commented 5 years ago

The documentation is too brief, but I've managed to get it working as follows (whilst also adhering to Onion Architecture):

  1. Create a class library 'App.Core'
public class Country
{
    public int Id { get; set; }

    public string Name { get; set; }
}
  1. Create a class library 'App.Infrastructure'
public class AppDbContext : DbContext
{
    public DbSet<Country> Countries { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(@"Server=localhost;Database=App;Trusted_Connection=True;",
            x => x.MigrationsAssembly("App.Migrations"));
    }
}
  1. Run dotnet ef migrations add InitialCreate
  1. Create a class library 'App.Migrations'
<PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
    <OutputPath>App.Infrastructure\bin\$(Configuration)\</OutputPath>
</PropertyGroup>
  1. Build the solution

  2. Run dotnet ef database update from the command line in the 'App.Infrastructure' directory and this should create the database and create the 'Countries' table

  3. Run dotnet ef migrations add AddCity --project App.Migrations for your next migration

  1. Remember to rebuild the solution every time a migration is added
ajcvickers commented 5 years ago

Note: several of the duplicate issues have scenarios/gotchas that we should make sure are covered.

IndigoHealth commented 4 years ago

Lots of good stuff in the comments - hopefully enough for me to get it to work...

Here's another specific problem with the docs. I got as far as item 2:

Add a reference to your DbContext assembly.

Add WHAT KIND OF reference (I assume it's an assembly reference) FROM WHERE (I assume its the existing project assembly that contains the DbContext and migrations) to the DbContext assembly

IndigoHealth commented 4 years ago

The examples in these comments put the migrations into a Migrations library, and the entities and DbContext into a Data library. If the point of this repackaging exercise is to allow more than one "startup" project to reference the entities and migrations, why use two class libraries? Is there a fundamental reason why the migrations, entities, and DbContext can't all be moved to a class library, and that library be referenced from the startup project(s)? Is this 2-library scheme necessary, or simply advantageous for some (unstated) reason?

bricelam commented 4 years ago

We should add some samples for this page: (similar to this one)

bsquidwrd commented 4 years ago

@bricelam Your example was perfect. Thank you very much for posting that here.

MolallaComm commented 4 years ago

I'd also like to see additional documentation explaining how/when to use IDesignTimeDbContextFactory and also the -s option. In my case, my DbContext derived classes are in .net standard 2.0 projects (i have a few legacy .net projects that use the same DB model/code). To get the right DB options, I had to add a factory to my EFCoreMigrationTest.CLI assembly and then also use the -s option like:

PS C:\mydbcontextlib1 dotnet ef migrations add InitialCreate -s ..\EFCoreMigrationTest.CLI\

I eventually figured this out by googling, after I didn't find much useful for this case in the documentation.

gojanpaolo commented 4 years ago

Just tried separate migrations project today and was unsuccessful. bricelam's sample looks promising but it is a three-project solution. I'd like to just have a two-project solution. Anyways, back to googling! :)

gojanpaolo commented 4 years ago

I just got a two-project solution working with the following setup:

Projects:

MyApp.csproj

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.7">
      <PrivateAssets>all</PrivateAssets>
    </PackageReference>
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.7" />
  </ItemGroup>
</Project>

Startup.cs ConfigureServices

services.AddDbContext<MyDbContext>(o => o
    .UseSqlServer(
        "server=(localdb)\\mssqllocaldb;database=mydb;",
        x => x.MigrationsAssembly("MyApp.Migrations")));

MyApp.Migrations.csproj

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp3.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.7" />
  </ItemGroup>
  <ItemGroup>
    <ProjectReference Include="..\MyApp\MyApp.csproj" />
  </ItemGroup>
  <PropertyGroup>
    <OutputPath>..\MyApp\bin\$(Configuration)\</OutputPath>
  </PropertyGroup>
</Project>

The folder structure is:

Build both the projects and open powershell in src and run the following command:

dotnet ef migrations add create --project MyApp.Migrations --startup-project MyApp

Note that you should build both the projects before running any dotnet ef command. This is to update the MyApp.Migrations.dll referenced by MyApp.

Update the database by running

dotnet ef database update --project MyApp.Migrations --startup-project MyApp
ajcvickers commented 3 years ago

See also discussions in #3147 and #3152

VerdonTrigance commented 3 years ago

Guys, I still can't figure out how to add migration to such projects like this one in example https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Schemas/ThreeProjectMigrations ? When trying to execute this: dotnet ef migrations add OnDeleteRestrict --project Project.WebAPI -- --Provider Postgre I've got an error:

Your target project 'Project.WebAPI' doesn't match your migrations assembly 'Project.Migration.Postgre'. Either change your target project or change your migrations assembly.

I already configured startup.cs with:

var provider = Configuration.GetValue("Provider", "Postgre");
            services.AddDbContext<MyDbCtx>(
                options => _ = provider switch
                {
                    "MsSql" => options.UseSqlServer(
                        Configuration.GetConnectionString("MyMsSql"),
                        x => x.MigrationsAssembly("Project.Migration.MsSql")),

                    "Postgre" => options.UseNpgsql(
                        Configuration.GetConnectionString("MyPostgre"),
                        x => x.MigrationsAssembly("Project.Migration.Postgre")),

                    _ => throw new Exception($"Unsupported provider: {provider}. Use 'MsSql' or 'Postgre'.")
                });

Just FYI. I have successfully executed such thing like: dotnet ef migrations script --project Project.WebAPI -i -o $(Build.ArtifactStagingDirectory)/Sql/db-postgre.sql -- --Provider Postgre

bricelam commented 3 years ago

@VerdonTrigance These two things contradict each other:

VerdonTrigance commented 3 years ago

@bricelam but that is only way I make dotnet ef migrations script working properly. So I tried to do same with migrations add. Is there any other way? I tried neither with or without each of these args. It’s not working. PS: I changed current folder from solution root folder to Project.Migration.Postgre and then executed: dotnet ef migrations add OnDeleteRestrict --startup-project ../Project.WebAPI -- --Provider Postgre I thought '--project' doing same as 'cd Project.Migration.Postgre', but it doesn't. I'm confused. What is the purpose for project, startup-project and .MigrationsAssembly() ? Can you add some clarifying readme to the project https://github.com/dotnet/EntityFramework.Docs/tree/main/samples/core/Schemas/ThreeProjectMigrations ?

shorstok commented 2 years ago

It also seems that TwoProjectMigrations sample is not working — at least, not with context.Database.Migrate(), probably because Migrate() attempts to load migration assembly by calling Assembly.Load with configured MigrationsAssembly argument passed as assembly name, and .net core by default does not look for copied assembly .dll in the application folder.

This could be worked around with setting "Microsoft.NETCore.DotNetHostPolicy.SetAppPaths": true in the main (wokrer) app's runtimeconfig.template.json, but this is extremely non-intuitive, for the sample project at least.

dgxhubbard commented 2 years ago

I think this issue #3152 should re-opened, trying to follow the docs which seem to assume most people are running migrations from CLI command line, does not work for the case of context.Database.Migrate. I have done the separate solutions for our different providers and generated the migrations, only to find context.Migrate.Database cannot find the migrations assembly specified in UseMyProvider ( connStr, x => x.MigrationsAssembly ( "MyProviderMigrations" ).

defense89 commented 2 years ago

The documentation is too brief, but I've managed to get it working as follows (whilst also adhering to Onion Architecture):

  1. Create a class library 'App.Core'
  • Add a 'Country' domain model to this library:
public class Country
{
  public int Id { get; set; }

  public string Name { get; set; }
}
  • This is just a simple class to get things working quickly
  1. Create a class library 'App.Infrastructure'
  • Add a DbContext to this library:
public class AppDbContext : DbContext
{
  public DbSet<Country> Countries { get; set; }

  protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
  {
      optionsBuilder.UseSqlServer(@"Server=localhost;Database=App;Trusted_Connection=True;",
          x => x.MigrationsAssembly("App.Migrations"));
  }
}
  • 'App.Migrations' will be our separate class library just for migrations
  • 'App.Infrastructure' needs to reference 'Microsoft.EntityFrameworkCore.SqlServer' and 'Microsoft.EntityFrameworkCore.Tools'
  1. Run dotnet ef migrations add InitialCreate
  • Do this from the command line in the 'App.Infrastructure' directory
  • This will create a 'Migrations' folder in your 'App.Infrastructure' class library with a migration called 'InitialCreate'
  1. Create a class library 'App.Migrations'
  • Move the 'Migrations' folder from 'App.Infrastructure' to 'App.Migrations' - you will need to update the namespaces after the move
  • Add a project reference in 'App.Migrations' to 'App.Infrastructure'
  • Edit the .csproj file for 'App.Migrations' and add an output path:
<PropertyGroup>
  <TargetFramework>netcoreapp2.1</TargetFramework>
  <OutputPath>App.Infrastructure\bin\$(Configuration)\</OutputPath>
</PropertyGroup>
  • The above path will be correct if 'App.Infrastructure' and 'App.Migrations' are in the same directory, if not, the output path will need to be adjusted
  • On build this results in 'App.Migrations' being output to the 'App.Infrastructure' bin directory - we need to do this as we can't reference 'App.Migrations' in 'App.Infrastructure' as this results in a circular reference, so this is a workaround
  1. Build the solution
  2. Run dotnet ef database update from the command line in the 'App.Infrastructure' directory and this should create the database and create the 'Countries' table
  3. Run dotnet ef migrations add AddCity --project App.Migrations for your next migration
  • 'AddCity' is just another migration to create a 'Cities' table - this requires adding a 'City' class and updating the DbContext
  • Run the command from the 'App.Infrastructure' directory and the migration will be added to the 'App.Migrations' class library
  1. Remember to rebuild the solution every time a migration is added

talking about a tutorial!!!!! you got it man a step by step for beginners your awesome.... i get an error however while running dotnet ef migrations add initial create with code , could not find librairy app.migration , i tried to add it but still not working.

probmig

thewrath commented 1 year ago

Hello, For those who are looking for a solution to put Migrations in different assemblies here is a little helper I wrote: https://gist.github.com/thewrath/a6b735efccff34230ceb3ca11a2d9cb4

I simply documented the steps that took me to a working solution. Maybe it can help until the documentation is suitable for a beginner like me 😉.