OrchardCMS / OrchardCore

Orchard Core is an open-source modular and multi-tenant application framework built with ASP.NET Core, and a content management system (CMS) built on top of that framework.
https://orchardcore.net
BSD 3-Clause "New" or "Revised" License
7.43k stars 2.4k forks source link

[Solution included] Unable to add the EntityFramework Core migration #9882

Closed NickMaev closed 3 years ago

NickMaev commented 3 years ago

Describe the bug

When I tried to add initial Entity Framework Core migration it couldn't create the database context.

To Reproduce

Steps to reproduce the behaviour:

  1. Create a solution with name Test.
  2. Create the web application project in the solution with name WebApp from the latest dev branch using the following commands:
    dotnet new -i "OrchardCore.ProjectTemplates::1.0.0-rc2-*" --nuget-source https://nuget.cloudsmith.io/orchardcore/preview/v3/index.json
    dotnet new occms
  3. Create the .NET Core 5 library with name Core;
  4. Add EntityFramework Core packages to both projects.
  5. Create the EF Core database context ApplicationDbContext in the Core project and one entity type.
  6. Try to create the initial migration. Run the following command in the solution's directory:
    dotnet ef migrations add Initial -c ApplicationDbContext -s .\WebApp -v -p .\Core
  7. Get the error:
    Unable to create an object of type 'ApplicationDbContext'. For the different patterns supported at design time, see https://go.microsoft.com/fwlink/?linkid=851728

Expected behavior

Migration will be created successfully.

Screenshots

image

Solution

The problem was in the Program.cs. I renamed the BuildHost static method to CreateHostBuilder and changed the return type to IHostBuilder.

Before: Program.cs

    public class Program
    {
        public static Task Main(string[] args)
            => BuildHost(args).RunAsync();

        public static IHost BuildHost(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureLogging(logging => logging.ClearProviders())
                .ConfigureWebHostDefaults(webBuilder => webBuilder
                    .UseStartup<Startup>()
                    .UseNLogWeb())
                .Build()
            ;
    }

After: Program.cs

    public class Program
    {
        public static Task Main(string[] args)
            => CreateHostBuilder(args).Build().RunAsync();

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureLogging(logging => logging.ClearProviders())
                .ConfigureWebHostDefaults(webBuilder => webBuilder
                    .UseStartup<Startup>()
                    .UseNLogWeb())
            ;
    }
ns8482e commented 3 years ago

Have you tried with latest dev templates? I guess rc2 templates are based on .net core 3.1 and not .net 5

NickMaev commented 3 years ago

Have you tried with latest dev templates? I guess rc2 templates are based on .net core 3.1 and not .net 5

It seems that the dev branch contains old templates:

image https://github.com/OrchardCMS/OrchardCore/blob/dev/src/OrchardCore.Cms.Web/Program.cs

ns8482e commented 3 years ago

Looks like templates needs update for EF as is looking for CreateHostBuilder method

ns8482e commented 3 years ago

The other solution that i am using in my 3.1 project is to create design time factory as below.

public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
    {
        public ApplicationDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
            optionsBuilder.UseSqlServer("******");

            return new ApplicationDbContext(optionsBuilder.Options);
        }
    }
NickMaev commented 3 years ago

The other solution that i am using in my 3.1 project is to create design time factory as below.

public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
    {
        public ApplicationDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
            optionsBuilder.UseSqlServer("******");

            return new ApplicationDbContext(optionsBuilder.Options);
        }
    }

Custom "Design time factory" has a lot of disadvantages such as:

ns8482e commented 3 years ago

Agree, this only helps you if you are not using DI to configure your Context options

NickMaev commented 3 years ago

Agree, this only helps you if you are not using DI to configure your Context options

... and you can't share your connection string from the appsettings.json or appsettings.[env].json

ns8482e commented 3 years ago

For my specific use case, for connection strings, I use command line with arguments and manage my uid/pwd in secret manager.

deanmarcussen commented 3 years ago

Is the solution here to update the templates to use CreateHostBuilder ?

ns8482e commented 3 years ago

@deanmarcussen Sure! template can be updated for EF

Btw, It's unrelated to OrchardCore CMS's connection string as OC's connection string is stored per Tenant under App_Data

NickMaev commented 3 years ago

@deanmarcussen Sure! template can be updated for EF

Btw, It's unrelated to OrchardCore CMS's connection string as OC's connection string is stored per Tenant under App_Data

It would be great to add some examples how to init the EC Core for tenants in the right way. 😉

ns8482e commented 3 years ago

@NickMaev OC is not using EF. Also each tenant can have it's own yessql.db

NickMaev commented 3 years ago

@NickMaev OC is not using EF. Also each tenant can have it's own yessql.db

I know it. But EF Core is the most popular ORM.

Skrypt commented 3 years ago

Dapper is quite popular too and you can use it with YesSQL.

ns8482e commented 3 years ago

@Skrypt @deanmarcussen I guess we can update the template to have CreateHostBuilder to align with latest ASP.NET Web application template.

@NickMaev this will help EF to read connection string from appsettings, However be aware that the change you are proposing is not related to OC's database connection string and EF will not connect to any tenant specific yessql.db as OC's connection string is stored under App_Data