1

I'm trying to write a query provider that instantiates a DbContext before execution, executes the query and then immediately disposes the generated context object.

For this I have to implement IQueryProvider.Execute(....). Inside of this method i need to get the IQueryProvider from my DbContext object to have it execute the query.

The problem is that I don't know how to access the provider from the context object. As far as I can see, it is stored in ((IObjectContextAdapter)context).ObjectContext.QueryProvider but that property is internal.

I also tried getting it from a DbSet, but I don't know what type to specify. context.Set(type?).AsQueryable().Provider.Execute<TResult>(expression) or context.Set<type?>().AsQueryable().Provider.Execute<TResult>(expression)

So my question is how to get the underlying IQueryProvider from a given DbContext object.

The underlying problem that I am trying to solve

I want to execute multiple queries in different contexts against the same database. However DbContext is IDisposable you have to wrap it in using.Blocks.

IEnumerable<T1> result1;
IEnumerable<T2> result2;

using(var context1 = new MyContext())
using(var context2 = new MyContext())
{
    var result1Task = context1.Set<T1>().ToListAsync();
    var result2Task = context2.Set<T2>().ToListAsync();

    await Task.WhenAll(result1Task, result2Task).ConfigureAwait(false);

    result1 = result1Task.Result;
    result2 = result1Task.Result;
}

The alternative is to wrap it in own methods:

// could also be written as generic method GetResultAsync<T>(),
// but you should get the point :)
async Task<IEnumerable<T1>> GetResult1Async()
{
    using(var context = new MyContext())
        return await context.Set<T1>().ToListAsync().ConfigureAwait(false);
}

async Task<IEnumerable<T2>> GetResult2Async()
{
    using(var context = new MyContext())
        return await context.Set<T2>().ToListAsync().ConfigureAwait(false);
}

var result1Task = GetResult1Async();
var result2Task = GetResult2Async();

await Task.WhenAll(result1Task, result2Task).ConfigureAwait(false);

var result1 = result1Task.Result;
var result2 = result1Task.Result;

Both have a lot of boilerplate code, so I want to write a generalization for opening/disposing the context object.

I want to simplify this to something like this:

var result1Task = repository.Set<T1>().ToListAsync();
var result2Task = repository.Set<T2>().ToListAsync();

await Task.WhenAll(result1Task, result2Task).ConfigureAwait(false);

var result1 = result1Task.Result;
var result2 = result1Task.Result;

The trick is that I want repository.Set<T>() to return IQueryable<T> so i can use all Linq methods. For this purpose I am trying to creaty my own IQueryProvider that wraps the context using-Block in its Execute method.

This is my class so far:

public class RepositoryQueryProvider<TContext> : IQueryProvider
    where TContext : DbContext, new()
{
    // some irrelevant code here

    public TResult Execute<TResult>(Expression expression)
    {
        using (var context = new TContext())
        {
            var efProvider = ?; // how to get the provider from the context?
            return efProvider.Execute<TResult>(expression);
        }
    }
}
wertzui
  • 5,148
  • 3
  • 31
  • 51
  • what is wrong with just newing up a context and querying the tables straight up? – Callum Linington Jul 25 '16 at 08:20
  • DbContext is IDisposable, so it needs to be wrapped inside a using statement. However that way it is hard to usit it with Task.WhenAll. – wertzui Jul 25 '16 at 08:25
  • You just call `Task.WhenAll` just before the closing brace... put that example so we can see the actual problem you're trying to solve – Callum Linington Jul 25 '16 at 08:37
  • I edited the question to outline the underlying problem, hope that helps – wertzui Jul 25 '16 at 08:54
  • Depending on what framework this is sitting under, you can either use the DI, WebApi or MVC request cycle. So when your class asks for a `DbContext` you can provide it with a transient one. – Callum Linington Jul 25 '16 at 09:15
  • You cannot use 1 context with 2 async queries at the same time. I added my query provider class to exactly show where the problem is located. – wertzui Jul 25 '16 at 09:21
  • Noted that IDisposable doesn't mean you must to call `dispose()` or using `using`. IDisposable is just giving you a choice to dispose the resource as soon as possible but it's not a requirement. It's still be disposed afterward. – shtse8 May 14 '18 at 15:11
  • Did you eventually resolve this? I see the 1 answer is not accepted, which seems understandable as when I've tried it I don't see a GetService on a DbContext? – Dave Jan 09 '20 at 10:01
  • I Wrote my own factory "Repository" class wich has a Set() method, like DbContext. This method will create a "RepositoryQuery" which has the same methods as IQueryable and an instance of a DbContext. In The end methods like ToListAsync will automatically dispose the context at the end. However this has the limitation that you cannot reuse those Queries, as the underlying DbContext is disposed at the end. – wertzui Jan 09 '20 at 21:39

1 Answers1

0

It's fairly simple:

dbcontext.GetService<IAsyncQueryProvider>();

And I think use 'using' against DbContext is not needed

Bonelol
  • 526
  • 6
  • 13