dotnet / Scaffolding

Code generators to speed up development.
MIT License
632 stars 226 forks source link

Scaffolding doesn't work when DbContext is in a separate project #1765

Open signumq opened 2 years ago

signumq commented 2 years ago

Consistently getting this error when attempting to scaffold a controller while having DbContext in a separate project:

There was an error running selected code generator:

'Unable to resolve service for type 
'Microsoft.EntityFrameworkCore.DbContextOptions`1[MyProject.Data.MyProjectDbContext]' 
while attempting to activate 'MyProject.Data.MyProjectDbContext'.'

MyProjectDbContext:

public class MyProjectDbContext : DbContext
{
    public MyProjectDbContext(DbContextOptions<MyProjectDbContext> options) : base (options)
    { }

    public DbSet<Note> Notes => Set<Note>();
}

DbContext is registered in Program.cs as follows:

builder.Services.AddDbContext<MyProjectDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("MyProjectDbContext")));

Scaffolding works if DbContext moved to the web project.

IDE: Visual Studio 2022 Community SDK: .NET 6.0 Packages: "Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="6.0.1" "Microsoft.EntityFrameworkCore.Tools" Version="6.0.1" "Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.1"

nitsujT commented 2 years ago

Did you find a solution? I'm having the same issue.

The migrations are working fine, but Scaffolding wont work. Only slight difference is I am using IdentityDbContext.

iannicktl commented 2 years ago

Same issue here . But with Version 6.02

mjohn commented 2 years ago

Same here.

cemg commented 2 years ago

I found a solution. You can create a dbcontext factory class in same folder with your ApplicationDbContext class. This factory class creates ApplicationDbContext at design time and scaffolding runs correctly.

public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
        public ApplicationDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
            optionsBuilder.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Database=EcommerceDb;Trusted_Connection=True;MultipleActiveResultSets=true");

            return new ApplicationDbContext(optionsBuilder.Options);
        }
}

IDE: VS 2022 Pro Framework: .NET6 Packages Version: 6.0.2

mjohn commented 2 years ago

I found a solution. You can create a dbcontext factory class in same folder with your ApplicationDbContext class. This factory class creates ApplicationDbContext at design time and scaffolding runs correctly.

public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
        public ApplicationDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
            optionsBuilder.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Database=EcommerceDb;Trusted_Connection=True;MultipleActiveResultSets=true");

            return new ApplicationDbContext(optionsBuilder.Options);
        }
}

IDE: VS 2022 Pro Framework: .NET6 Packages Version:

Thanks Cem, nice workaround though, its working! Teşekkürler.

nitsujT commented 2 years ago

@cemg Thanks! This also works for me.

avinashtauro-arctechinfo commented 2 years ago

Thanks @cemg. Found this after hours of testing. Works great

farazymaxit2021 commented 2 years ago

thanks for the solution

d99mli commented 2 years ago

Had the same issue, with scaffolding an api controller. This did the trick, THANKS!

signumq commented 2 years ago

This solution works, thank you! But how do I pass connection string to it from configuration? It wants to use parameterless constructor, so I can't inject IConfiguration.

cemg commented 2 years ago

You can use ConfigurationBuilder class for build the configuration.

public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
    public ApplicationDbContext CreateDbContext(string[] args)
    {
        var configuration = new ConfigurationBuilder()
            .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
            .AddJsonFile("appsettings.json")
            .Build();

        var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
        optionsBuilder.UseSqlServer(configuration.GetConnectionString("Default"));

        return new ApplicationDbContext(optionsBuilder.Options);
    }
}
JuliusJacobsohn commented 2 years ago

I found a solution. You can create a dbcontext factory class in same folder with your ApplicationDbContext class. This factory class creates ApplicationDbContext at design time and scaffolding runs correctly.

public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
        public ApplicationDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
            optionsBuilder.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Database=EcommerceDb;Trusted_Connection=True;MultipleActiveResultSets=true");

            return new ApplicationDbContext(optionsBuilder.Options);
        }
}

IDE: VS 2022 Pro Framework: .NET6 Packages Version: 6.0.2

Although this resolved the issue for me, it introduced some side effects which I only realized weeks later. It changes some of the internals of identity classes, see the following migration:

            migrationBuilder.AlterColumn<string>(
                name: "Name",
                table: "AspNetUserTokens",
                type: "nvarchar(450)",
                nullable: false,
                oldClrType: typeof(string),
                oldType: "nvarchar(128)",
                oldMaxLength: 128);

            migrationBuilder.AlterColumn<string>(
                name: "LoginProvider",
                table: "AspNetUserTokens",
                type: "nvarchar(450)",
                nullable: false,
                oldClrType: typeof(string),
                oldType: "nvarchar(128)",
                oldMaxLength: 128);

            migrationBuilder.AlterColumn<string>(
                name: "ProviderKey",
                table: "AspNetUserLogins",
                type: "nvarchar(450)",
                nullable: false,
                oldClrType: typeof(string),
                oldType: "nvarchar(128)",
                oldMaxLength: 128);

            migrationBuilder.AlterColumn<string>(
                name: "LoginProvider",
                table: "AspNetUserLogins",
                type: "nvarchar(450)",
                nullable: false,
                oldClrType: typeof(string),
                oldType: "nvarchar(128)",
                oldMaxLength: 128);

Even though nothing else was changed in the project (related to the database schema), when your proposed class is present, these columns are changed when trying to create a new migration. Removing the class and rescaffolding the migration results in an empty migration.

Setting the MaxLengthForKeys property manually in the relevant Program.cs (as mentioned here) changes nothing.

Not sure where this comes from but maybe this is useful information to someone.

ataccounts commented 2 years ago

Worked on first try. Thanks!

FaouziSelmi commented 2 years ago

thanks bro

aftabalamans commented 1 year ago
configuration 

Not working at my end getting same issue

Microsoft Visual Studio Community 2022 (64-bit) - Preview Version 17.4.0 Preview 2.1

TargetFramework net7.0 Microsoft.VisualStudio.Web.CodeGeneration.Design, Version=7.0.0-rc.1.22452.2

rafaoliveira4 commented 1 year ago

@cemg Thanks! This work for me.

awdorrin commented 1 year ago
Ipublic class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
    public ApplicationDbContext CreateDbContext(string[] args)
    {
        var configuration = new ConfigurationBuilder()
            .SetBasePath(AppDomain.CurrentDomain.BaseDirectory)
            .AddJsonFile("appsettings.json")
            .Build();

        var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
        optionsBuilder.UseSqlServer(configuration.GetConnectionString("Default"));

        return new ApplicationDbContext(optionsBuilder.Options);
    }
}

Issue with this approach is that it doesn't consider environment (Development/Staging/Production) for the appsettings.{env}.json.

HagenKnight commented 1 year ago
configuration.GetConnectionString("Default")

It was useful for me but the SetBasePath method throwed me an error. I fix it adding two nugget packages:

Microsoft.Extensions.Configuration.FileExtensions Microsoft.Extensions.Configuration.Json

The answer was commented on: https://stackoverflow.com/questions/36001695/setting-base-path-using-configurationbuilder

mhasz commented 1 year ago

Eu encontrei uma solução. Você pode criar uma classe de fábrica dbcontext na mesma pasta com sua classe ApplicationDbContext. Essa classe de fábrica cria ApplicationDbContext em tempo de design e o scaffolding é executado corretamente.

public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
        public ApplicationDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
            optionsBuilder.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Database=EcommerceDb;Trusted_Connection=True;MultipleActiveResultSets=true");

            return new ApplicationDbContext(optionsBuilder.Options);
        }
}

IDE: VS 2022 Pro Framework: Pacotes .NET6 Versão: 6.0.2

Embora isso tenha resolvido o problema para mim, introduziu alguns efeitos colaterais que só percebi semanas depois. Ele altera algumas das partes internas das classes de identidade, consulte a migração a seguir:

            migrationBuilder.AlterColumn<string>(
                name: "Name",
                table: "AspNetUserTokens",
                type: "nvarchar(450)",
                nullable: false,
                oldClrType: typeof(string),
                oldType: "nvarchar(128)",
                oldMaxLength: 128);

            migrationBuilder.AlterColumn<string>(
                name: "LoginProvider",
                table: "AspNetUserTokens",
                type: "nvarchar(450)",
                nullable: false,
                oldClrType: typeof(string),
                oldType: "nvarchar(128)",
                oldMaxLength: 128);

            migrationBuilder.AlterColumn<string>(
                name: "ProviderKey",
                table: "AspNetUserLogins",
                type: "nvarchar(450)",
                nullable: false,
                oldClrType: typeof(string),
                oldType: "nvarchar(128)",
                oldMaxLength: 128);

            migrationBuilder.AlterColumn<string>(
                name: "LoginProvider",
                table: "AspNetUserLogins",
                type: "nvarchar(450)",
                nullable: false,
                oldClrType: typeof(string),
                oldType: "nvarchar(128)",
                oldMaxLength: 128);

Mesmo que nada mais tenha sido alterado no projeto (relacionado ao esquema do banco de dados), quando sua classe proposta está presente, essas colunas são alteradas ao tentar criar uma nova migração. Remover a classe e reestruturar a migração resulta em uma migração vazia.

Definir a MaxLengthForKeyspropriedade manualmente no relevante Program.cs(como mencionado aqui ) não altera nada.

Não tenho certeza de onde isso vem, mas talvez seja uma informação útil para alguém.

I believe that your problem has come from elsewhere, microsoft itself recommends building a db context factory.

ahaque98 commented 1 year ago

I found a solution. You can create a dbcontext factory class in same folder with your ApplicationDbContext class. This factory class creates ApplicationDbContext at design time and scaffolding runs correctly.

public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
        public ApplicationDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
            optionsBuilder.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Database=EcommerceDb;Trusted_Connection=True;MultipleActiveResultSets=true");

            return new ApplicationDbContext(optionsBuilder.Options);
        }
}

IDE: VS 2022 Pro Framework: .NET6 Packages Version: 6.0.2

Thanks. Worked like a charm!

VladDerptastic commented 1 year ago

I found a solution. You can create a dbcontext factory class in same folder with your ApplicationDbContext class. This factory class creates ApplicationDbContext at design time and scaffolding runs correctly.

public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
        public ApplicationDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
            optionsBuilder.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Database=EcommerceDb;Trusted_Connection=True;MultipleActiveResultSets=true");

            return new ApplicationDbContext(optionsBuilder.Options);
        }
}

IDE: VS 2022 Pro Framework: .NET6 Packages Version: 6.0.2

Does this also apply when referencing a different DBContext? I've created 2 factory classes - one for the ApplicationDbContext, and one for [MyOwnDBContext] which is in another project (but referenced in its factory of course) and placed them in the same folder with ApplicationDbContext.cs and the problem still persists. I've tried with only one or the other, and that still doesn't help. I'm a bit stuck, honestly.

arezaie9331 commented 1 year ago

I found a solution. You can create a dbcontext factory class in same folder with your ApplicationDbContext class. This factory class creates ApplicationDbContext at design time and scaffolding runs correctly.

public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
        public ApplicationDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
            optionsBuilder.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Database=EcommerceDb;Trusted_Connection=True;MultipleActiveResultSets=true");

            return new ApplicationDbContext(optionsBuilder.Options);
        }
}

IDE: VS 2022 Pro Framework: .NET6 Packages Version: 6.0.2

Is Also works for me . thx❤️

ghhv commented 1 year ago

This doesn't work as shown with .NET 7.. You now need to add new package references. See this page - https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration#basic-example

hamzabourkha commented 1 year ago

This doesn't work as shown with .NET 7.. You now need to add new package references. See this page - https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration#basic-example

it works for me in .NET 7

tiennsloit commented 1 year ago

I found a solution. You can create a dbcontext factory class in same folder with your ApplicationDbContext class. This factory class creates ApplicationDbContext at design time and scaffolding runs correctly.

public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
        public ApplicationDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
            optionsBuilder.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Database=EcommerceDb;Trusted_Connection=True;MultipleActiveResultSets=true");

            return new ApplicationDbContext(optionsBuilder.Options);
        }
}

IDE: VS 2022 Pro Framework: .NET6 Packages Version: 6.0.2

Awesome! Thanks cemg. It works for me.

kdenaeem commented 1 year ago

Hey @cemg Im having a similar issue with creating a Scaffolding item. Im trying to create a MVC controller with views, using Entity framework on an example application. I've followed this video step by step but Im stuck at creating this controller https://www.youtube.com/watch?v=BfEjDD8mWYg image I have my project on github, if you could have a look at the repo you'll see the exact same structure as my project. image Thats the error I'm having.

Thanks

JoshuaPHolden commented 10 months ago

Is there a more concrete solution to this? Creating a temporary factory feels pretty janky. It's been several years, lets FIX the tool instead of work around it.

uehlbran commented 9 months ago

Is there a more concrete solution to this? Creating a temporary factory feels pretty janky. It's been several years, lets FIX the tool instead of work around it.

I have to agree. It's absolutely absurd that a workaround is required for what has to be one of the most common scenarios in using ef core.

svrooij commented 9 months ago

I found a solution. You can create a dbcontext factory class in same folder with your ApplicationDbContext class. This factory class creates ApplicationDbContext at design time and scaffolding runs correctly.

public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
        public ApplicationDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
            optionsBuilder.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Database=EcommerceDb;Trusted_Connection=True;MultipleActiveResultSets=true");

            return new ApplicationDbContext(optionsBuilder.Options);
        }
}

IDE: VS 2022 Pro Framework: .NET6 Packages Version: 6.0.2

I cannot believe this incredible simple solution is not in the official documentation, @cemg thank you for this!!

1a2e1 commented 6 months ago

you saved us cemg

I was considering creating everything by hand, but it seems that divine intervention has sent you to help us. AI helps to Update my gratitude: "i was thinking about creating it all by hands, but divine itself send you for us".

Thanks a lot

JordanCampos20 commented 5 months ago

I found a solution. You can create a dbcontext factory class in same folder with your ApplicationDbContext class. This factory class creates ApplicationDbContext at design time and scaffolding runs correctly.

public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
        public ApplicationDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
            optionsBuilder.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Database=EcommerceDb;Trusted_Connection=True;MultipleActiveResultSets=true");

            return new ApplicationDbContext(optionsBuilder.Options);
        }
}

IDE: VS 2022 Pro Framework: .NET6 Packages Version: 6.0.2

Thank you very much, this saved my creation of razor pages with entity framework

3baaady07 commented 1 month ago

I found a solution. You can create a dbcontext factory class in same folder with your ApplicationDbContext class. This factory class creates ApplicationDbContext at design time and scaffolding runs correctly.

public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
{
        public ApplicationDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
            optionsBuilder.UseSqlServer("Server=(localdb)\\MSSQLLocalDB;Database=EcommerceDb;Trusted_Connection=True;MultipleActiveResultSets=true");

            return new ApplicationDbContext(optionsBuilder.Options);
        }
}

IDE: VS 2022 Pro Framework: .NET6 Packages Version: 6.0.2

I tried that and it worked, however with a pesky bug. This ends up scuffolding its own instance of ApplicationDbContext in the Identity Area -- (mine is called PieDbContext). image This PieDbContext extends IdentityDbContext<IdentityUser>. This type takes in an IdentityUser type parameter which, by default, has a string type parameter. I already have a type that extends IdentityUser<Guid>, and I use it in my own PieDbContext, but for some reason I couldn't get the scuffolding to use it.

sliekens commented 1 month ago

I am cautiously optimistic that I found the bug and I raised a PR to fix it (#2869).

deepchoudhery commented 1 month ago

Merged the fix suggested above, unfortunately updates will go out 08/13 for .NET 8 and 9 (keeping the issue open for feedback)

Also, there will be something new for scaffolding for you all to try. It will be lot more stable and we are not reusing any of the existing scaffolding engine. Will keep you udpated!