0

In EF Core, I'd like to intercept the expression right before it's being executed. I thought of the interface IAsyncQueryProvider and try to replace the default with my own implementation. The IAsyncQueryProvider.CreateQuery is invoked OK (right at the time creating the query) but the IAsyncQueryProvider.ExecuteAsync (together with other overloads of Execute) is not invoked at all.

I thought that this should be easily achieved with EF Core but looks like it is either still limited or requires some hidden knowledge.

Here's how I hook-up my own implementation of IAsyncQueryProvider:

var efServices = new ServiceCollection();
efServices.AddEntityFrameworkSqlServer();
//find the default provider to remove it
var defaultProviderService = efServices.FirstOrDefault(e => e.ServiceType == typeof(IAsyncQueryProvider));            
efServices.Remove(defaultProviderService);

efServices.AddScoped(defaultProviderService.ServiceType, 
                     sp => {                
                         var defaultProvider = ActivatorUtilities.CreateInstance(sp, defaultProviderService.ImplementationType) as IAsyncQueryProvider;
            return new CustomQueryProvider(defaultProvider);
                     });

var efSp = efServices.BuildServiceProvider();

services.AddDbContext<MyDbContext>((sp,builder) => {
            builder.UseInternalServiceProvider(efSp);                
        });

Here's the custom implemenation for IAsyncQueryProvider:

public class CustomQueryProvider : IAsyncQueryProvider
{
    private readonly IAsyncQueryProvider _proxy;
    public CustomQueryProvider(IAsyncQueryProvider proxy)
    {
        _proxy = proxy;
    }

    public IQueryable CreateQuery(Expression expression)
    {
        return _proxy.CreateQuery(expression);
    }

    public IQueryable<TElement> CreateQuery<TElement>(Expression expression)
    {
        //code can be invoked here fine
        return _proxy.CreateQuery<TElement>(expression);
    }

    public object Execute(Expression expression)
    {
        //never invoked here
        return _proxy.Execute(expression);
    }

    public TResult Execute<TResult>(Expression expression)
    {
        //never invoked here
        return _proxy.Execute<TResult>(expression);
    }

    public TResult ExecuteAsync<TResult>(Expression expression, CancellationToken cancellationToken = default)
    {
        //never invoked here
        return _proxy.ExecuteAsync<TResult>(expression, cancellationToken);
    }
}

It could be by design or requires some condition for those callbacks to be invoked. So I don't hope that there is any fix here. But I do hope if there is some service type other than IAsyncQueryProvider for me to implement (and replace the corresponding default) instead so that I can intercept the execution right at the time before calling the evaluating methods (ToList, ToListAsync, First, FirstAsync, ...)?

Hopeless
  • 4,397
  • 5
  • 37
  • 64
  • `intercept the expression right before it's being executed`, Do you want to change the expression while it is being compiled? Or do you want to know every time it is executed? – Jeremy Lakeman Jun 10 '22 at 07:11
  • @JeremyLakeman there are many scenarios for what I want, e.g: modify the expression before it's being executed (by using an `ExpressionVisitor`), get the stacktrace info at that time (which can provide the correct info whereas getting the stacktrace later (such as in a `DbCommandInterceptor` callback) seems unable to have full info) ... – Hopeless Jun 10 '22 at 07:13
  • 1
    EF Core caches compiled queries, including the sql and the function to materialise the results. What I would recommend, would depend on what you are trying to do. (eg https://stackoverflow.com/a/62708275/4139809 / https://learn.microsoft.com/en-us/ef/core/querying/user-defined-function-mapping) – Jeremy Lakeman Jun 10 '22 at 07:26
  • For expressions interception in EF Core there is another technique. Check LINQKit implementation - [this is starting point](https://github.com/scottksmith95/LINQKit/blob/master/src/LinqKit.Microsoft.EntityFrameworkCore/ExpandableDbContextOptionsBuilderExtensions.cs#L19) – Svyatoslav Danyliv Jun 10 '22 at 07:44
  • @SvyatoslavDanyliv please quickly let me know about the new technique in EF Core to intercept expressions. Please note that I want a global interception (not per-expression in which you have to explicitly append some extension method to the `IQueryable`). – Hopeless Jun 10 '22 at 07:51
  • @SvyatoslavDanyliv thanks for mentioning about compiled query but I don't think all the methods not invoked (mentioned in my question) due to something related to that. I mean even when I run the test app (a simple one), the callbacks are never invoked (at least the first time the query has not been cached yet). – Hopeless Jun 10 '22 at 07:53
  • You have made wrong conclusions from what I have proposed. This is starting point for `WithExpressionExpanding` extension method for configuring `DbContextOptions`. If you configure options with this extension, LINQKit automatically intercepts ALL queries which has these options. – Svyatoslav Danyliv Jun 10 '22 at 07:56
  • And this technique not only for Compiled Query. – Svyatoslav Danyliv Jun 10 '22 at 07:57
  • @SvyatoslavDanyliv thanks, I just asked you to confirm (because you might misunderstand what I really need). I heard about LINQKit a long time ago but never tried it yet. It's well-known for helping modify the expression tree easily but I've never thought that it could ***globally intercept*** the expressions (of course configuring is just a one-time work). – Hopeless Jun 10 '22 at 08:17
  • When you say globally, what do you mean? Is there some extra rule you want to apply, maybe Global Query Filters is a good fit. Is there some known function or expression you want to replace with an expression or raw sql? Are you just trying to log something? Or something else? Why do you need a global interception? What kind of changes do you want to make to the query? Can you give us at least one concrete example? – Jeremy Lakeman Jun 14 '22 at 01:19
  • @JeremyLakeman what I want was already mentioned in my first comment replied to your question (see the first comment above in this thread). It's like modifying an Expression (we can apply many kinds of modifying here, so there is no specific one for you to work it around). Normally you need to use the extension methods on `IQueryable` to apply the modifying. But for global (or implicit) applying, you don't have to do that, you just need a hooking point to do that (yes, it's exactly like a global query filter). For EF Core I think it's possible but not sure exactly how. – Hopeless Jun 14 '22 at 02:14

0 Answers0