0

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

4
  • The DataLoader does not guarantee a single call. Also, if you are debugging its more likely that it does not result in a single call. Commented Mar 20, 2023 at 22:14
  • @MichaelIngmarStaib while learning DataLoader, I created POC where I was hitting datloader directly from my query class. Here, only diff is, I am using query -> service -> dataLoader. Basically, structure of my code.
    – GThree
    Commented Mar 21, 2023 at 18:52
  • As an aside, you really shouldn't be using the term "Exception" in your business/domain model seeming as that's a very important class in .NET (and most other programming languages). Isn't there a better name you can use?
    – Dai
    Commented Mar 21, 2023 at 23:31
  • @Dai You have a very good point, but this is existing structure. I am trying to introduce data Loader (while learning graphQl) here.
    – GThree
    Commented Mar 22, 2023 at 14:44

1 Answer 1

1

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;
    }
}
6
  • 1
    I think that might work, but I want my logic in service.cs. If I move this to query then most likely I will end up using DBContext in query.cs as well which I want to avoid, ATM.
    – GThree
    Commented Mar 22, 2023 at 14:43
  • I get the same error when I go to this route, which is surprising. I have updated the question with more info.
    – GThree
    Commented Mar 23, 2023 at 18:20
  • So this works with option2 (you suggested the same). My mistake in option 2 was, I kept service reference, which was messing it up even though I was hitting DataLoader directly. I wonder why service declaration caused this issue. I'll look into that. But, it is working now. Thank you.
    – GThree
    Commented Mar 23, 2023 at 18:54
  • great, I'm glad it helped! I've been working a lot on the GraphQL piece. It's a great approach I think.
    – pauliec
    Commented Mar 23, 2023 at 20:29
  • Then you will more questions from me since I am learning GraphQL :) .
    – GThree
    Commented Mar 23, 2023 at 20:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.