I am working on a .NET 5 targeted ASP.NET API where I need to access three different SQL databases.

I am using AutoFac as my DI (disclaimer: I am new to AutoFac and have only used the ASP.NET Core build in DI in the past).

I am also using the CleanArchitecture framework from Steve Smith (https://github.com/ardalis/CleanArchitecture)

I am using generic repositories, one for each DbContext (representing the 3 different databases).

I have the following code in my startup project's Startup.cs --> ConfigureContainer method;

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

And in my DefaultInfrastructureModeule class, I have the following code;

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();
    
    ...
    ...
    ...
}

The project compiles and runs. However when I try to call methods, for example the ListAsync method, from the repositories, the only repository that works properly is the one that is the last one listed in the registration sequence.

For example, using the order listed in the code above, a ListAsync call to the M1Repository works as expected, but calls to the ListAsync methods in the IdpRepository or CdRepository fail with a SqlException;

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

Like it doesn't understand the DbContext.Set should query the Users table (plural), not the User table.

Now, if I rearrange the order of the DI registrations and move the registration of the IdpRepository to the last one registered in the order, The ListAsync call then works for the IdpRepository but then the calls to the M1Repository and CdRepository fail with a similar SQL exception.

No matter what order I register them in, only the last one registered works correctly.

All three generic repositories use the same basic design that is used in the CleanArchitecture template. The CdRepository example is shown below;

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

I am sure this is just an AutoFac issue I am not aware of since I am an AutoFac beginner. I just can't seem to resolve it.

Any ideas?

[UPDATE 1]

Here is my ListDepartments endpoint, where I inject a repository.

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);
    }
}

[Update 2]

After reading the comment from @ssmith, I realized that I needed unique interfaces for each of the 3 generic repositories. What was causing the problem was using the IRepository and IReadRepository interfaces presented in the CleanArchitecture template for each of the three different repository registrations in AutoFac. An obvious example of a brain fart on my part.

Once I created unique interfaces for each of the three repositories, the solution is now working.

Here are the revised repositories;

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)
    {
    }
}

Here is the revision to my DefaultInfrastructureModule class where I am registering the repositories.

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();

    ...
    ...
    ...
}

Thanks @ssmith for the guidance. :)

1

There are 1 answers

0
ssmith On BEST ANSWER

Autofac will give you the most recently registered instance type, which is why you're getting the last one. You can actually request (via DI) an IEnumerable<IReadRepository<Department>> in your endpoint class if you want, and Autofac should give you all of the registered instances for that type. That might help you in debugging, or if you want to use some strategy in the endpoint to select which one is the appropriate instance for a given request.