hxsh.fx
hxsh.fx

Reputation: 33

Can't create Entity Framework code-first migrations

I've been developing a .NET Core 6 console application (not ASP.NET) the last weeks and now I've tried to implement Entity Framework 6 migrations to it.

However, even though I reused some code from a working database model that used migrations, now I can't manage to make it work and I've also been struggling due to the lack of output from dotnet-ef.

For reasons I can't remember, the database project I reused code from used Design-Time DbContext creation. I don't know if that's my optimal way to make migrations but at least it managed to work on the previous project. I implemented the required IDesignTimeDbContextFactory<DbContext> interface the same way it was done previously:

public class MySqlContextFactory : IDesignTimeDbContextFactory<MySqlContext>
{
    public MySqlContext CreateDbContext(string[] args)
    {
        DbContextOptionsBuilder optionsBuilder = new();
        ServerVersion mariaDbVersion = new MariaDbServerVersion(new Version(10, 6, 5));
        optionsBuilder.UseMySql(DatabaseCredentials.GetConnectionString(), mariaDbVersion);

        return new MySqlContext();
    }
}

public class MySqlContext : DbContext
{
    public DbSet<Endpoint> EndpointsSet { get; set; }
    
    private readonly string _connectionString;

    public MySqlContext() : base()
        => _connectionString = DatabaseCredentials.GetConnectionString();

    public MySqlContext(string connectionString) : base()
        => _connectionString = connectionString;

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => Configurator.Configure(optionsBuilder, _connectionString);

    protected override void OnModelCreating(ModelBuilder modelBuilder)
        => Configurator.Create(modelBuilder);
}

public static void Configure(DbContextOptionsBuilder optionsBuilder, string connectionString)
{
        ServerVersion mariaDbVersion = new MariaDbServerVersion(new Version(10, 6, 5));
        optionsBuilder.UseMySql(connectionString, mariaDbVersion);
}

public static void Create(ModelBuilder modelBuilder)
{
        IEnumerable<Type> types = ReflectionUtils.GetImplementedTypes(typeof(IEntityTypeConfiguration<>));
        if (types.Any())
        {
            foreach (Type entityConfigurationType in types)
            {
                modelBuilder.ApplyConfigurationsFromAssembly(entityConfigurationType.Assembly);
            }
        }
        else
        {
            Environment.Exit((int) EExitCodes.EF_MODEL_NOT_FOUND);
        }
}

However, when I tried to create the first migration, I've been prompted with this absolutely non-descriptive output from the dotnet-ef tool:

PS> dotnet ef migrations add Init
Build started...
Build succeeded.
PS>

But no migrations were made nor anything changed in my project. So I decide to force dotnet ef to tell me more things by appending the --verbose flag on the PS command:

[...]
Build succeeded.
dotnet exec --depsfile F:\pablo\Documents\source\MyBot\bin\Debug\net6.0\MyBot.deps.json --additionalprobingpath C:\Users\pablo\.nuget\packages --runtimeconfig F:\pablo\Documents\source\MyBot\bin\Debug\net6.0\MyBot.runtimeconfig.json C:\Users\pablo\.dotnet\tools\.store\dotnet-ef\6.0.1\dotnet-ef\6.0.1\tools\netcoreapp3.1\any\tools\netcoreapp2.0\any\ef.dll migrations add Init -o Migrations\Init --assembly F:\pablo\Documents\source\MyBot\bin\Debug\net6.0\MyBot.dll --project F:\pablo\Documents\source\MyBot\MyBot.csproj --startup-assembly F:\pablo\Documents\source\MyBot\bin\Debug\net6.0\MyBot.dll --startup-project F:\pablo\Documents\source\MyBot\MyBot.csproj --project-dir F:\pablo\Documents\source\MyBot\ --root-namespace MyBot--language C# --framework net6.0 --nullable --working-dir F:\pablo\Documents\source\MyBot--verbose

Using assembly 'MyBot'.
Using startup assembly 'MyBot'.
Using application base 'F:\pablo\Documents\source\MyBot\bin\Debug\net6.0'.
Using working directory 'F:\pablo\Documents\source\MyBot'.
Using root namespace 'MyBot'.
Using project directory 'F:\pablo\Documents\source\MyBot\'.
Remaining arguments: .
Finding DbContext classes...
Finding IDesignTimeDbContextFactory implementations...
Found IDesignTimeDbContextFactory implementation 'MySqlContextFactory'.
Found DbContext 'MySqlContext'.
Finding application service provider in assembly 'MyBot'...
Finding Microsoft.Extensions.Hosting service provider...
No static method 'CreateHostBuilder(string[])' was found on class 'Program'.
No application service provider was found.
Finding DbContext classes in the project...
Using DbContext factory 'MySqlContextFactory'.
PS>

The first thing I thought I could search for was that CreateHostBuilder function the tool is searching but not retrieving. However, once again, all the documentation I could find was refer to ASP.NET applications, and programming patterns I'm not implementing in my bot application. My app does retrieve the services via Dependency Injection, custom made (maybe that's the reason of the line No application service provider was found. ?), but I didn't find a way to implement that CreateHostBuilder function without changing everything.

Just for adding the information, this is how I managed to create and configure the EF model with the non-migrations approach:

public static IServiceProvider GetServices(DiscordSocketClient client, CommandService commands)
    {
        ServiceCollection services = new();

        services.AddSingleton(client);
        services.AddSingleton(commands);
        services.AddSingleton<HttpClient>();

        services.AddDbContext<MySqlContext>(ServiceLifetime.Scoped);

        return AddServices(services) // builds service provider;
    }

private static async Task InitDatabaseModel(IServiceProvider provider)
    {
        MySqlContext? dbCtxt = provider.GetService<MySqlContext>();

        if (dbCtxt == null)
        {
            Environment.Exit((int) EExitCodes.DB_SERVICE_UNAVAILABLE);
        }

        await dbContext.Database.EnsureDeletedAsync();
        await dbContext.Database.EnsureCreatedAsync();
    }

But unfortunately, my application is planned to interact with a database dynamically, so the Code-First configuring approach is not valid for me.

How can I solve this? Is an approach problem, or am I messing around with the custom non ASP.NET Dependency Injection provider? Thank you all

Upvotes: 0

Views: 1721

Answers (1)

Steve Wong
Steve Wong

Reputation: 2256

There is an issue with your IDesignTimeDbContextFactory. EF Core is trying to your this factory to create a MySqlContext.

public class MySqlContextFactory : IDesignTimeDbContextFactory<MySqlContext>
{
    public MySqlContext CreateDbContext(string[] args)
    {
        // set up options
        DbContextOptionsBuilder optionsBuilder = new();
        ServerVersion mariaDbVersion = new MariaDbServerVersion(new Version(10, 6, 5));
        optionsBuilder.UseMySql(DatabaseCredentials.GetConnectionString(), mariaDbVersion);
        
        // *** this is the issue *** 
        // return default constructor W/O options (ie, UseMySql is never called)
        return new MySqlContext(); 
    }
}

You can add this constructor to your DbContext class:

public MySqlContext(DbContextOptions<MySqlContext> options)
        : base(options)
    {
    }

and then return new MySqlContext(optionsBuilder.Options) from your factory.

Upvotes: 4

Related Questions