1
votes

I'm trying to register and inject DbContext instances into my components through Autofac. I know that the approach i'm currently using can be improved and i'm looking for ideas on how to achieve that too. At the moment I'm just playing around with the idea of a generic and trying to get it to work but it is throwing this exception. Is it possible Autofac is spawning multiple threads to inject the component?

System.Threading.Tasks.TaskCanceledException: A task was canceled.
   at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenDbConnectionAsync(Boolean errorsExpected, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenDbConnectionAsync(Boolean errorsExpected, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenAsync(CancellationToken cancellationToken, Boolean errorsExpected)
   at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.ExecuteReaderAsync(RelationalCommandParameterObject parameterObject, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.AsyncEnumerator.InitializeReaderAsync(DbContext _, Boolean result, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal.SqlServerExecutionStrategy.ExecuteAsync[TState,TResult](TState state, Func`4 operation, Func`4 verifySucceeded, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryingEnumerable`1.AsyncEnumerator.MoveNextAsync()
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleOrDefaultAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.ShapedQueryCompilingExpressionVisitor.SingleOrDefaultAsync[TSource](IAsyncEnumerable`1 asyncEnumerable, CancellationToken cancellationToken)
   at UserService.Repositories.UserRepository.FindByIdAsync(String id) in /Users/ryanr/Documents/invoicer-backend-microservices/src/UserService/Repositories/UserRepository.cs:line 31
   at UserService.Queries.Handlers.GetUserByIDQueryHandler.Handle(GetUserByIDQuery query) in /Users/ryanr/Documents/invoicer-backend-microservices/src/UserService/Queries/Handlers/GetUserByIDQueryHandler.cs:line 19
   at UserService.Controllers.UsersController.GetUserById(GetUserByIDQuery query) in /Users/ryanr/Documents/invoicer-backend-microservices/src/UserService/Controllers/UsersController.cs:line 43
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeInnerFilterAsync>g__Awaited|13_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeFilterPipelineAsync>g__Awaited|19_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Awaited|17_0(ResourceInvoker invoker, Task task, IDisposable scope)
   at Microsoft.AspNetCore.Builder.RouterMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

I have a class library that registers the Autofac types and defines the DBContext interface:

    public interface IDbContext<T> where T: class
    {
        DbSet<T> DataSet { get; set; }
    }

The Autofac module for registering Repositories and injecting the DbContext

    public class RepositoryModule : Autofac.Module
    {
        public Assembly ExecutingAssembly { get; set; }
        public string ConnectionString { get; set; }

        protected override void Load(ContainerBuilder builder)
        {
            builder.RegisterAssemblyTypes(ExecutingAssembly)
            .Where(t => t.Name.EndsWith("Repository"))
            .AsImplementedInterfaces();

            builder.RegisterAssemblyTypes(ExecutingAssembly)
                .Where(t => t.IsClosedTypeOf(typeof(IDbContext<>)))
                .AsSelf()
                .InstancePerLifetimeScope();

        }
    }

This then gets resolved and injected into my UserService(ASP.Net):

public class UserRepository : IUserRepository
    {
        private readonly UserDBContext _dbContext;
        public UserRepository(UserDBContext dbContext)
        {
            _dbContext = dbContext;
        }


        public async Task<User> FindByIdAsync(string id)
        {
            return await _dbContext.DataSet.FirstOrDefaultAsync(user => user.Id == id);

        }
}

The DbContext class

public class UserDBContext : DbContext, IDbContext<User>
    {
        public DbSet<User> DataSet { get; set; }

        public UserDBContext()
        {
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer("server=localhost,1434;user id=sa;password=password;database=UserManagement;");
        }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.Entity<User>().HasKey(m => m.Id);
            builder.Entity<User>().ToTable("User");
            base.OnModelCreating(builder);
        }
   }

The repository then gets injected into my into my QueryHandler and that is where the DBContext is used:

public class GetUserByIDQueryHandler : IQueryHandler<GetUserByIDQuery, User>
    {
        private IUserRepository _repository;
        public GetUserByIDQueryHandler(IUserRepository repository)
        {
            _repository = repository;
        }

        public async Task<User> Handle(GetUserByIDQuery query)
        {
            return await _repository.FindByIdAsync(query.Id);
        }
    }

Where QueryHandlers are resolved like:

var handlers = scope.Resolve<IEnumerable<IQueryHandler<TRequest, TResponse>>>().ToList();

EDIT: I have reason to believe this is a Dependency Injection/an Autofac problem, because this trivial test ended up working:

// GET api/users/5
        [HttpGet("{Id}", Name = "GetUserById")]
        public async Task<IActionResult> GetUserById([FromBody]GetUserByIDQuery query)
        {
            //var users = await _queryBus.Query<GetUserByIDQuery, User>(query);
            //string id = new Guid().ToString();
            //var users = await _dbContext.DataSet.FirstOrDefaultAsync(x => x.Id == id);
            using(var dbContext = new UserDBContext())
            {
                return Ok(await dbContext.DataSet.ToListAsync());
            }
        }

Any help would be appreciated! Thanks :)

2

2 Answers

2
votes

I don't think this has anything to do with dependency injection or autofac. Look at the stack trace--the top of the trace says:

at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenDbConnectionAsync(Boolean errorsExpected, CancellationToken cancellationToken)

This looks like a problem connecting to the database. Check your connection string and do a quick test to make sure you can connect to the database without the complexity overhead of autofac or query handlers.

0
votes

I solved this.

The QueryHandler was trying to resolve an async function synchronously, and it was causing problems.

For example:

var handlers = scope.Resolve<IEnumerable<IQueryHandler<TRequest, TResponse>>>().ToList();
return await handlers[0].Handle(query);