How to resolve specific DbContext based on query argument in GraphQL?

Error processing SSI file

Answers

  1. Odin

    • 2017/9/25

    After doing some more research, I come to the conclusion that resolving DbContext solely via AddDbContext / service factory is risky and pretty much a bad idea as this would require the DbContext to be registered as transient service. This brings side effects, some of them are mentioned here, see comments, one of the comments links to https://github.com/aspnet/DependencyInjection/issues/456 where the transient issue is also discussed.

    DbContext needs to be transient, because a GraphQL query can contain multiple fields and therefore multiple licensee IDs, for example

    {
      addressTypes(licenseeId: "02050fd1-b312-4571-baaf-4ee40a54eae5") {
        id,
        name,
        timestamp
      },
      banks(licenseeId: "77047070-3CBE-4E58-9805-1EA8DA621C74") {
        id,
        accountNumber
      }
    }
    

    So in the same HttpRequest request DbContext needs to be resolved differently for field addressTypes and banks, therefore it must be transient. But this would also mean that other code parts need to know that DbContext is now transient. Regarding the initial question and the repository pattern - now each repository instance would get a new DbContext instance injected which is (without having tested this) a bad idea.

    Anyway, one of the options I found after digging in the GraphQL sources would be to retrieve the current query from HttpContext, parse it with a IDocumentBuilder, validate it with a IDocumentValidator and finally build the field arguments. All this is implemented by the GraphQL package in a IDocumentExecutor and yeah, it is super complex. I would not recommend to do it this way as you would need to copy most parts and customize it.

    Another option is using FieldMiddleware to store the current licensee ID into a provider instance. The provider is then used in AddDbContext to build the connection string and return a transient DbContext instance. Here are some code snippets:

    Provider class to remember LicenseeId

    public class LicenseeIdProvider
    {
        public Guid LicenseeId { get; set; }
    }
    
    services.AddScoped<LicenseeIdProvider>();
    

    Schema with FieldMiddleware to remember licenseeId in provider instance

    public class DefaultSchema : Schema
    {
        public DefaultSchema(IServiceProvider serviceProvider)
            : base(serviceProvider)
        {
            ...
    
            FieldMiddleware.Use(next =>
            {
                return resolveFieldContext =>
                {
                    if (resolveFieldContext.HasArgument("LicenseeId"))
                    {
                        var licenseeId = resolveFieldContext.GetArgument<Guid>("LicenseeId");
                        var licenseeIdProvider= resolveFieldContext.RequestServices.GetService<LicenseeIdProvider>();
    
                        licenseeIdProvider.LicenseeId = licenseeId;
                    }
    
                    var result = next(resolveFieldContext);
    
                    return result;
                };
            });
        }
    }
    

    DbContext registration with service factory to create instance based on licenseeId

    services.AddDbContext<MyDbContext>((sp, dbContextOptionsBuilder) =>
    {
        var licenseeIdProvider = sp.GetService<LicenseeIdProvider>();
        var licenseeId = licenseeIdProvider.LicenseeId;
    
        var connectionString = $"...;Catalog=MyDatabase_{licenseeId}";
    
        dbContextOptionsBuilder.UseSqlServer(connectionString, ...);
    }, ServiceLifetime.Transient);
    
  2. Ferrara

    • 2018/11/3

    I want to use GraphQL and Entity Framework Core to query multiple databases. Each database is linked to a licensee therefore all queries 

  3. Samson

    • 2017/11/19

    Query class. public class MainQuery : ObjectGraphType { public MainQuery () { objectGraph.FieldAsync<ListGraphType<MyModel>> ("items", arguments: new QueryArguments ( new QueryArgument<NonNullGraphType<GuidGraphType>> { Name = "licenseeId" } ), resolve: async context => { var licenseeId = resolveFieldContext.GetArgument<Guid> ("licenseeId"); // *1, create dbContext based on licenseeId manually via factory var dbContext = ; var repository = resolveFieldContext.ResolveServices.

  4. Thomas

    • 2020/3/27

    My resolve method also needs to include code to grab the value of any arguments passed to the query. So, at the top of my resolve parameter for 

  5. Franklin

    • 2015/10/12

    I propose the following approach (assume that licensed is int). Note that caching DbContextOptions is important, because EF Core caches LINQ queries based on this object.

    public interface ILicenseOptionFactory
    {
        public DbContextOptions GetOptions(int licenseId);
    }
    
    public class LicenseOptionFactory : ILicenseOptionFactory
    {
        private ConcurrentDictionary<int, DbContextOptions> _options = new ConcurrentDictionary<int, DbContextOptions>();
    
        public DbContextOptions GetOptions(int licenseId)
        {
            var options = _options.GetOrAdd(licenseId, lid =>
            {
                // any other way how to retrieve connections string based on licenseId
                string cs;
                switch (lid)
                {
                    case 0:
                        cs = "connectionString0";
                        break;
                    case 1:
                        cs = "connectionString1";
                        break;
                    default:
                        throw new Exception($"Invalid licenseId: {lid}");
                }
    
                return new DbContextOptionsBuilder().UseSqlServer(cs).Options;
            });
    
            return options;
        }
    }
    
    public interface ILicenseConnectionFactory<TContext> : IDisposable, IAsyncDisposable
        where TContext : DbContext
    {
        TContext GetContext(int licenseId);
    }
    
    public class LicenseConnectionFactory<TContext> : ILicenseConnectionFactory<TContext> where TContext : DbContext
    {
        private readonly ILicenseOptionFactory _optionFactory;
        private static Dictionary<int, TContext> _contexts;
    
        public LicenseConnectionFactory(ILicenseOptionFactory optionFactory)
        {
            _optionFactory = optionFactory;
        }
    
        public TContext GetContext(int licenseId)
        {
            _contexts ??= new Dictionary<int, TContext>();
            if (_contexts.TryGetValue(licenseId, out var ctx))
                return ctx;
    
            var options = _optionFactory.GetOptions(licenseId);
            ctx = (TContext)Activator.CreateInstance(typeof(TContext), options);
            _contexts.Add(licenseId, ctx);
            return ctx;
        }
    
        public void Dispose()
        {
            if (_contexts == null)
                return;
    
            foreach (var dbContext in _contexts.Values)
            {
                dbContext.Dispose();   
            }
    
            _contexts = null;
        }
    
        public async ValueTask DisposeAsync()
        {
            if (_contexts == null)
                return;
    
            foreach (var dbContext in _contexts.Values)
            {
                await dbContext.DisposeAsync();   
            }
    
            _contexts = null;
        }
    }
    

    Registration sample, note that Singleton and Scoped is required for these services:

    var serviceCollection = new ServiceCollection();
    
    serviceCollection.AddSingleton<ILicenseOptionFactory, LicenseOptionFactory>();
    serviceCollection
        .AddScoped<ILicenseConnectionFactory<MyDbContext>, LicenseConnectionFactory<MyDbContext>>();
    

    Sample of repository (but better remove this abstraction at all)

    public class MyRepository
    {
        private readonly ILicenseConnectionFactory<MyDbContext> _factory;
        private MyDbContext _dbContext;
    
        public MyDbContext DbContext
        {
            get => _dbContext ?? throw new Exception("Repository is not initialized.");
        }
    
        public MyRepository(ILicenseConnectionFactory<MyDbContext> factory)
        {
            _factory = factory;
        }
    
        public void SetLicenseId(int licnseId)
        {
            _dbContext = _factory.GetContext(licnseId);
        }
    }
    

    And final usage. I don't know what is resolveFieldContext if it can be resolved by DI - you can simplify repository initialisation without using SetLicenseId.

    public class MainQuery : ObjectGraphType
    {
        public MainQuery()
        {
            objectGraph.FieldAsync<ListGraphType<MyModel>>("items",
                arguments: new QueryArguments(
                    new QueryArgument<NonNullGraphType<GuidGraphType>> { Name = "licenseeId" }
                ),
                resolve: async context => {
                    var licenseeId = resolveFieldContext.GetArgument<Guid>("licenseeId");
    
                    // *1, create dbContext based on licenseeId manually via factory
                    var repository = resolveFieldContext.ResolveServices.GetRequiredService<CucumberRepository>();
    
                    // *2, assign context manually
                    repository.SetLicenseId(licenseeId);
    
                    return await repository.GetAllAsync();
                });
        }
    }
    
  6. Gideon

    • 2020/2/10

    Step 1 − Download and Install Required Dependencies for the Project. Create a folder named resolver-app. Change your directory to resolver-app from the terminal. Later, follow steps 3 to 5 in the Environment Setup chapter.

  7. Luca

    • 2021/5/17

    Coding · public class AuthorSchema:Schema · { · public AuthorSchema(IDependencyResolver resolver):base(resolver) · { · Query = resolver.Resolve< 

  8. Malakai

    • 2020/8/4

    type Query {. rollThreeDice: [Int] } Instead of hardcoding “three”, we might want a more general function that rolls numDice dice, each of which have numSides sides. We can add arguments to the GraphQL schema language like this: type Query {. rollDice(numDice: Int!, numSides: Int): [Int]

  9. Fisher

    • 2017/4/5

    I wish we would provide GraphQL the EF DbContext and it would solve allows the GraphQL to change the result of our queries based on criteria we provide.

  10. Donald

    • 2021/3/30

    const resolvers ={ Query:{ user(parent,args,ctx,info){ console.log('hello') return Users.find((user)=> user. id === args. id) } } } Note: Resolver function names should be match with the Query field names in the schema. Every resolver function in GraphQL has four arguments.

  11. Edison

    • 2019/11/3

    "At its simplest, GraphQL is about asking for specific fields on objects" i.e. dot net string type will be resolved to StringGraphType .

  12. Hassan

    • 2016/9/8

    You could also pull my GraphQL.DI package from PR #1376, which lets you create MVC-style "controllers" for each of your graphs, inferring arguments and so on from the names of your functions. It has very complex multithreading support and will automatically create a scope for parallel async resolvers that require services, or can use attributes

  13. Jacob

    • 2017/5/12

    Let looking at the article for find out how can I resolve it. my POC project that tries to combine Entity Framework Core and GraphQL.

  14. Raul

    • 2015/1/25

    GraphQL queries look the same for both single items or lists of items, however we know which one to expect based on what is indicated in the schema. Arguments # If the only thing we could do was traverse objects and their fields, GraphQL would already be a very useful language for data fetching.

  15. Corbin

    • 2018/3/15

    NET CORE PROJECT ARCHITECTURE 2.4 SCAFFOLDING SQL SERVER DBCONTEXT 2.5 MIDDLEWARE 3.8 ACCESSING QUERY 3.9 QUERY ARGUMENT 3.10 ERROR HANDLING IN RESOLVER 

  16. Barbieri

    • 2015/6/21

    In GraphQL, we can achieve this with arguments. Arguments. GraphQL queries allow us to pass in arguments into query fields and nested query objects. You can pass arguments to every field and every nested object in your query to further deepen your request and make multiple fetches.

  17. Sergio

    • 2016/11/5

    NET CORE 2.4 SCAFFOLDING DBCONTEXT SQL SERVER 2.5 SCAFFOLDING DBCONTEXT STRING 3.3 MEMBUAT REPOSITORY 3.4 MEMBUAT GRAPH TYPE 3.5 MEMBUAT QUERY.

  18. Hall

    • 2016/9/6

    Hi, I started using graphiQL + graphql-dotnet with mysql database and EF. GraphType> resolveType) : base(resolveType) { Query 

  19. Alistair

    • 2021/8/13

    If null then the DbContext will be resolved from the container. Resolve Filters. A delegate that resolves the Filters. namespace GraphQL.EntityFramework { 

Comments are closed.

More Posts