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: Multiple constructors accepting all given argument types have been found in type #25273

Closed
Schoof-T opened this issue Jul 16, 2021 · 11 comments

Comments

@Schoof-T
Copy link

Schoof-T commented Jul 16, 2021

Ask a question

While implementing the DbContextFactory in our Blazor Server application (per described in https://docs.microsoft.com/en-us/aspnet/core/blazor/blazor-server-ef-core?view=aspnetcore-5.0) we get the exception 'Multiple constructors accepting all given argument types have been found in type'. We have a custom DbContext with multiple constructors.

Adding DbContextFactory

            services.AddDbContextFactory<ExampleDbContext>();

Custom DbContext

/// <summary>
    /// Data Access using Entity Framework
    /// </summary>
    public partial class ExampleDbContext : DbContext
    {
        private readonly IHttpContextAccessor _httpContextAccessor;

        #region construction

        public ExampleDbContext()
        {
        }

        public ExampleDbContext(IHttpContextAccessor httpContextAccessor)
        {
            _httpContextAccessor = httpContextAccessor;
        }

        public ExampleDbContext(DbContextOptions<ExampleDbContext> options, IHttpContextAccessor httpContextAccessor) : base(options)
        {
            _httpContextAccessor = httpContextAccessor;
        }

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

        #endregion

I have found the following issues regarding this: #24124
The person in said ticket seems to have found a solution using a design time factory. We tried to implement this as well, but this did not seem to have any effect.

    public class ExampleDbContextFactory : IDesignTimeDbContextFactory<ExampleDbContext>
    {
        public ExampleDbContextDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<ExampleDbContext>();
            optionsBuilder.UseOracle("CONNECTIONSTRING");

            return new ExampleDbContext(optionsBuilder.Options);
        }
    }

All we had to do to implement the design time factory was just add this class in the same library as the DbContext, correct?

We noticed this issue should be fixed in the lastest daily build of .NET 6, but we cannot currently use .NET 6. Is there anything we are missing or can do in the meantime?

Include stack traces

InvalidOperationException: Multiple constructors accepting all given argument types have been found in type 'Schema.Infrastructure.ExampleDbContext'. There should only be one applicable constructor.
Microsoft.Extensions.DependencyInjection.ActivatorUtilities.TryFindMatchingConstructor(Type instanceType, Type[] argumentTypes, ref ConstructorInfo matchingConstructor, ref Nullable<int>[] parameterMap)

Include provider and version information

EF Core version: 5.0.8
Database provider: Oracle.EntityFrameworkCore
Target framework: NET 5.0
Operating system: Windows 10
IDE: Visual Studio 2019 16.10

@ErikEJ
Copy link
Contributor

ErikEJ commented Jul 16, 2021

Have you tried moving the parameterless constructor down (or removing it) ?

@Schoof-T
Copy link
Author

Schoof-T commented Jul 16, 2021

Have you tried moving the parameterless constructor down (or removing it) ?

I just tried this and it unfortunately made no difference.

I did forget to mention that the DbContext works perfectly without using the DbContextFactory. The DbContext is also hosted in an external project (because it is used by multiple projects, also non Blazor projects).

@ajcvickers
Copy link
Contributor

@Schoof-T This is an error from dependency injection, which does not support multiple matching constructors. However, if I understand correctly, you don't get this error when using AddDbContext, which seems strange, since that will also resolve the context from D.I. Can you confirm that using AddDbContext works while AddDbContextFactory does not work with the same DbContext type?

@Schoof-T
Copy link
Author

Schoof-T commented Jul 20, 2021

@ajcvickers Can confirm I do not have this issue with AddDbContext. But I also do not have multiple matching constructors, I think?

So this works

//Startup.cs
services.AddDbContext<ExampleDbContext>(options => options.UseOracle(configuration["ConnectionStrings:ExampleConnection"]));

//TestClass.cs
  private ExampleDbContext _context;

        public CertificatesTests(ExampleDbContext context)
        {
            _context = context;
        }

        [Fact]
        public void Certificate_GetCertificatesFromView()
        {
            try
            {
                var result = _context.Certificates.ToList();
            }
            catch (Exception e)
            {
                throw new XunitException($"Failed to perform a DB get for entity {nameof(Certificate)} because of exception {e.InnerException?.Message ?? e.Message}.");
            }
        }

This does not work:

//Startup.cs
 services.AddDbContextFactory<ExampleDbContext>(options => options.UseOracle(configuration["ConnectionStrings:ExampleConnection"]));
 
//TestClass.cs
   private IDbContextFactory<ExampleDbContext> _dbFactory;

        public CertificatesTests(IDbContextFactory<ExampleDbContext> dbFactory)
        {
            _dbFactory = dbFactory;
        }

        [Fact]
        public void Certificate_GetCertificatesFromView()
        {
            try
            {
                var _context = _dbFactory.CreateDbContext();

                var result = _context.Certificates.ToList();
            }
            catch (Exception e)
            {
                throw new XunitException($"Failed to perform a DB get for entity {nameof(Certificate)} because of exception {e.InnerException?.Message ?? e.Message}.");
            }
        }
    }

@ajcvickers
Copy link
Contributor

@Schoof-T This looks related to dotnet/runtime#46132. You can tell D.I. which constructor to use using an attribute:

[ActivatorUtilitiesConstructor]
public ExampleDbContext(DbContextOptions<ExampleDbContext> options, IHttpContextAccessor httpContextAccessor) :
    base(options)
{
    _httpContextAccessor = httpContextAccessor;
}

@ajcvickers
Copy link
Contributor

@davidfowl Is this going to be fixed, or is it a limitation of ActivatorUtilities? Stand-alone repo:

public class ExampleDbContext : DbContext
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public ExampleDbContext()
    {
    }

    public ExampleDbContext(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public ExampleDbContext(DbContextOptions<ExampleDbContext> options, IHttpContextAccessor httpContextAccessor) :
        base(options)
    {
        _httpContextAccessor = httpContextAccessor;
    }

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

public class FakeHttpContextAccessor : IHttpContextAccessor
{
    public HttpContext HttpContext { get; set; }
}

public class Program
{
    public static void Main()
    {
        WithoutFactory();
        WithFactory();
    }

    private static void WithoutFactory()
    {
        var serviceProvider = new ServiceCollection()
            .AddScoped<IHttpContextAccessor, FakeHttpContextAccessor>()
            .AddDbContext<ExampleDbContext>(options => options.UseSqlite("Data Source=test.db"))
            .BuildServiceProvider();

        using (var scope = serviceProvider.CreateScope())
        {
            var context = scope.ServiceProvider.GetService<ExampleDbContext>();
            Console.WriteLine(context.GetType());
        }
    }
    
    private static void WithFactory()
    {
        var serviceProvider = new ServiceCollection()
            .AddScoped<IHttpContextAccessor, FakeHttpContextAccessor>()
            .AddDbContextFactory<ExampleDbContext>(options => options.UseSqlite("Data Source=test.db"))
            .BuildServiceProvider();

        using (var scope = serviceProvider.CreateScope())
        {
            var factory = scope.ServiceProvider.GetService<IDbContextFactory<ExampleDbContext>>();
            Console.WriteLine(factory.GetType());
            Console.WriteLine(factory.CreateDbContext().GetType());
        }
    }
}

@davidfowl
Copy link
Member

Fixed in .NET 6? No, it's been punted to 7 at the moment

@ajcvickers
Copy link
Contributor

@davidfowl 7 is fine. Thanks!

@Schoof-T
Copy link
Author

Schoof-T commented Aug 10, 2021

@Schoof-T This looks related to dotnet/runtime#46132. You can tell D.I. which constructor to use using an attribute:

[ActivatorUtilitiesConstructor]
public ExampleDbContext(DbContextOptions<ExampleDbContext> options, IHttpContextAccessor httpContextAccessor) :
    base(options)
{
    _httpContextAccessor = httpContextAccessor;
}

Hi, sorry for the late reply (I was on vacation) and thanks for the help! Although I think I'm missing something here. Doesn't the attribute make the DI always try to find the classes of your chosen constructor?
My DbContext is used in multiple applications, where sometimes I have options and httpContext and sometimes I don't. I need to be able to use it in both cases.

@midas1977
Copy link

A quick workaroud I use currently:

CoreContext.cs has multiple constructors (generated by scaffolding with EF tools)

public partial class CoreContext : DbContext
    {
        public CoreContext() { }

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

CoreContextActivator.cs additional context file, created manually

public partial class CoreContext : DbContext
{
    [ActivatorUtilitiesConstructor]
    public CoreContext(DbContextOptions<CoreContext> options, bool nothing = false) : base(options) { }
}

The trick is, to utilize the partial class and add another constructor with the ActivatorUtillitiesConstructor attribute.
In my case I use database-first approach with EF and the context file will be regenerated from time to time.
Tested with VS2022 preview 4.1, .NET 6 Blazor Server application

@Schoof-T
Copy link
Author

Schoof-T commented Jan 25, 2022

A quick workaroud I use currently:

CoreContext.cs has multiple constructors (generated by scaffolding with EF tools)

public partial class CoreContext : DbContext
    {
        public CoreContext() { }

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

CoreContextActivator.cs additional context file, created manually

public partial class CoreContext : DbContext
{
    [ActivatorUtilitiesConstructor]
    public CoreContext(DbContextOptions<CoreContext> options, bool nothing = false) : base(options) { }
}

The trick is, to utilize the partial class and add another constructor with the ActivatorUtillitiesConstructor attribute. In my case I use database-first approach with EF and the context file will be regenerated from time to time. Tested with VS2022 preview 4.1, .NET 6 Blazor Server application

Does this make it work, but now my IHttpContextAccessor httpContextAccessor is not being filled in. Do you happen to have a solution for that?

I've tried it like this as well, but no luck.

        [ActivatorUtilitiesConstructor]
        public JdnDbContext(DbContextOptions<JdnDbContext> options, IHttpContextAccessor httpContextAccessor = null, bool nothing = false) : base(options) { }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants