Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

DbContextFactory could be more tolerant of there being multiple constructors on a DbContext #24124

Closed
lowds opened this issue Feb 11, 2021 · 6 comments · Fixed by #24717
Closed
Assignees
Labels
area-dbcontext closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-enhancement
Milestone

Comments

@lowds
Copy link

lowds commented Feb 11, 2021

When creating a DbContext we often have one constructor that accepts DbContextOptions and another no-arg constructor so the DbContext will work with the EF tooling for creating migrations. However, when using DbContextFactory having more than one constructor on the DbContext results in the factory being unable to create a new DbContext and results in an exception

System.InvalidOperationException
Multiple constructors accepting all given argument types have been found in type

This is due to the implementation of DbContextFactorySource that mandates a single public non-static constructor

if (constructors.Length == 1)

Although there are work-arounds available with regards to the EF core tooling (https://docs.microsoft.com/en-gb/ef/core/cli/dbcontext-creation?tabs=dotnet-core-cli) it would be great if these were not required just because we are using DbContextFactory.


The DbContextFactorySource could be changed so the first 'if' statement is replaced with a for-each loop, iterating over each candidate constructor. The rest of the code inside the existing if statement is already checking parameter count and parameter types, so the for-each change would still allow the DbContextFactorySource to find the constructor that accepted DbContextOptions, but ignore any other constructors that may be present on the DbContext

@ajcvickers
Copy link
Member

@lowds The if line that you reference is an optimization for a specific case where there is one parameter and it is the DbContextOptions. The general case is ActivatorUtilities.CreateFactory(typeof(TContext), new Type[0]);, which uses the D.I. system to resolve dependencies. The D.I. system has limitations with multiple constructors--see https://docs.microsoft.com/en-us/dotnet/core/extensions/dependency-injection#constructor-injection-behavior. Issues in the D.I. system should be filed at https://github.com/dotnet/runtime

@lowds
Copy link
Author

lowds commented Feb 19, 2021

@ajcvickers thanks for the response. I think we've just run into this situation because are migrating from using DI with DbContext, to using DbContextFactory as we bring server-side blazor into our solution.

We have a stand-alone console application acting as our startup project (referencing EF design and tools packages) for creating EF migrations where rather than a web app where the DI services are configured (as our DbContext is in a .NET standard library) so the no-arg constructor was being used to support this.

Our DbContext currently has two constructors:

  • no-arg to support EF tooling when creating migrations
  • DbContextOptions so that configuration happens via the extension method services.AddDbContextFactory(options)

With the two constructors everything used to work when registering a DbContext via services, but moving to DbContextFactory to support Blazor has meant that EF now expects our DbContext to have exactly one constructor. I made a small XUnit file showing this at https://gist.github.com/lowds/d029bb8eefda07d0e18a04a7f7c42daa .

Currently we're working around it by registering our own CustomDbContextFactorySource in the services collection for our web app, where the custom source contains the for-loop proposed in my previous comment, though will admit this is not ideal as the IDbContextFactorySource is documented as an internal API.

If when using DbContextFactory our DbContext must have exactly one constructor then long term we will look to change our migrations startup project to use one of the workarounds specific to the tooling (i.e. implement design-time factories).

@ajcvickers
Copy link
Member

Note for triage: Related, possibly same root cause: dotnet/runtime#45119

@ajcvickers
Copy link
Member

Note for triage: not a duplicate of dotnet/runtime#45119. In this case, the exception is correct because both constructors are valid. However, we could in EF Core select the single parameter constructor here instead of falling back to D.I. This wouldn't fix the general case, but would make the case with these two common constructors work.

@Schoof-T
Copy link

Schoof-T commented Jul 16, 2021

Currently we're working around it by registering our own CustomDbContextFactorySource in the services collection for our web app, where the custom source contains the for-loop proposed in my previous comment, though will admit this is not ideal as the IDbContextFactorySource is documented as an internal API.

Do you have an example how you worked around this? I'm having the same issue and I can't seem to figure this out.

@jesslilly
Copy link

Another way to workaround it is to combine 2 similar constructors into one and allow null parameters like this:

    public ProductDbContext(DbContextOptions<ProductDbContext> options, IAuditUser auditUser = null) : base(options)
    {
        _userName = (auditUser == null) ? "System" : auditUser.Name;
    }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-dbcontext closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-enhancement
Projects
None yet
4 participants