在 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 哪一个是给定请求的合适实例。
我正在开发面向 .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 哪一个是给定请求的合适实例。