类型为 DbContext 的对象无法池化,因为它没有一个公共构造函数来接受类型为 DbContextOptions 的单个参数。

38

我正在尝试将我们当前的 .Net Core 应用程序从 1.1 升级到 2.0,但是遇到了这个运行时错误:"类型为 'CoreContext' 的 DbContext 无法被池化,因为它没有单一公共构造函数接受一个类型为 DbContextOptions 的参数。"

这是由于使用了新的 IServiceCollection.AddDbContextPool<> 函数导致的。当我使用 IServiceCollection.AddDbContext<> 时仍然可以工作。

这个应用程序是基于数据库的,所以我使用'Scaffold-DbContext'生成所有的上下文。由于这个原因和需要注入其他服务,我有一个每个上下文都有的扩展,像这样:

public partial class CoreContext
{
    public CoreContext(
        DbContextOptions<CoreContext> options,
        IUserService userService,
        IAuditRepository auditRepository
        ) : base(options) {...}
}
无论何时我运行Scaffold-DbContext,我都会从CoreContext中删除自动生成的构造函数,但即使我将其放在那里,我仍然会收到此错误。
public partial class CoreContext : DbContext
{
    public CoreContext(DbContextOptions<CoreContext> options) : base(options) {}
}

我已经将 Program.cs 更新为新的风格:

public class Program
{
    public static void Main(string[] args)
    {
        BuildWebHost(args).Run();
    }

    public static IWebHost BuildWebHost(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .Build();
}

而Startup.cs非常简单:

public IServiceProvider ConfigureServices(IServiceCollection services)
{
    ...
    services.AddDbContextPool<CoreContext>(options => options.UseSqlServer(absConnectionString));
    ...
}

我正在使用Autofac进行依赖注入,如果有帮助的话。目前我将默认回退到非池化的替代方案,但是利用这个功能会很好。


1
我有同样的问题。你发现如何解决它了吗,还是回到了非池化状态?能够使用这个新功能将会很棒。 - grokky
我退回到非池选项。还没有时间去悲叹它,需要继续前进。 - Jeff Keslinke
1
@JeffKeslinke,你解决了这个问题吗? - C.Ikongo
异常信息非常清晰明了:上下文要求“一个接受类型为DbContextOptions的单个参数的公共构造函数”。 - Gert Arnold
6个回答

45

使用DbContext池时,您在派生的DbContext类中拥有的状态(例如私有字段)将被保留。这意味着您服务的生命周期现在是singleton。因此,在这里不应该有其他注入的服务。 但是可以通过以下方式查询所需的服务: 首先,我们应该在DbContextOptionsBuilder上使用UseInternalServiceProvider方法告诉EF要使用哪个服务提供程序来获取其服务。此服务提供程序必须为EF和任何提供程序配置所有服务。因此,我们应手动注册EF服务:

services.AddEntityFrameworkSqlServer();

然后介绍应用程序的服务提供商,现在也包括 EF 服务:

services.AddDbContextPool<ApplicationDbContext>((serviceProvider, optionsBuilder) =>
{
   optionsBuilder.UseSqlServer("...");
   optionsBuilder.UseInternalServiceProvider(serviceProvider);
});

之后定义这些命名空间:

using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;

现在,您可以使用以下方法在应用程序中的ApplicationDbContext类中访问已注册的服务:

var siteSettings = this.GetService<IOptionsSnapshot<SiteSettings>>();
var siteSettings = this.GetInfrastructure().GetRequiredService<IOptionsSnapshot<SiteSettings>>();

this 是 DbContext 的当前实例。


6
很好的回答,+1。但需要补充说明optionsBuilder.UseInternalServiceProvider(serviceProvider)并非必需,调用GetService<T>将从默认提供程序解析。 - davidcarr
与上述评论相关 https://github.com/aspnet/EntityFrameworkCore/pull/8622 - davidcarr
如何获取具有参数的服务。即使 SiteSettings 具有其他依赖项,在我的情况下,它会抛出编译器错误。 - jayasurya_j

25

移除DbContext类中的默认构造函数,这对我有用。


这句话与之前的回答完全相同(“删除空构造函数”),因此我只能得出结论,这是为了确认那个答案。请不要重复回答以确认它们,这只会增加噪音。请投票支持它们。 - Gert Arnold

11

8
问题在于 Scaffold-DbContext 会在其中添加额外的构造函数。 - Simon_Weaver
(至少现在是这样的 - 八月份时不是) - Simon_Weaver
仍然添加它。我不明白为什么最先进的功能总是会带来最多的痛苦。这让你觉得自己做错了。 - Simon_Weaver
2
完美的答案。这个错误应该是“因为它有多个公共...”而不是它现在的样子。在我们的文化中,“因为你没有一个X”听起来像“X或更多是可以的,但你一个都没有”,例如:“因为你没有一美元,你破产了”...虽然当前的短语是准确的,但在我们的文化中也存在内在的歧义。 - Rodger

4
当你使用"Scaffold-Dbcontext"时,通常会遇到这个问题,因为它生成了两个构造函数。
简单解决方案:
  1. AddDbContextPool:如果你想使用AddDbContextPool,请删除空的构造函数,并保留带有DbContextOptionsBuilder的构造函数。请注意,在这种情况下,您可能需要按照之前的帖子中建议的提供选项。

  2. AddDbContext:使用AddDbContext可以拥有两个构造函数/重载。

注意:AddDbContextPool出于性能原因更受欢迎!


3

建议使用AddDbContext而不是AddDbContextPool。这在我遇到类似情况时有所帮助。 services.AddDbContext<CoreContext>(options => options.UseSqlServer(absConnectionString));


1
这个答案不能被视为有效,因为AddDbContext和AddDbContextPool是不同的。 - Marco Concas

3
在某些情况下,需要移除零参数构造函数。
//public MyContext()
//{
//}

在startup.cs的ConfigureServices()方法中,使用

"AddDbContext"

代替

"AddDbContextPool"

services.AddDbContext(options => options.UseSqlServer(absConnectionString));

这样做可以更好地处理数据库上下文,提高应用程序性能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接