Search code examples
c#dataloaderhotchocolate

Why are there multiple DB calls with DataLoader?


Per my understanding, when I use DataLoader in GraphQL, it should hit DB once only with supplied key(s). This is regardless of the number of parallel calls.

What am I doing wrong here?

My scenarios:

  1. If I make 4 parallel calls with one ID then it is calling DB just once, so this is
    GOOD. Here is my query:
query {
 getExpA: exceptions(stockNumber: 2002250416) {
    location
  },
  getExpB: exceptions(stockNumber: 2002250416) {
    vin
  },
    getExpC: exceptions(stockNumber: 2002250416) {
   createdBy
  },
  getExpD: exceptions(stockNumber: 2002250416) {
    updatedBy
  },
}
  1. If I change ID in 2 out of these 4 calls, then I am seeing 2 calls, which is NOT right. Here is my query:
query {
 getExpA: exceptions(stockNumber: 2002250416) {
    location
  },
  getExpB: exceptions(stockNumber: 2002250416) {
    vin
  },
    getExpC: exceptions(stockNumber: 1112250416) {
   createdBy
  },
  getExpD: exceptions(stockNumber: 1112250416) {
    updatedBy
  },
}

Query.cs

public class ExceptionsQuery
{
    [Authorize(policy: "ProjectXAccess")]
    public async Task<Exception> GetExceptionsAsync(
        long stockNumber,
        [Service(ServiceKind.Synchronized)] IExceptionService exceptionService
    )
    {
        var result = await exceptionService.GetExceptionsByStockNumberV2(
            stockNumber,
            CancellationToken.None
        );
        return result;
    }
}

Service.cs


namespace Services
{
    public interface IExceptionService
    {
        Task<Database.Entities.Sql.Exception> GetExceptionsByStockNumberV2(
            long stockNumber,
            CancellationToken cancellationToken);
    }

    public class ExceptionService: ServiceBase, IExceptionService
    {
        private readonly ExceptionDataLoader _exceptionDataLoader;
       
        public ExceptionService(
            ExceptionDataLoader exceptionDataLoader
            )
        {
            _exceptionDataLoader = exceptionDataLoader;
        }

        #region DATA LOADER

        public Task<Database.Entities.Sql.Exception> GetExceptionsByStockNumberV2(
            long stockNumber,
            CancellationToken cancellationToken)
        {
         var result = _exceptionDataLoader.LoadAsync(stockNumber, cancellationToken);
         return result;
        }

        #endregion DATA LOADER
    }
}

DataLoader.cs

namespace DataLoaders;

public class ExceptionDataLoader : BatchDataLoader<long, Exception>
{
    private readonly IDbContextFactory<AppDbContext> _dbContext;
    
    public ExceptionDataLoader(
        IDbContextFactory<AppDbContext> dbContext,
        IBatchScheduler batchScheduler, 
        DataLoaderOptions options = null) 
        : base(batchScheduler, options)
    {
        _dbContext =
            dbContext ?? throw new ArgumentNullException(nameof(dbContext));
    }

    protected override async Task<IReadOnlyDictionary<long, Exception>> LoadBatchAsync(IReadOnlyList<long> keys, CancellationToken cancellationToken)
    {
        await using var dbContext = await _dbContext.CreateDbContextAsync(cancellationToken);

        var result = await dbContext.Exceptions
            .Where(s => keys.Contains(s.StockNumber))
            .ToDictionaryAsync(t => t.StockNumber, cancellationToken);
            
        return result;
    }
}

Option-2: This problem remains same even if I hit dataLoader directly from query. Below is the query, dataloader.cs remains same as above.

Query.cs

public class ExceptionsQuery
{
    public async Task<Exception> GetExceptionsAsync(
        long stockNumber,
        [Service(ServiceKind.Synchronized)] IExceptionService exceptionService,
         ExceptionBatchDataLoader exceptionBatchDataLoader
    )
    {
        var result = await exceptionBatchDataLoader.LoadAsync(
            stockNumber,
            CancellationToken.None
        );
        return result;
    }
}


Solution

  • I think it's because the 'ExceptionsQuery' isn't utilizing the DataLoader rather it directly calls the 'IExceptionService' In order to make use of DataLoader for batching and caching, you should change the query to use 'ExceptionDataLoader'

    public class ExceptionsQuery
    {
        [Authorize(policy: "ProjectXAccess")]
        public async Task<Exception> GetExceptionsAsync(
            long stockNumber,
            ExceptionDataLoader exceptionDataLoader,
            CancellationToken cancellationToken
        )
        {
            var result = await exceptionDataLoader.LoadAsync(stockNumber, cancellationToken);
            return result;
        }
    }