首页 > 解决方案 > 无法使用 EF 6 为 PostgreSQL DbContext 注册作用域或临时服务

问题描述

我们尝试在带有 EF Core 的 ASP .Net Core 项目上使用 PostgreSQL(通过 Npgsql)。通过依赖注入,我们可以将 DbContext 添加为单例,但尝试将其用作瞬态或范围服务会在应用程序启动时触发 500.30 错误。

问题是,作为单例,当服务器上的并发查询同时使用 DbContext 时会失败,然后在执行控制器和服务时生成 500 个错误。正如下面在评论中所说,这是一个重大错误,不可行。

因此,我们尝试将其添加为瞬态,但未能将其正确插入服务集合中。这是我们没有使用单例(悲惨)方法的启动配置代码:

public void ConfigureServices(IServiceCollection services)
{
  try
  {
    services.AddHttpContextAccessor();
    services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();

    // initiating database service for DI
    DbContextOptions<EFCoreContext> optionsBuilder = new DbContextOptions<EFCoreContext>();
    OurDbContext context = new OurDbContext (optionsBuilder);
                
    services.AddDbContext<OurDbContext >(
      options => options.UseNpgsql(
        Configuration["ConnectionStrings:ConnectionString"]
      )
    );

    // initiating session service with in memory store
    Services.AddDistributedMemoryCache();

    services.AddSession(options =>
    {
      options.IdleTimeout = TimeSpan.FromMinutes(
        Double.Parse(
          Configuration["Session:DurationInMinutes"]
        )
      );
      options.Cookie.HttpOnly = true;
      options.Cookie.IsEssential = true;
      options.Cookie.Name = ".api.myAccess-TDF";
    });

    services.AddControllers();

    // adding localization, with customized PO file finder
    services.AddMvc().
      AddViewLocalization(
        LanguageViewLocationExpanderFormat.Suffix
     );

    services.AddPortableObjectLocalization(
      options => options.ResourcesPath = "Localization"
    );

    // adding application services
    services.AddSingleton<ILocalizationFileLocationProvider, TranslatorFileLocationProvider>();
    services.AddSingleton<ISiteService, SiteService>();
    services.AddSingleton<IQueryService, QueryService>();
    services.AddSingleton<IQueryStatusService, QueryStatusService>();
    services.AddSingleton<IAccountService, AccountService>();
    services.AddSingleton<ISnowService, SnowService>();

    // adding HttpContextAccessor dependency injection
    services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();

    // configurating application cultures
    services.Configure<RequestLocalizationOptions>(options => {
      var supportedCultures = new List<CultureInfo>
      {
        new CultureInfo("fr-FR"),
        new CultureInfo("fr")
      };

      options.DefaultRequestCulture = new RequestCulture("fr-FR");
      options.SupportedCultures = supportedCultures;
      options.SupportedUICultures = supportedCultures;
    });

    // adding translation service (which use Localizer by D.I.)
    services.AddSingleton<ITranslator, Translator>();

    // adding swagger interface
    services.AddSwaggerGen(c =>
    {
      c.SwaggerDoc(
        "v1", new OpenApiInfo {Title = "WebApi", Version = "v1"}
      );
    });

    // adding super permissive CORS, 
    // since this is an open API server, anybody can access it
    services.AddCors(options => {
      options.AddDefaultPolicy(builder => {
        builder.AllowAnyOrigin()
          .AllowAnyMethod()
          .AllowAnyHeader();
      });
    });

    // Create a MapperConfiguration instance with profiles 
    // and add the mapper as a service for Dependency Injection
    var mappingConfig = new MapperConfiguration(mc => {
      mc.AddProfile(new MappingProfile());
    });
    IMapper mapper = mappingConfig.CreateMapper();
    services.AddSingleton(mapper);

  } catch (Exception e) {
    Console.WriteLine(e)
  }
}

OurDbContext 是一个派生自 EFCoreContext 的类,即使我们手动执行 Scaffold-DbContext (我们首先使用数据库,所以当我们执行脚手架时它会重写 EFCoreContext 并且我们不能放置),它让我们可以从 appsettings.json 文件配置我们的连接字符串我们在此类中的配置参考)。这是该类的代码:

namespace App_DAL
{
  public partial class OurDbContext : EFCoreContext
  {
    private readonly DbContextOptionsBuilder OptionBuilder;

    public OurDbContext(DbContextOptions<EFCoreContext> options)
    : base(options)
    {
      OptionBuilder = new DbContextOptionsBuilder();
    }

    public OurDbContext(
      DbContextOptions<EFCoreContext> options, 
      DbContextOptionsBuilder optionBuilder
    ) : base(options)
    {
      OptionBuilder = optionBuilder;
    }

    /// <summary>
    /// Executes the database connection string definition when the context is configured
    /// The connection string is read from the main application appsettings.json file
    /// This file is defined in the starting project of the solution
    /// </summary>
    /// <param name="optionsBuilder">Optional context builder options</param>
    protected override void OnConfiguring(
      DbContextOptionsBuilder optionsBuilder
    ){
      if (!optionsBuilder.IsConfigured) {

        // getting the connection string from configuration files
        // two configuration files can be used : 
        // appsettings.json and appsettings.Development.json, 
        // both placed in the main project root directory

        string testingProjectDir = Directory.GetCurrentDirectory();
        string configPath = Path.Combine(
          testingProjectDir, "appsettings.json"
        );
        string configPathDev = Path.Combine(
          testingProjectDir, "appsettings.Development.json"
        );

        IConfigurationBuilder builder = new ConfigurationBuilder()
          .AddJsonFile(
            configPath, optional: false, 
            reloadOnChange: true
          );

        // if we have a development configuration file 
        // in the project then we add it to the main one, 
        // so it can redefine properties linked to the 
        // development platform

        if (File.Exists(configPathDev))
        {
          builder.AddJsonFile(
            configPathDev, optional: false, 
            reloadOnChange: true
          );
        }

        // now we read the connection string inside the configuration

        IConfigurationRoot configuration = builder.Build();

        string connectionString = configuration.GetSection(
          "ConnectionStrings:ConnectionString"
        ).Value;

        // unable to get the connection string :/
        // trigering an exception

        if (string.IsNullOrEmpty(connectionString))
        {
          string msg = "<ERROR> DemandesAccesContext.OnConfiguring - Unable to read connection string in application settings. Please check settings file content";
          Console.WriteLine(msg);
          throw new System.Exception(msg);
        }
        else
        {
          // the connection string is defined to access the database

          optionsBuilder.UseNpgsql(
            connectionString, 
            options => options.EnableRetryOnFailure()
          );
        }
      }
    }
  }
}

因此,从那里启动 API 应用程序将触发默认打开的 Swagger 页面上显示的 500.30 错误。当尝试访问 API 时,我们得到同样的错误:

IIS Express 日志中未跟踪任何异常,但数据库中的应用程序日志 (Serilog) 包含指向此的异常:

Error while validating the service descriptor 'ServiceType: App_DAL.OurDbContext Lifetime: Scoped ImplementationType: App_DAL.OurDbContext': 
No constructor for type 'App_DAL.OurDbContext' can be instantiated using services from the service container and default values. 

完整的异常堆栈包含 5 个内部异常,但它们都达到了这一点: OurDbContext 无法验证,因为它的构造函数和我们在服务集合中添加它的方式之间缺少某些东西。但坦率地说,我不明白如何纠正这一点。对不起,我在这里缺乏理解。有人知道如何使这件事起作用吗?

谢谢,弗朗索瓦

标签: c#dependency-injectionentity-framework-corenpgsqltransient

解决方案


推荐阅读