Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IDbCommandInterceptor or similar to allow injecting custom query execution service #15066

Closed
kosinsky opened this issue Mar 18, 2019 · 8 comments · Fixed by #16113
Closed
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-enhancement
Milestone

Comments

@kosinsky
Copy link
Contributor

Sometime application needs more control on executing build SQL statement in the database to add custom retry logic, circuit breakers, CPU usage logging and throttling, injecting session-wide information to support Row-Level-Security etc. That requires injecting custom code that will get SQL Command from the EF Core (or just text + list of parameters), executing it and returning IDataReader back to the EF Core.

EF6 supported that approach by deriving from DbCommandInterceptor and overriding ReaderExecuting and ScalarExecuting.

This issue is a detailed case for #626

@ajcvickers ajcvickers self-assigned this Mar 20, 2019
@ajcvickers ajcvickers added this to the 3.0.0 milestone Mar 22, 2019
@tstone84
Copy link

"injecting session-wide information to support Row-Level-Security" is a critical feature preventing us from moving to EF Core. Currently, in EF6 we use DbCommandInterceptor and DbConnectionInterceptor to inject SQL to handle setting session context for SQL Server Row Level Security. Is there an existing way to do this in EF Core?

@ajcvickers ajcvickers changed the title Support injecting custom query execution service Implement DbCommandInterceptor or similar to allow injecting custom query execution service Jun 15, 2019
@ajcvickers ajcvickers changed the title Implement DbCommandInterceptor or similar to allow injecting custom query execution service Add IDbCommandInterceptor or similar to allow injecting custom query execution service Jun 15, 2019
ajcvickers added a commit that referenced this issue Jun 15, 2019
Fixes #15066

The general idea is the same as `IDbCommandInterceptor` in EF6.

Specifically:
* Command interceptors can be used to view, change, or suppress execution of the `DbCommand` and to modify the result before it is returned to EF.
`DbCommandInterceptor` is provided as an abstract base class.
* Example of registration:
```C#
builder.UseSqlServer(
    myConnectionString,
	b => b.CommandInterceptor(myInterceptor);
```
* Multiple interceptors can be composed using the `CompositeDbCommandInterceptor`.
* Extensions can also register interceptors in the internal service provider. If both injected and application interceptors are found, then the injected interceptors are run in the
order that they are resolved from the service provider, and then the application interceptor is run last.
@ajcvickers ajcvickers added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Jun 15, 2019
@ajcvickers
Copy link
Contributor

@kosinsky PR: #16113 Does this look like it covers what you need?

ajcvickers added a commit that referenced this issue Jun 16, 2019
Fixes #15066

The general idea is the same as `IDbCommandInterceptor` in EF6.

Specifically:
* Command interceptors can be used to view, change, or suppress execution of the `DbCommand` and to modify the result before it is returned to EF.
`DbCommandInterceptor` is provided as an abstract base class.
* Example of registration:
```C#
builder.UseSqlServer(
    myConnectionString,
	b => b.CommandInterceptor(myInterceptor);
```
* Multiple interceptors can be composed using the `CompositeDbCommandInterceptor`.
* Extensions can also register interceptors in the internal service provider. If both injected and application interceptors are found, then the injected interceptors are run in the
order that they are resolved from the service provider, and then the application interceptor is run last.
@kosinsky
Copy link
Contributor Author

@kosinsky PR: #16113 Does this look like it covers what you need?

@ajcvickers Partially. Looks like when implementing IDbCommandInterceptor we don't have a way to access original DbContext or service provider (and then get DbContext scoped service that we could use for command execution). Am I missing something?
In my use case, we attaching our custom SqlResourceComponent (it's responsible for query execution, RLS, circuit breakers etc.) to DbContext and then in IDbCommandIntercepor we have something like:

public override void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
        {
            var context = interceptionContext.DbContexts.FirstOrDefault() as IComponentContext;
            if (context != null)
            {
                if (context.Component != null)
                {
                    var result = context.Component.ExecuteContextCommand(command as SqlCommand);
                    interceptionContext.Result = result;
                    return;
                }
                    base.ReaderExecuting(command, interceptionContext);
                    return;
            }
        }

@nphmuller
Copy link

@kosinsky @ajcvickers Injecting the DbContext in the interceptor's constructor would probably work, right? Or are only Singleton interceptors supported?

ajcvickers added a commit that referenced this issue Jun 17, 2019
Fixes #15066

The general idea is the same as `IDbCommandInterceptor` in EF6.

Specifically:
* Command interceptors can be used to view, change, or suppress execution of the `DbCommand` and to modify the result before it is returned to EF.
`DbCommandInterceptor` is provided as an abstract base class.
* Example of registration:
```C#
builder.UseSqlServer(
    myConnectionString,
	b => b.CommandInterceptor(myInterceptor);
```
* Multiple interceptors can be composed using the `CompositeDbCommandInterceptor`.
* Extensions can also register interceptors in the internal service provider. If both injected and application interceptors are found, then the injected interceptors are run in the
order that they are resolved from the service provider, and then the application interceptor is run last.
ajcvickers added a commit that referenced this issue Jun 18, 2019
Fixes #15066

The general idea is the same as `IDbCommandInterceptor` in EF6.

Specifically:
* Command interceptors can be used to view, change, or suppress execution of the `DbCommand` and to modify the result before it is returned to EF.
`DbCommandInterceptor` is provided as an abstract base class.
* Example of registration:
```C#
builder.UseSqlServer(
    myConnectionString,
	b => b.CommandInterceptor(myInterceptor);
```
* Multiple interceptors can be composed using the `CompositeDbCommandInterceptor`.
* Extensions can also register interceptors in the internal service provider. If both injected and application interceptors are found, then the injected interceptors are run in the
order that they are resolved from the service provider, and then the application interceptor is run last.
ajcvickers added a commit that referenced this issue Jun 18, 2019
Fixes #15066

The general idea is the same as `IDbCommandInterceptor` in EF6.

Specifically:
* Command interceptors can be used to view, change, or suppress execution of the `DbCommand` and to modify the result before it is returned to EF.
`DbCommandInterceptor` is provided as an abstract base class.
* Example of registration:
```C#
builder.UseSqlServer(
    myConnectionString,
	b => b.CommandInterceptor(myInterceptor);
```
* Multiple interceptors can be composed using the `CompositeDbCommandInterceptor`.
* Extensions can also register interceptors in the internal service provider. If both injected and application interceptors are found, then the injected interceptors are run in the
order that they are resolved from the service provider, and then the application interceptor is run last.
@ajcvickers
Copy link
Contributor

@kosinsky See #16159 and PR #16162

@nphmuller
Copy link

@ajcvickers Great! Just curious, but is there any reason the DbContext couldn't by injected in the interceptor via its constructor? Or would that give a circular reference somehow? Or is there perhaps another benefit I'm missing?

@ajcvickers
Copy link
Contributor

@nphmuller Interceptors are currently resolved only from the internal service provider. So having one depend on DbContext will usually not work, since it's not usually registered in the internal service provider. The interceptor might be able to depend on ICurrentDbContext to achieve the same thing, but that wouldn't help when an app is using an interceptor that is not registered in D.I., which is what I expect will be the most common usage.

@nphmuller
Copy link

nphmuller commented Jun 19, 2019

@ajcvickers I didn't think about the non DI scenario, since DI seems so much like the normal route in ASP.NET Core. Thanks for the clear explanation!

I learned about ICurrentDbContext when I looked at these PRs and it seems like an easy way to access the current DbContext in extension points.

@ajcvickers ajcvickers modified the milestones: 3.0.0, 3.0.0-preview7 Jul 2, 2019
@ajcvickers ajcvickers modified the milestones: 3.0.0-preview7, 3.0.0 Nov 11, 2019
@ajcvickers ajcvickers removed their assignment Sep 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-enhancement
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants