实例化EF Core DbContext

时间:2019-07-11 12:32:35

标签: c# asp.net-core dependency-injection entity-framework-core

我在使用EF Core时遇到了一个奇怪的问题,我不明白为什么...

public class Startup
{
    static Config Config;

    public Startup(IConfiguration configuration)
    {
        Config = new Config();
        configuration.Bind(Config);
    }

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddHttpContextAccessor();
        services.AddScoped(ctx => ctx.GetService<IHttpContextAccessor>()?.HttpContext);
        services.AddScoped(ctx => ctx.GetService<HttpContext>()?.Request);
        services.AddAuthInfo();
        services.AddSingleton(Config);

        services.AddDbContext<MembershipDataContext>(options => options.UseSqlServer(Config.Connections["Membership"]));
        services.AddDbContext<CoreDataContext>(options => options.UseSqlServer(Config.Connections["Core"]));
        services.AddDbContext<B2BDataContext>(options => options.UseSqlServer(Config.Connections["B2B"]));

        services.AddScoped<IMembershipDataContext, MembershipDataContext>();
        services.AddScoped<ICoreDataContext, CoreDataContext>();
        services.AddScoped<IB2BDataContext, B2BDataContext>();

        ...

...我从每个请求中提取自定义身份验证信息,并将其注入到我的DbContexts中。

由于初始化过程,我必须拥有一个接受DbContextOptions的CTOR,因此我只添加了第二个,希望它可以调用正确的一个...

public EFDataContext(DbContextOptions options, IAuthInfo auth) : base(options) { AuthInfo = auth; }

public EFDataContext(DbContextOptions options) : base(options) { }

...在运行时,我看到两个CTOR在单个请求中都被击中了几次(不是我期望的那样)。

在其他帖子中,我注意到许多人都说我不需要最后三行,但是删除它们会给我带来例外,告诉我其他对象不能再由DI构造。

所以我很困惑...

我该如何工作,以便每个请求只能构造1个实例,并且在执行此操作时仅击中具有最多参数的CTOR?

2 个答案:

答案 0 :(得分:0)

所以我想出了如果我修改工厂代码,则可以在数据库“设置”场景中很好地处理构造。

然后,这使我只能在需要的上下文中使用一个CTOR,但是最终结果是我仍然需要这些额外的行,否则它会因DI异常而失败...

尝试激活“其他类型”时,无法解析“ IMyContext”类型的服务。

然后单击它……我从不要求“ MyDbContext”,我总是要求“ IMyDbContext”……额外的行是将容器中的接口调用映射到强类型上下文而不是“将上下文重新添加到”容器中。

答案 1 :(得分:0)

您不应该不要根据范围分别添加上下文。那是问题的主要根源。当您使用AddDbContext时,它将注册您的DbContext派生的类。如果继续请求一个接口,它不知道如何满足该接口。它只知道如何满足班级要求。这就是为什么您会获得异常,这就是为什么您感到需要添加单独的接口注册。但是,当您执行那个时,您正在请求上下文类的另一个实例,因为它是完全不同的注册。

您不需要上下文界面。我看到人们一直在这样做,这只是在麻木。绝对为零。在您的上下文中应该可以公开访问的唯一内容应该是DbSet属性和内置方法,例如AddSaveChangesAsync等。如果您想或需要与DbSet,有Set<T>,因此您的上下文类可以直接注入。此外,这不像您要对此上下文进行多种实现。它是数据库的表示形式,因此,它是:一直以来都是一种实现。

此外,除了DbContextOptions之外,您不应在上下文中注入其他任何内容。如果您需要服务,DbContext有一个内部服务提供商,那就是您应该使用的,即this.GetService<IAuthInfo>

更新

基于评论主题,我认为我的建议不清楚。您应该有类似的内容:

public interface IService
{
    void DoSomething();
}

然后,您将为每个提供商创建 this 的实现,例如CosmosDbServiceRavenDbServiceEFService等。然后在EFService中注入上下文类,而不是接口: >

public class EFService : IService
{
    private readonly MyDbContext _context;

    public EFService(MyDbContext context)
    {
        _context = context;
    }

    public void DoSomething()
    {
        // use _context
    }
}

通过这种方式,出于EF的考虑,上下文仅在 处存在。没有多余的添加,并且那里没有业务逻辑。在代码中的其他任何位置,您都注入IService,然后通过DI容器将其提供给适当的提供程序实现(EF,Cosmos,Raven等)。您所有的业务逻辑都应该存在于服务类中,并且它不需要上下文的接口。

这也可能意味着您也不需要上下文中的IAuthInfo之类的多余服务,这意味着您可以避免使用内部服务提供商。您应该将IAuthInfo注入服务类。

您现在正在做的事情恰恰相反。通过使上下文实现一个与目的无关的接口,您将从根本上将业务逻辑与EF纠缠在一起:作为一个简单的工作单元。