在 AutoFac 中,为什么我的通用存储库的 RegisterGeneric 调用顺序只对最后一个注册的存储库正常工作?

In AutoFac, why does the order of RegisterGeneric calls for my generic repositories only work correctly for the last one registered?

我正在开发面向 .NET 5 的 ASP.NET API,我需要访问三个不同的 SQL 数据库。

我正在使用 AutoFac 作为我的 DI(免责声明:我是 AutoFac 的新手,过去只在 DI 中使用过 ASP.NET 核心构建)。

我也在使用 Steve Smith (https://github.com/ardalis/CleanArchitecture)

的 CleanArchitecture 框架

我正在使用通用存储库,每个 DbContext 一个(代表 3 个不同的数据库)。

我的启动项目的 Startup.cs --> ConfigureContainer 方法中有以下代码;

    public void ConfigureContainer(ContainerBuilder builder)
    {
        builder.RegisterModule(new DefaultCoreModule());
        builder.RegisterModule(new DefaultInfrastructureModule(_env.EnvironmentName == "Development"));
    }

在我的 DefaultInfrastructureModule class 中,我有以下代码;

private void RegisterCommonDependencies(ContainerBuilder builder)
{
    builder.RegisterGeneric(typeof(IdpRepository<>))
        .As(typeof(IRepository<>))
        .As(typeof(IReadRepository<>))
        .InstancePerLifetimeScope();

    builder.RegisterGeneric(typeof(CdRepository<>))
        .As(typeof(IRepository<>))
        .As(typeof(IReadRepository<>))
        .InstancePerLifetimeScope();

    builder.RegisterGeneric(typeof(M1Repository<>))
        .As(typeof(IRepository<>))
        .As(typeof(IReadRepository<>))
        .InstancePerLifetimeScope();
    
    ...
    ...
    ...
}

项目编译运行。但是,当我尝试从存储库调用方法(例如 ListAsync 方法)时,唯一正常工作的存储库是注册序列中列出的最后一个存储库。

例如,使用上面代码中列出的顺序,对 M1Repository 的 ListAsync 调用按预期工作,但对 IdpRepository 或 CdRepository 中的 ListAsync 方法的调用失败并出现 SqlException;

Microsoft.Data.SqlClient.SqlException (0x80131904): Invalid object name 'User'.
   at Microsoft.Data.SqlClient.SqlCommand.<>c.<ExecuteDbDataReaderAsync>b__169_0(Task`1 result)

好像不明白 DbContext.Set 应该查询用户 table(复数),而不是用户 table.

现在,如果我重新排列 DI 注册的顺序并将 IdpRepository 的注册移动到顺序中最后注册的一个,ListAsync 调用将适用于 IdpRepository 但随后对 M1Repository 和 CdRepository 的调用失败有一个类似的 SQL 异常。

无论我以什么顺序注册它们,只有最后一个注册的才能正确工作。

所有三个通用存储库都使用与 CleanArchitecture 模板中使用的相同的基本设计。 CdRepository 示例如下所示;

public class CdRepository<T> : RepositoryBase<T>, IReadRepository<T>, IRepository<T>
    where T : class
{
    public CdRepository(CdDbContext dbContext)
        : base(dbContext)
    {
    }
}

我确定这只是一个 AutoFac 问题,我不知道,因为我是 AutoFac 初学者。我似乎无法解决它。

有什么想法吗?

[更新 1]

这是我的 ListDepartments 端点,我在其中注入存储库。

public class ListDepartments : BaseAsyncEndpoint
    .WithRequest<DepartmentListRequest>
    .WithResponse<DepartmentListResponse>
{
    private readonly IReadRepository<Department> _repository;

    public ListDepartments(IReadRepository<Department> repository)
    {
        _repository = repository;
    }

    [HttpGet("/Organizations/{OrganizationId}/Departments")]
    [SwaggerOperation(
        Summary = "Gets a list of all Departments for the specified Organization ID",
        Description = "Gets a list of all Departments for the specified Organization ID",
        OperationId = "Department.ListDepartments",
        Tags = new[] { "OrganizationEndpoints" })
    ]
    public override async Task<ActionResult<DepartmentListResponse>> HandleAsync([FromQuery] DepartmentListRequest request, CancellationToken cancellationToken)
    {
        var response = new DepartmentListResponse();
        response.OrganizationId = request.OrganizationId;
        response.Departments = (await _repository.ListAsync(cancellationToken))
            .Select(department => new DepartmentListSummaryRecord(department.Id, department.Name))
            .ToList();
        return Ok(response);
    }
}

[更新 2]

阅读@ssmith 的评论后,我意识到我需要为 3 个通用存储库中的每一个提供独特的接口。导致问题的原因是在 CleanArchitecture 模板中为 AutoFac 中的三个不同存储库注册中的每一个使用了 IRepository 和 IReadRepository 接口。我脑放屁的一个明显例子。

一旦我为三个存储库中的每一个创建了唯一的接口,解决方案就可以使用了。

这是修改后的存储库;

public class CdRepository<T> : RepositoryBase<T>, ICdReadRepository<T>, ICdRepository<T>
    where T : class
{
    public CdRepository(CdDbContext dbContext)
        : base(dbContext)
    {
    }
}


public class IdpRepository<T> : RepositoryBase<T>, IIdpReadRepository<T>, IIdpRepository<T>
    where T : class
{
    public IdpRepository(IdpDbContext dbContext)
        : base(dbContext)
    {
    }
}


public class M1Repository<T> : RepositoryBase<T>, IM1ReadRepository<T>, IM1Repository<T>
    where T : class
{
    public M1Repository(M1DbContext dbContext)
        : base(dbContext)
    {
    }
}

这是我在其中注册存储库的 DefaultInfrastructureModule class 的修订版。

private void RegisterCommonDependencies(ContainerBuilder builder)
{
    builder.RegisterGeneric(typeof(IdpRepository<>))
        .As(typeof(IIdpRepository<>))
        .As(typeof(IIdpReadRepository<>))
        .InstancePerLifetimeScope();

    builder.RegisterGeneric(typeof(CdRepository<>))
        .As(typeof(ICdRepository<>))
        .As(typeof(ICdReadRepository<>))
        .InstancePerLifetimeScope();

    builder.RegisterGeneric(typeof(M1Repository<>))
        .As(typeof(IM1Repository<>))
        .As(typeof(IM1ReadRepository<>))
        .InstancePerLifetimeScope();

    ...
    ...
    ...
}

感谢@ssmith 的指导。 :)

Autofac 将为您提供最近注册的实例类型,这就是您获得最后一个的原因。如果需要,您实际上可以(通过 DI)在端点 class 中请求 IEnumerable<IReadRepository<Department>>,Autofac 应该为您提供该类型的所有已注册实例。这可能有助于您进行调试,或者如果您想在端点中使用某种策略来 select 哪一个是给定请求的合适实例。