Efcore: Consider having sugar methods to make it easier to apply migrations programatically

Created on 4 Aug 2017  路  50Comments  路  Source: dotnet/efcore

To run migrations at the moment, we have to run the cli tool which runs in a seperate process and calls back into the project (building the project in the process) to then reflect on it and find various things before producing the migrations.

This model suffers from various issues like:

  • the project may not build under dot cli due to things like incompatible msbuild tasks or target frameworks.
  • can only one migration at a time
  • the cli tool has to scan to find things like the db context factory etc.
  • you have to add things in your core library (factories) that are purely there just to support the migrations command.

Can I suggest an alternative model:

  1. Let us create our own console.exe
  2. Let us add your nuget package, which will add a reference to a netstandard compatible assmbly which has an API for generating migrations for a db context.
  3. Let us use your api from within our own exe, in order to generare a migrations for dbcontexts.

Using the above approach:

  1. we can run our exe whenever we want to generate migrations. We could have it generate multiple very easily or tailor it to certain needs.
  2. There is no external process trying to call back into projects, build them, and discover things via reflection. Therefore the surface area for problems is a lot lower.
  3. Depending on the API you provide, we could provide instances of db contexts into the api, this simplifies and negates the use for factories in many cases.
  4. It means its easy to generate migrations for any referenced dbcontexts - no project startup milarkey to worry about.

Consider at the moment, we are able to add ef to a netstandard1.5 library project. But good luck getting the dotnet-ef tool to run to generate migrations for that. You end up having to create shim project just to be able to have the tooling run!

closed-fixed good first issue help wanted type-enhancement

Most helpful comment

dotnet ef migrations apply

using (var db = new MyDbContext())
{
    db.Database.Migrate();
}

dotnet ef migrations add

using (var db = new MyDbContext())
{
    var reporter = new OperationReporter(
        new OperationReportHandler(
            m => Console.WriteLine("  error: " + m),
            m => Console.WriteLine("   warn: " + m),
            m => Console.WriteLine("   info: " + m),
            m => Console.WriteLine("verbose: " + m)));

    var designTimeServices = new ServiceCollection()
        .AddSingleton(db.GetService<IHistoryRepository>())
        .AddSingleton(db.GetService<IMigrationsIdGenerator>())
        .AddSingleton(db.GetService<IMigrationsModelDiffer>())
        .AddSingleton(db.GetService<IMigrationsAssembly>())
        .AddSingleton(db.Model)
        .AddSingleton(db.GetService<ICurrentDbContext>())
        .AddSingleton(db.GetService<IDatabaseProvider>())
        .AddSingleton<MigrationsCodeGeneratorDependencies>()
        .AddSingleton<ICSharpHelper, CSharpHelper>()
        .AddSingleton<CSharpMigrationOperationGeneratorDependencies>()
        .AddSingleton<ICSharpMigrationOperationGenerator, CSharpMigrationOperationGenerator>()
        .AddSingleton<CSharpSnapshotGeneratorDependencies>()
        .AddSingleton<ICSharpSnapshotGenerator, CSharpSnapshotGenerator>()
        .AddSingleton<CSharpMigrationsGeneratorDependencies>()
        .AddSingleton<IMigrationsCodeGenerator, CSharpMigrationsGenerator>()
        .AddSingleton<IOperationReporter>(reporter)
        .AddSingleton<MigrationsScaffolderDependencies>()
        .AddSingleton<MigrationsScaffolder>()
        .BuildServiceProvider();

    var scaffolder = designTimeServices.GetRequiredService<MigrationsScaffolder>();

    var migration = scaffolder.ScaffoldMigration(
        "MyMigration",
        "MyApp.Data");

    File.WriteAllText(
        migration.MigrationId + migration.FileExtension,
        migration.MigrationCode);
    File.WriteAllText(
        migration.MigrationId + ".Designer" + migration.FileExtension,
        migration.MetadataCode);
    File.WriteAllText(migration.SnapshotName + migration.FileExtension,
        migration.SnapshotCode);
}

All 50 comments

dotnet ef migrations apply

using (var db = new MyDbContext())
{
    db.Database.Migrate();
}

dotnet ef migrations add

using (var db = new MyDbContext())
{
    var reporter = new OperationReporter(
        new OperationReportHandler(
            m => Console.WriteLine("  error: " + m),
            m => Console.WriteLine("   warn: " + m),
            m => Console.WriteLine("   info: " + m),
            m => Console.WriteLine("verbose: " + m)));

    var designTimeServices = new ServiceCollection()
        .AddSingleton(db.GetService<IHistoryRepository>())
        .AddSingleton(db.GetService<IMigrationsIdGenerator>())
        .AddSingleton(db.GetService<IMigrationsModelDiffer>())
        .AddSingleton(db.GetService<IMigrationsAssembly>())
        .AddSingleton(db.Model)
        .AddSingleton(db.GetService<ICurrentDbContext>())
        .AddSingleton(db.GetService<IDatabaseProvider>())
        .AddSingleton<MigrationsCodeGeneratorDependencies>()
        .AddSingleton<ICSharpHelper, CSharpHelper>()
        .AddSingleton<CSharpMigrationOperationGeneratorDependencies>()
        .AddSingleton<ICSharpMigrationOperationGenerator, CSharpMigrationOperationGenerator>()
        .AddSingleton<CSharpSnapshotGeneratorDependencies>()
        .AddSingleton<ICSharpSnapshotGenerator, CSharpSnapshotGenerator>()
        .AddSingleton<CSharpMigrationsGeneratorDependencies>()
        .AddSingleton<IMigrationsCodeGenerator, CSharpMigrationsGenerator>()
        .AddSingleton<IOperationReporter>(reporter)
        .AddSingleton<MigrationsScaffolderDependencies>()
        .AddSingleton<MigrationsScaffolder>()
        .BuildServiceProvider();

    var scaffolder = designTimeServices.GetRequiredService<MigrationsScaffolder>();

    var migration = scaffolder.ScaffoldMigration(
        "MyMigration",
        "MyApp.Data");

    File.WriteAllText(
        migration.MigrationId + migration.FileExtension,
        migration.MigrationCode);
    File.WriteAllText(
        migration.MigrationId + ".Designer" + migration.FileExtension,
        migration.MetadataCode);
    File.WriteAllText(migration.SnapshotName + migration.FileExtension,
        migration.SnapshotCode);
}

Would be nice with some sugar on top of the second one

Thanks @bricelam - I can use that to get up and running.
Agree with @ErikEJ - would be brilliant to see a fluent api for that longer term!

I agree building the design-time service provider should be simplified. However, we don't typically add design-time APIs to DbContext, so the sugar would likely stop there.

Could you also provide / point to example of running a scaffold to programtically generate model from a set of tables? (i.e db first generation)

@ErikEJ - much obliged!

damn.. I actually need this for 1.0.x (sorry) - the code on this issue is all for 1.1.0 I believe. Upgrading to 1.1.0 isn't something I can do very easily at present :-(

The code is actually for 2.0.0. It shouldn't be too bad to backport. Try removing some of the Is from the services and just remove the non-existent ones.

I gave it a go. I can't seem to find the following service (in 1.0.x):

 var scaffolder = designTimeServices.GetRequiredService<MigrationsScaffolder>();

Is there an alternative for MigrationsScaffolder or perhaps I am missing a package reference or using directive?

I also have commented out (hopefully they aren't essential):

  • OperationReporter and OperationReportHandler

and:

 .AddSingleton<MigrationsCodeGeneratorDependencies>()
                    .AddSingleton<ICSharpHelper, CSharpHelper>()
                    .AddSingleton<CSharpMigrationOperationGeneratorDependencies>()
                    .AddSingleton<ICSharpMigrationOperationGenerator, CSharpMigrationOperationGenerator>()
                    .AddSingleton<CSharpSnapshotGeneratorDependencies>()
                    .AddSingleton<ICSharpSnapshotGenerator, CSharpSnapshotGenerator>()
                    .AddSingleton<CSharpMigrationsGeneratorDependencies>()
                    .AddSingleton<IMigrationsCodeGenerator, CSharpMigrationsGenerator>()
                    .AddSingleton<IOperationReporter>(reporter)
                    .AddSingleton<MigrationsScaffolderDependencies>()
                    .AddSingleton<MigrationsScaffolder>()

Ah found them. I had a problem with the reference to: Microsoft.EntityFrameworkCore.Design

So, after some tinkering, here is a 1.0.x equivalent:


using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore.Migrations.Design;
using Reach.GCv3.Data.EF7.Models;
using System;
using System.IO;
using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Storage.Internal;

namespace Scaffolder
{
    class Program
    {
        static void Main(string[] args)
        {
            var services = new ServiceCollection();
            services.AddLogging();
            var connString = "your conn string";
            services.AddDbContext<YourDbContext>((optionsBuilder) =>
            {
                optionsBuilder.UseSqlServer(connString);
            });

            var serviceProvider = services.BuildServiceProvider();
            var scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
            using (var scope = scopeFactory.CreateScope())
            {
                using (var db = scope.ServiceProvider.GetRequiredService<YourDbContext>())
                {
                    var reporter = new OperationReporter(
                        new OperationReportHandler(
                            m => Console.WriteLine("  error: " + m),
                            m => Console.WriteLine("   warn: " + m),
                            m => Console.WriteLine("   info: " + m),
                            m => Console.WriteLine("verbose: " + m)));

                    var designTimeServices = new ServiceCollection()
                        .AddLogging()
                        .AddSingleton(db.GetService<IHistoryRepository>())
                        .AddSingleton(db.GetService<IMigrationsIdGenerator>())
                        .AddSingleton(db.GetService<IMigrationsModelDiffer>())
                        .AddSingleton(db.GetService<IMigrationsAssembly>())
                        .AddSingleton(db.Model)
                        .AddSingleton(db.GetService<ICurrentDbContext>())
                        .AddSingleton(db.GetService<IDatabaseProvider>())
                        // .AddSingleton<MigrationsCodeGeneratorDependencies>()
                        .AddSingleton<CSharpHelper>()
                        // .AddSingleton<CSharpMigrationOperationGeneratorDependencies>()
                        .AddSingleton<CSharpMigrationOperationGenerator>()
                        // .AddSingleton<CSharpSnapshotGeneratorDependencies>()
                        .AddSingleton<CSharpSnapshotGenerator>()
                         //.AddSingleton<CSharpMigrationsGeneratorDependencies>()
                         .AddSingleton<MigrationsCodeGenerator, CSharpMigrationsGenerator>()
                        .AddSingleton<IOperationReporter>(reporter)
                        // .AddSingleton<MigrationsScaffolderDependencies>()
                        .AddSingleton<MigrationsScaffolder>()
                        .AddSingleton<IDatabaseProviderServices, SqlServerDatabaseProviderServices>()
                         .AddSingleton<IRelationalDatabaseProviderServices, SqlServerDatabaseProviderServices>()
                        .BuildServiceProvider();


                    var scaffolder = designTimeServices.GetRequiredService<MigrationsScaffolder>();

                    var migration = scaffolder.ScaffoldMigration(
                        "MyMigration",
                        "MyApp.Data");

                    File.WriteAllText(
                        migration.MigrationId + migration.FileExtension,
                        migration.MigrationCode);
                    File.WriteAllText(
                        migration.MigrationId + ".Designer" + migration.FileExtension,
                        migration.MetadataCode);
                    File.WriteAllText(migration.SnapshotName + migration.FileExtension,
                        migration.SnapshotCode);
                }
            }



        }
    }
}

I had to add Logging and IDatabaseProviderServices registrations to the designer services collection.

Ok so I can successfully generate migrations, and now I am trying to reverse engineer like @ErikEJ 's code shows!

Problem I have is I am not sure how to register: .ReverseEngineeringGenerator in 1.0.x?

At the moment if I just add it like this:

  .AddSingleton<ReverseEngineeringGenerator>()

Then when I resolve it I get an exception that IScaffoldingModelFactory can't be resolved. I can't find an implementation of IScaffoldingModelFactory anywhere though to register.

Right so I am close.. but I have been thwarted.
After figuring out where things are in 1.0.x. and adding a bunch of additional service registrations, I can now resolve the ReverseEngineeringGenerator. The problem is, when I try to use it I get this exception:

image

I have no idea what that exception message means.

Here are my services:


   var services = new ServiceCollection();
            services.AddLogging();
            var connString = "MY REAL SQL SERVER CONN STRING IS HERE";
            services.AddDbContext<MyDbContext>((optionsBuilder) =>
            {
                optionsBuilder.UseSqlServer(connString);
            });

            var serviceProvider = services.BuildServiceProvider();
            var scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
            using (var scope = scopeFactory.CreateScope())
            {
                using (var db = scope.ServiceProvider.GetRequiredService<MyDbContext>())
                {
                    var reporter = new OperationReporter(
                        new OperationReportHandler(
                            m => Console.WriteLine("  error: " + m),
                            m => Console.WriteLine("   warn: " + m),
                            m => Console.WriteLine("   info: " + m),
                            m => Console.WriteLine("verbose: " + m)));

                    var designTimeServices = new ServiceCollection()
                        .AddLogging()
                        .AddSingleton(db.GetService<IHistoryRepository>())
                        .AddSingleton(db.GetService<IMigrationsIdGenerator>())
                        .AddSingleton(db.GetService<IMigrationsModelDiffer>())
                        .AddSingleton(db.GetService<IMigrationsAssembly>())
                        .AddSingleton(db.Model)
                        .AddSingleton(db.GetService<ICurrentDbContext>())
                        .AddSingleton(db.GetService<IDatabaseProvider>())
                        // .AddSingleton<MigrationsCodeGeneratorDependencies>()
                        .AddSingleton<CSharpHelper>()
                        // .AddSingleton<CSharpMigrationOperationGeneratorDependencies>()
                        .AddSingleton<CSharpMigrationOperationGenerator>()
                        // .AddSingleton<CSharpSnapshotGeneratorDependencies>()
                        .AddSingleton<CSharpSnapshotGenerator>()
                        // .AddSingleton<CSharpMigrationsGeneratorDependencies>()
                        .AddSingleton<MigrationsCodeGenerator, CSharpMigrationsGenerator>()
                        .AddSingleton<IOperationReporter>(reporter)
                        // .AddSingleton<MigrationsScaffolderDependencies>()
                        .AddSingleton<MigrationsScaffolder>()
                        .AddSingleton<IDatabaseProviderServices, SqlServerDatabaseProviderServices>()
                        .AddSingleton<IRelationalDatabaseProviderServices, SqlServerDatabaseProviderServices>()

                        .AddSingleton<ReverseEngineeringGenerator>()
                        .AddSingleton<IScaffoldingModelFactory, SqlServerScaffoldingModelFactory>()
                        .AddSingleton<IRelationalTypeMapper, SqlServerTypeMapper>()  //RelationalTypeMapper
                        .AddSingleton<IDatabaseModelFactory, SqlServerDatabaseModelFactory>()

                        // .AddSingleton<AnnotationCodeGeneratorDependencies>()
                        .AddSingleton<IFileService, FileSystemFileService>()
                        // .AddSingleton<RelationalTypeMapperDependencies>()
                        // .AddSingleton<IModelScaffolder, ModelScaffolder>()
                        .AddSingleton<CandidateNamingService>()
                        // .AddSingleton<IPluralizer, NullPluralizer>()
                        .AddSingleton<CSharpUtilities>()
                        // .AddSingleton<ICSharpDbContextGenerator, CSharpDbContextGenerator>()
                        // .AddSingleton<ICSharpEntityTypeGenerator, CSharpEntityTypeGenerator>()
                        .AddSingleton<ScaffoldingTypeMapper>()
                        .AddSingleton<ConfigurationFactory>()
                        // .AddSingleton<IScaffoldingCodeGenerator, CSharpScaffoldingGenerator>()
                        .AddSingleton<IScaffoldingModelFactory, RelationalScaffoldingModelFactory>()
                        .AddSingleton<IRelationalAnnotationProvider, SqlServerAnnotationProvider>()
                        .AddSingleton<CodeWriter, StringBuilderCodeWriter>()
                        .AddSingleton<DbContextWriter>()
                        .AddSingleton<EntityTypeWriter>()
                        .AddSingleton<ScaffoldingUtilities>()
                        .BuildServiceProvider();

                    var currentPath = Environment.CurrentDirectory;
                    var outputPath = Path.Combine(currentPath, "output");
                    var tables = new string[] {
                    "Foo",
                    "Bar"                  
                    };

                    var tableSelection = new Microsoft.EntityFrameworkCore.Scaffolding.TableSelectionSet(tables);

                    var options = new ReverseEngineeringConfiguration
                    {
                        ConnectionString = connString,
                        ProjectPath = currentPath,
                        OutputPath = outputPath,
                        ProjectRootNamespace = "My.Namespace",
                        OverwriteFiles = true,
                        UseFluentApiOnly = true,
                        ContextClassName = "MyDbContext",
                        TableSelectionSet = tableSelection
                    };


                    var generator = designTimeServices.GetService<ReverseEngineeringGenerator>();

                    var model = generator.GetMetadataModel(options);
                    // ALERT: Following line throws!
                    var filePaths = generator.GenerateAsync(options).GetAwaiter().GetResult();
                    var errors = model.Scaffolding().EntityTypeErrors;
                    var contextFilePath = filePaths.ContextFile;
                    var entityTypeFilePaths = filePaths.EntityTypeFiles;

                }
            }

Found the culprit. This registration:

  .AddSingleton<IScaffoldingModelFactory, RelationalScaffoldingModelFactory>()

Should have been:

  .AddSingleton<IScaffoldingModelFactory, SqlServerScaffoldingModelFactory>()

and I was also registering it twice :-(

Here is the working code for 1.0.x the service registrations are enough to scaffold migrations and also reverse engineer - using sql server:


using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore.Migrations.Design;
using System;
using System.IO;
using Microsoft.EntityFrameworkCore.Design.Internal;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.EntityFrameworkCore.Storage.Internal;
using Microsoft.EntityFrameworkCore.Scaffolding.Internal;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Scaffolding;

namespace Scaffolder
{
    class Program
    {
        static void Main(string[] args)
        {

            var services = new ServiceCollection();
            services.AddLogging();
            var connString = "YOUR CONN STRING";
            services.AddDbContext<YourDbContext>((optionsBuilder) =>
            {
                optionsBuilder.UseSqlServer(connString);
            });

            var serviceProvider = services.BuildServiceProvider();
            var scopeFactory = serviceProvider.GetRequiredService<IServiceScopeFactory>();
            using (var scope = scopeFactory.CreateScope())
            {
                using (var db = scope.ServiceProvider.GetRequiredService<YourDbContext>())
                {
                    var reporter = new OperationReporter(
                        new OperationReportHandler(
                            m => Console.WriteLine("  error: " + m),
                            m => Console.WriteLine("   warn: " + m),
                            m => Console.WriteLine("   info: " + m),
                            m => Console.WriteLine("verbose: " + m)));

                    var designTimeServices = new ServiceCollection()
                        .AddLogging()
                        .AddSingleton(db.GetService<IHistoryRepository>())
                        .AddSingleton(db.GetService<IMigrationsIdGenerator>())
                        .AddSingleton(db.GetService<IMigrationsModelDiffer>())
                        .AddSingleton(db.GetService<IMigrationsAssembly>())
                        .AddSingleton(db.Model)
                        .AddSingleton(db.GetService<ICurrentDbContext>())
                        .AddSingleton(db.GetService<IDatabaseProvider>())                     
                        .AddSingleton<CSharpHelper>()                    
                        .AddSingleton<CSharpMigrationOperationGenerator>()                 
                        .AddSingleton<CSharpSnapshotGenerator>()                    
                        .AddSingleton<MigrationsCodeGenerator, CSharpMigrationsGenerator>()
                        .AddSingleton<IOperationReporter>(reporter)                     
                        .AddSingleton<MigrationsScaffolder>()
                        .AddSingleton<IDatabaseProviderServices, SqlServerDatabaseProviderServices>()
                        .AddSingleton<IRelationalDatabaseProviderServices, SqlServerDatabaseProviderServices>()

                        .AddSingleton<ReverseEngineeringGenerator>()
                        .AddSingleton<IScaffoldingModelFactory, SqlServerScaffoldingModelFactory>()
                        .AddSingleton<IRelationalTypeMapper, SqlServerTypeMapper>()  //RelationalTypeMapper
                        .AddSingleton<IDatabaseModelFactory, SqlServerDatabaseModelFactory>()      
                        .AddSingleton<IFileService, FileSystemFileService>()                       
                        .AddSingleton<CandidateNamingService>()                   
                        .AddSingleton<CSharpUtilities>()                  
                        .AddSingleton<ScaffoldingTypeMapper>()
                        .AddSingleton<ConfigurationFactory>()                              
                        .AddSingleton<IRelationalAnnotationProvider, SqlServerAnnotationProvider>()
                        .AddSingleton<CodeWriter, StringBuilderCodeWriter>()
                        .AddSingleton<DbContextWriter>()
                        .AddSingleton<EntityTypeWriter>()
                        .AddSingleton<ScaffoldingUtilities>() 
                        .BuildServiceProvider();

                    var currentPath = Environment.CurrentDirectory;
                    var outputPath = Path.Combine(currentPath, "output");
                    var tables = new string[] {
                    "Foo",
                    "Bar"                 
                    };

                    var tableSelection = new Microsoft.EntityFrameworkCore.Scaffolding.TableSelectionSet(tables);

                    var options = new ReverseEngineeringConfiguration
                    {
                        ConnectionString = connString,
                        ProjectPath = currentPath,
                        OutputPath = outputPath,
                        ProjectRootNamespace = "Your.Namespace",
                        OverwriteFiles = true,
                        UseFluentApiOnly = true,
                        ContextClassName = "YourDbContext",
                        TableSelectionSet = tableSelection
                    };

                   // Reverse engineer:
                    var generator = designTimeServices.GetService<ReverseEngineeringGenerator>();

                    var model = generator.GetMetadataModel(options);
                    var filePaths = generator.GenerateAsync(options).GetAwaiter().GetResult();
                    var errors = model.Scaffolding().EntityTypeErrors;
                    var contextFilePath = filePaths.ContextFile;
                    var entityTypeFilePaths = filePaths.EntityTypeFiles;

                   // Scaffold a migration
                   var scaffolder = designTimeServices.GetRequiredService<MigrationsScaffolder>();

                    var migration = scaffolder.ScaffoldMigration( "MyMigration",  "MyApp.Data");

                    //File.WriteAllText(
                    //    migration.MigrationId + migration.FileExtension,
                    //    migration.MigrationCode);
                    //File.WriteAllText(
                    //    migration.MigrationId + ".Designer" + migration.FileExtension,
                    //    migration.MetadataCode);
                    //File.WriteAllText(migration.SnapshotName + migration.FileExtension,
                    //    migration.SnapshotCode);
                }
            }



        }
    }
}

I had to added .AddSingleton<ISnapshotModelProcessor, SnapshotModelProcessor>()

dotnet ef migrations add

to go it work

Dazinator.
Somebody tested this solution with Postgresql Database ???

FYI this has changed for 2.1, lots more services need to be injected now

I don't think so.

I use this

  var designTimeServiceCollection = new ServiceCollection()
                .AddEntityFrameworkDesignTimeServices()
                //.AddSingleton<MigrationsScaffolder>()
                .AddDbContextDesignTimeServices(db);
            new SqlServerDesignTimeServices().ConfigureDesignTimeServices(designTimeServiceCollection);
            return designTimeServiceCollection.BuildServiceProvider();

Before 2.1 I used this

 return new ServiceCollection()
                .AddSingleton(db.GetService<IHistoryRepository>())
                .AddSingleton(db.GetService<IMigrationsIdGenerator>())
                .AddSingleton(db.GetService<IMigrationsModelDiffer>())
                .AddSingleton(db.Model)
                .AddSingleton(db.GetService<ICurrentDbContext>())
                .AddSingleton(db.GetService<IDatabaseProvider>())
                .AddSingleton<MigrationsCodeGeneratorDependencies>()
                .AddSingleton<ICSharpHelper, CSharpHelper>()
                .AddSingleton<CSharpMigrationOperationGeneratorDependencies>()
                .AddSingleton<ICSharpMigrationOperationGenerator, CSharpMigrationOperationGenerator>()
                .AddSingleton<CSharpSnapshotGeneratorDependencies>()
                .AddSingleton<ICSharpSnapshotGenerator, CSharpSnapshotGenerator>()
                .AddSingleton<ISnapshotModelProcessor, SnapshotModelProcessor>()
                .AddSingleton<CSharpMigrationsGeneratorDependencies>()
                .AddSingleton<IMigrationsCodeGenerator, CSharpMigrationsGenerator>()
                .AddSingleton<IMigrationsCodeGeneratorSelector, MigrationsCodeGeneratorSelector>()
                .AddSingleton<IRelationalConnection, SqlServerConnection>()
                .AddSingleton<IDbContextOptions, DbContextOptions<Sql>>()
                .AddSingleton<IMigrator, Migrator>()
                .AddSingleton<IDatabaseCreator, SqlServerDatabaseCreator>()
                .AddSingleton<IOperationReporter>(reporter)
                .AddSingleton<MigrationsScaffolderDependencies>()
                .AddSingleton<RelationalConnectionDependencies>()
                .AddSingleton<RelationalDatabaseCreatorDependencies>()
                .AddSingleton<MigrationsScaffolder>()
                .AddSingleton(db.GetService<IMigrationsAssembly>())
                .AddSingleton(db.GetService<IMigrationsAssembly>())
                .BuildServiceProvider();

@bricelam could you provide this code for Razor Class Library ( NetStandard.Library 2.0 ) or is it possible for Razor Class Library ?

@oOAhmedKingOo What's the question? You shouldn't have to do anything different for a Razor Class Library...

@bricelam my Admin area is a Razor Class Library and i want to generate migration files in that area. if still not clear for you i will explain more. best regards

Still not clear. What have you tried? What error are you seeing?

@bricelam

thanx for your response ,
simply if i copy your above code to generate migration files then i will get

( type or namespace MigrationsCodeGeneratorDependenciescould not be found )
this error is show for all below (.AddSingleton<>())

var designTimeServices = new ServiceCollection() .AddSingleton<MigrationsCodeGeneratorDependencies>() .AddSingleton<ICSharpHelper, CSharpHelper>() .AddSingleton<CSharpMigrationOperationGeneratorDependencies>() .AddSingleton<ICSharpMigrationOperationGenerator, CSharpMigrationOperationGenerator>() .AddSingleton<CSharpSnapshotGeneratorDependencies>() .AddSingleton<ICSharpSnapshotGenerator, CSharpSnapshotGenerator>() .AddSingleton<CSharpMigrationsGeneratorDependencies>() .AddSingleton<IMigrationsCodeGenerator, CSharpMigrationsGenerator>() .AddSingleton<IOperationReporter>(reporter) .AddSingleton<MigrationsScaffolderDependencies>() .AddSingleton<MigrationsScaffolder>() .BuildServiceProvider();

Note:

_I don't have problem with regular Asp.Net Core 2.1 App and works as expected_

Best Regards

For EF Core 2.1+, use Anderman's code:

var designTimeServiceCollection = new ServiceCollection()
    .AddEntityFrameworkDesignTimeServices()
    .AddDbContextDesignTimeServices(db);
new SqlServerDesignTimeServices().ConfigureDesignTimeServices(designTimeServiceCollection);

var designTimeServices = designTimeServiceCollection.BuildServiceProvider();

@bricelam thanx for your help,

sorry for bothering you !
but i want the generated files to be in different assembly ( project )
in this line var migration = scaffolder.ScaffoldMigration("MyMigration", "MyApp.Data");

i set MyApp.Data to different namespace ( MyDatabaseModels.Data which is not the current namespace (Admin project )) , and always the generated files are placed in the Main Asp.net Core app (SMCore.Web ) not the MyDatabaseModels.Data project

:)

if its not clear i will show you my solution tree ( which contains 3 project )

Just change the path you write the files to:

var scaffolder = designTimeServices.GetRequiredService<MigrationsScaffolder>();
var migration = scaffolder.ScaffoldMigration("MyMigration", "MyDatabaseModels.Data");
var projectDir = @"..\MyDatabaseModels\Data\";

File.WriteAllText(
    projectDir + migration.MigrationId + migration.FileExtension,
    migration.MigrationCode);
File.WriteAllText(
    projectDir + migration.MigrationId + ".Designer" + migration.FileExtension,
    migration.MetadataCode);
File.WriteAllText(
    projectDir + migration.SnapshotName + migration.FileExtension,
    migration.SnapshotCode);

sorry for late replay.

thank you for your help .
you saved my day 馃

Best Regard
Ahmed :)

  • one last question .
    how do i check that the generated files are not empty , i mean the Method ( Up and Down ) are not empty , means there is database changes.

  • another last question
    this line await context.Database.MigrateAsync()
    how do i know the database is already up to date

thank you man

Regards

:(

how do i check that the generated files are not empty

See this line in the database error page.

how do i know the database is already up to date

You can use this:

!context.Database.GetPendingMigrations().Any()

But, MigrateAsync() will just no-op if it's up to date.

@bricelam thank you so much .
i appreciate your help 馃 .
:) and sorry for bothering you too much.

thanx again

Regards

@bricelam I was previoulsy using ReverseEngineeringGenerator to scaffold a set of models for a set of tables:

 var tableSelection = new Microsoft.EntityFrameworkCore.Scaffolding.TableSelectionSet(tables);

            var options = new ReverseEngineeringConfiguration
            {
                ConnectionString = connString,
                ProjectPath = currentPath,
                OutputPath = outputPath,
                ProjectRootNamespace = "Your.Namespace",
                OverwriteFiles = true,
                UseFluentApiOnly = true,
                ContextClassName = "YourDbContext",
                TableSelectionSet = tableSelection
            };

            // Reverse engineer:
            var generator = designTimeServices.GetService<ReverseEngineeringGenerator>();

            var model = generator.GetMetadataModel(options);
            var filePaths = generator.GenerateAsync(options).GetAwaiter().GetResult();
            var errors = model.Scaffolding().EntityTypeErrors;
            var contextFilePath = filePaths.ContextFile;
            var entityTypeFilePaths = filePaths.EntityTypeFiles;

However in 2.2 I see TableSelectionSet is marked as obsolete, and I can't find ReverseEngineeringGenerator class anywhere.. What's the new way?

@dazinator look at the source in EF Core Power Tools

We were using code below to modify entity names.
With EFCore 2.2, DbContextWriter can not be found. Is there an alternative way to accomplish this?
Thanks.
services.AddSingleton();
...
public class CustomDbContextWriter : DbContextWriter
{
public CustomDbContextWriter(
ScaffoldingUtilities scaffoldingUtilities,
CSharpUtilities cSharpUtilities)
: base(scaffoldingUtilities, cSharpUtilities)
{ }

    public override string WriteCode(ModelConfiguration modelConfiguration)
    {
        // There is no good way to override the DbSet naming, as it uses 
        // an internal StringBuilder. This means we can't override 
        // AddDbSetProperties without re-implementing the entire class.
        // Therefore, we have to get the code and then do string manipulation 
        // to replace the DbSet property code
        var code = base.WriteCode(modelConfiguration);

        foreach (var entityConfig in modelConfiguration.EntityConfigurations)
        {
            var entityName = entityConfig.EntityType.Name;
            //var setName = Inflector.Inflector.Pluralize(entityName) ?? entityName;
            // We will always have original names as is
            var setName = entityName;
            code = code.Replace(
                $"DbSet<{entityName}> {entityName}",
                $"DbSet<{entityName}> {setName}");
        }
        return code;
    }

@AlansButler Just replace the IPluralizer service instead?

Thanks for the suggestion bricelam.
Could you point me to an example?

services.AddSingleton<IPluralizer, CustomPluralizer>();
public class CustomPluarlizer : IPluralizer
{
    public override string Pluralize(string identifier)
        => Inflector.Inflector.Pluralize(identifier);

    public override string Singularize(string identifier)
        => Inflector.Inflector.Singularize(identifier);
}

Thanks. But what we'd like to do is prepend the schema name. I'm not sure how to access the schema name from within Pluralize.
public string Pluralize(string name)
{
return schema +'_'+ name;
}

Well, that's nothing like the original code you showed... 馃檪 We don't have an easy hook for that. Start in CSharpModelGenerator to find a new hack. But also recognize that using internal API will likely break again in the future.

I got the code running with EF Core 2.2.4. The code generates the migration code, but how do I get it integrated (compiled) at runtime?

Ok, found a solution.

  1. Compile the code with roslyn
  2. Set the migration assembly in the optionsbuilder
optionsBuilder.UseSqlServer("Data Source=localhost;Initial Catalog=Test;Integrated Security=true", m => m.MigrationsAssembly(assemblyName));

```
Brice,

Thanks for the suggestion.

I have this mostly working except for below:
When building the models this is what I want for example:
public partial class X837_DTP
{
public long DTPID { get; set; }
public virtual X837_Claim X837_Claim { get; set; }

}
Where the simple types like DTPID property names don't contain the schema,
but property types that are classes do. Where X837 is the schema and Claim is the table.

By Overridding RelationalScaffoldingModelFactory
protected override string GetPropertyName(DatabaseColumn column)
I am able to get all the properties to contain the schema. This is close as I can get:
public partial class X837_DTP
{
public long X837_DTPID { get; set; } //Don't want schema name here in property name.
public virtual X837_Claim X837_Claim { get; set; }

}

If I don't use override GetPropertyName I get below model with no schemas in name.
public partial class X837_DTP
{
public long DTPID { get; set; }
public virtual X837_Claim Claim { get; set; }

}

Now, I can override Overridding CSharpEntityTypeGenerator.WriteCode
and do a replace, but, this doesn't seem to change the name internally.
The DBContext still uses the tablename only. Like Claim.
example:
entity.HasOne(d => d.Claim) //HasOne Still using table not Schama_Table
...

I tried to override VisitColumn:
protected override PropertyBuilder VisitColumn(EntityTypeBuilder builder, DatabaseColumn column)
so I could tell the type of the property, but even the properties with class types come back as
simple types in column.storeType.

Any suggestions would be appreciated.

Code:
public class RelationalScaffoldingModelFactoryGaf : RelationalScaffoldingModelFactory
{
public static string fSchema = string.Empty;
public static Dictionary> rememberColumns = new Dictionary>();
public RelationalScaffoldingModelFactoryGaf(

    IOperationReporter reporter,
    ICandidateNamingService candidateNamingService,
    IPluralizer pluralizer,
    ICSharpUtilities cSharpUtilities,
    IScaffoldingTypeMapper scaffoldingTypeMapper)
    : base(
          reporter,
          candidateNamingService,
          pluralizer,
          cSharpUtilities,
          scaffoldingTypeMapper)
{
    rememberColumns.Clear();
}


/// <summary>
/// Changes the property names in the models.
/// public int x { get; set; }   
/// This converts too many properties.  For example in X837_DTP.
/// This we want for example: public virtual X837_Claim X837_Claim { get; set; }
/// This we do not: public DateTime? X837_DateTo { get; set; }
/// </summary>
/// <param name="column"></param>
/// <returns></returns>
protected override string GetPropertyName(DatabaseColumn column)
{
    return column.Table.Schema + "_" + column.Name;  
}

 protected override PropertyBuilder VisitColumn(EntityTypeBuilder builder, DatabaseColumn column)
    {
        PropertyBuilder pb = base.VisitColumn(builder, column);

Console.WriteLine("RelationalScaffoldingModelFactoryGaf-VisitColumn");
Console.WriteLine("     columnName " + column.Name);
Console.WriteLine("    pb.MetaDataCLRType: " + pb.Metadata.ClrType);
Console.WriteLine("    Column.StoreType: " + column.StoreType);

        //var typeScaffoldingInfo = GetTypeScaffoldingInfo(column);
        //var clrType = typeScaffoldingInfo.ClrType;
        //var propertyBuilder = builder.Property(clrType, GetPropertyName(column));

        return pb;
    }

}

public class CSharpEntityTypeGeneratorGaf : CSharpEntityTypeGenerator
{
public CSharpEntityTypeGeneratorGaf(
ICSharpHelper cSharpHelper)
: base(cSharpHelper)
{ }

public override string WriteCode(IEntityType entityType, string @namespace, bool useDataAnnotations)
{

    string code = base.WriteCode(entityType, @namespace, useDataAnnotations);

    string[] modelLines = code.Split(Environment.NewLine);
    foreach (string line in modelLines)
    {
        List<string> modifiedModelLines = new List<string>();
        //Some code here to get the schemaName and tableName from the current model line.

        modifLine = line.Replace(schemaName + "_" + tableName + " " + tableName,
                                                              schemaName + "_" + tableName + " " + schemaName +"_"+ tableName);


        modifiedModelLines.Add(modifLine);
    }
    code = string.Join(Environment.NewLine, modifiedModelLines.ToArray());      
    return code;
}

}

Is there anything I need to change to do this correctly in 3.0?

@kierenj I don't recall any major changes in this area. There is one breaking change to the way you have to reference EFCore.Design. Other than that, I think it will just work.

When upgrading to 3.0 I need to add the compile asset to Microsoft.EntityFrameworkCore.Design 3.0 package. Is there a reason for this?

    <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.0.0">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive;compile</IncludeAssets>
    </PackageReference>

Is there a reason for this?

Yes, cited in the breaking change note

@bricelam I'm very happy to see your answer, I've been troubled by a related problem for the last few days. I am having the same problem as @DarkGraySun , your answer doesn't have a section on how to compile and integrate the generated code at runtime, but roslyn doesn't work for me. I have two ideas:

  1. instead of generating cs code, generate sql scripts and execute sql through ef core.
  1. don't generate any code or scripts, compare the differences and then execute and save the changes, a relevant reference is how-can-i-use-the-entity-framework-core- capabilities-to-change-my-database

I would like to have some sample code, thank you very much.

Was this page helpful?
0 / 5 - 0 ratings