Skip to content
This repository was archived by the owner on Feb 1, 2025. It is now read-only.

Commit da9d70c

Browse files
MaceWinduplukawski
andauthored
Backport interceptors to v2 (#271)
* Added possibility to define default interceptors for Linq2Db. (#259) * Added possibility to define default interceptors for Linq2Db. * CR fixes * changed the way of Linq2Db Interceptors registration. Made it possible to use interceptors registered for EF core also. * improved existing interceptors checking logic. Naming changes inside interceptor tests. * cr fix: removed duplicated interceptors check * cr fixes * Update Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.ContextExtensions.cs Co-authored-by: Svyatoslav Danyliv <sdanyliv@gmail.com> * Update Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.ContextExtensions.cs Co-authored-by: Svyatoslav Danyliv <sdanyliv@gmail.com> * cosmetics * Make traversing EF core interceptors configurable and disabled by default. * Update Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.cs Co-authored-by: Svyatoslav Danyliv <sdanyliv@gmail.com> * Update Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.ContextExtensions.cs Co-authored-by: Svyatoslav Danyliv <sdanyliv@gmail.com> * changed the name of method for using ef core registered interceptors to UseEfCoreRegisteredInterceptorsIfPossible. * removed UseEfCoreRegisteredInterceptorsIfPossible support and added possibility to create extension having same functionality. Added sample implementation of such extension method in tests. * updated README.md * cosmetic naming change Co-authored-by: Svyatoslav Danyliv <sdanyliv@gmail.com> (cherry picked from commit df56f7f) (cherry picked from commit 5176487) * v3 changes (cherry picked from commit 7a83d51) * remove non-existing efcore interceptors support Co-authored-by: Przemysław Łukawski <przemyslaw.luk@gmail.com>
1 parent 145a617 commit da9d70c

14 files changed

+601
-2
lines changed

README.md

+11
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,17 @@ LinqToDBForEFTools.Initialize();
3939

4040
After that you can just call DbContext and IQueryable extension methods, provided by `LINQ To DB`.
4141

42+
You can also register additional options (like interceptors) for LinqToDB during EF context registration, here is an example:
43+
44+
```cs
45+
var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
46+
optionsBuilder.UseSqlite();
47+
optionsBuilder.UseLinqToDb(builder =>
48+
{
49+
builder.AddInterceptor(new MyCommandInterceptor());
50+
});
51+
```
52+
4253
There are many extensions for CRUD Operations missing in vanilla EF ([watch our video](https://www.youtube.com/watch?v=m--oX73EGeQ)):
4354

4455
```cs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
using System.Collections.Generic;
2+
3+
using Microsoft.EntityFrameworkCore.Infrastructure;
4+
using Microsoft.Extensions.DependencyInjection;
5+
6+
namespace LinqToDB.EntityFrameworkCore.Internal
7+
{
8+
using Interceptors;
9+
10+
/// <summary>
11+
/// Model containing LinqToDB related context options
12+
/// </summary>
13+
public class LinqToDBOptionsExtension : IDbContextOptionsExtension
14+
{
15+
private string? _logFragment;
16+
17+
private IList<IInterceptor>? _interceptors;
18+
19+
/// <summary>
20+
/// List of registered LinqToDB interceptors
21+
/// </summary>
22+
public virtual IList<IInterceptor> Interceptors => _interceptors ??= new List<IInterceptor>();
23+
24+
/// <inheritdoc cref="IDbContextOptionsExtension.LogFragment"/>
25+
public string LogFragment
26+
{
27+
get
28+
{
29+
if (_logFragment == null)
30+
{
31+
string logFragment = string.Empty;
32+
33+
if (_interceptors?.Count > 0)
34+
{
35+
_logFragment = $"Interceptors count: {_interceptors.Count}";
36+
}
37+
else
38+
{
39+
_logFragment = string.Empty;
40+
}
41+
}
42+
43+
return _logFragment;
44+
}
45+
}
46+
47+
/// <summary>
48+
/// .ctor
49+
/// </summary>
50+
public LinqToDBOptionsExtension()
51+
{
52+
}
53+
54+
/// <summary>
55+
/// .ctor
56+
/// </summary>
57+
/// <param name="copyFrom"></param>
58+
protected LinqToDBOptionsExtension(LinqToDBOptionsExtension copyFrom)
59+
{
60+
_interceptors = copyFrom._interceptors;
61+
}
62+
63+
/// <inheritdoc cref="IDbContextOptionsExtension.ApplyServices(IServiceCollection)"/>
64+
public bool ApplyServices(IServiceCollection services) => false;
65+
66+
/// <summary>
67+
/// Gives the extension a chance to validate that all options in the extension are
68+
/// valid. Most extensions do not have invalid combinations and so this will be a
69+
/// no-op. If options are invalid, then an exception should be thrown.
70+
/// </summary>
71+
/// <param name="options"></param>
72+
public void Validate(IDbContextOptions options)
73+
{
74+
}
75+
76+
/// <inheritdoc cref="IDbContextOptionsExtension.GetServiceProviderHashCode()"/>
77+
public long GetServiceProviderHashCode() => 0;
78+
}
79+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
using Microsoft.EntityFrameworkCore;
2+
3+
namespace LinqToDB.EntityFrameworkCore
4+
{
5+
using Internal;
6+
using Interceptors;
7+
8+
/// <summary>
9+
/// LinqToDB context options builder
10+
/// </summary>
11+
public class LinqToDBContextOptionsBuilder
12+
{
13+
private readonly LinqToDBOptionsExtension _extension;
14+
15+
/// <summary>
16+
/// Db context options
17+
/// </summary>
18+
public DbContextOptions DbContextOptions { get; private set; }
19+
20+
/// <summary>
21+
/// .ctor
22+
/// </summary>
23+
/// <param name="optionsBuilder"></param>
24+
public LinqToDBContextOptionsBuilder(DbContextOptionsBuilder optionsBuilder)
25+
{
26+
_extension = optionsBuilder.Options.FindExtension<LinqToDBOptionsExtension>();
27+
DbContextOptions = optionsBuilder.Options;
28+
}
29+
30+
/// <summary>
31+
/// Registers LinqToDb interceptor
32+
/// </summary>
33+
/// <param name="interceptor">The interceptor instance to register</param>
34+
/// <returns></returns>
35+
public LinqToDBContextOptionsBuilder AddInterceptor(IInterceptor interceptor)
36+
{
37+
_extension.Interceptors.Add(interceptor);
38+
return this;
39+
}
40+
}
41+
}

Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.ContextExtensions.cs

+41-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Threading;
44
using System.Threading.Tasks;
55
using Microsoft.EntityFrameworkCore;
6+
using Microsoft.EntityFrameworkCore.Infrastructure;
67

78
using JetBrains.Annotations;
89

@@ -11,6 +12,8 @@ namespace LinqToDB.EntityFrameworkCore
1112
extern alias interactive_async;
1213
using Data;
1314
using Linq;
15+
using Internal;
16+
using Interceptors;
1417

1518
public static partial class LinqToDBForEFTools
1619
{
@@ -188,7 +191,44 @@ public static ITable<T> GetTable<T>(this DbContext context)
188191

189192
return context.CreateLinqToDbContext().GetTable<T>();
190193
}
191-
194+
195+
#endregion
196+
197+
#region Interceptors
198+
199+
/// <summary>
200+
/// Returns list of registered Linq2Db interceptors from EF Context
201+
/// </summary>
202+
/// <returns>Db context object</returns>
203+
public static IList<IInterceptor>? GetLinq2DbInterceptors(this DbContext context)
204+
205+
{
206+
if (context == null) throw new ArgumentNullException(nameof(context));
207+
208+
var contextOptions = ((IInfrastructure<IServiceProvider>)context.Database)?
209+
.Instance?.GetService(typeof(IDbContextOptions)) as IDbContextOptions;
210+
211+
return contextOptions?.GetLinq2DbInterceptors();
212+
}
213+
214+
/// <summary>
215+
/// Returns list of registered Linq2Db interceptors from EF Context options
216+
/// </summary>
217+
/// <returns>Db context options</returns>
218+
public static IList<IInterceptor> GetLinq2DbInterceptors(this IDbContextOptions contextOptions)
219+
{
220+
if (contextOptions == null) throw new ArgumentNullException(nameof(contextOptions));
221+
222+
var linq2DbExtension = contextOptions?.FindExtension<LinqToDBOptionsExtension>();
223+
List<IInterceptor> interceptors = new List<IInterceptor>();
224+
if (linq2DbExtension?.Interceptors != null)
225+
{
226+
interceptors.AddRange(linq2DbExtension.Interceptors);
227+
}
228+
229+
return interceptors;
230+
}
231+
192232
#endregion
193233
}
194234
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System;
2+
using Microsoft.EntityFrameworkCore;
3+
using Microsoft.EntityFrameworkCore.Infrastructure;
4+
5+
namespace LinqToDB.EntityFrameworkCore
6+
{
7+
using Internal;
8+
9+
public static partial class LinqToDBForEFTools
10+
{
11+
/// <summary>
12+
/// Registers custom options related to LinqToDB provider
13+
/// </summary>
14+
/// <param name="optionsBuilder"></param>
15+
/// <param name="linq2DbOptionsAction">Custom options action</param>
16+
/// <returns></returns>
17+
public static DbContextOptionsBuilder UseLinqToDb(
18+
this DbContextOptionsBuilder optionsBuilder,
19+
Action<LinqToDBContextOptionsBuilder>? linq2DbOptionsAction = null)
20+
{
21+
((IDbContextOptionsBuilderInfrastructure)optionsBuilder)
22+
.AddOrUpdateExtension(GetOrCreateExtension(optionsBuilder));
23+
24+
linq2DbOptionsAction?.Invoke(new LinqToDBContextOptionsBuilder(optionsBuilder));
25+
26+
return optionsBuilder;
27+
}
28+
29+
private static LinqToDBOptionsExtension GetOrCreateExtension(DbContextOptionsBuilder options)
30+
=> options.Options.FindExtension<LinqToDBOptionsExtension>()
31+
?? new LinqToDBOptionsExtension();
32+
}
33+
}

Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.cs

+30
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,8 @@ public static DataConnection CreateLinqToDbConnection(this DbContext context,
288288
if (mappingSchema != null)
289289
dc.AddMappingSchema(mappingSchema);
290290

291+
AddInterceptorsToDataContext(context, dc);
292+
291293
return dc;
292294
}
293295

@@ -372,6 +374,8 @@ public static IDataContext CreateLinqToDbContext(this DbContext context,
372374
EnableTracing(dc, logger);
373375
}
374376

377+
AddInterceptorsToDataContext(context, dc);
378+
375379
return dc;
376380
}
377381

@@ -497,6 +501,8 @@ public static DataConnection CreateLinqToDbConnection(this DbContextOptions opti
497501
dc.AddMappingSchema(mappingSchema);
498502
}
499503

504+
AddInterceptorsToDataContext(options, dc);
505+
500506
return dc;
501507
}
502508

@@ -513,6 +519,7 @@ public static IQueryable<T> ToLinqToDB<T>(this IQueryable<T> query, IDataContext
513519
if (context == null)
514520
throw new LinqToDBForEFToolsException("Can not evaluate current context from query");
515521

522+
AddInterceptorsToDataContext(context, dc);
516523
return new LinqToDBForEFQueryProvider<T>(dc, query.Expression);
517524
}
518525

@@ -558,5 +565,28 @@ public static bool EnableChangeTracker
558565
set => Implementation.EnableChangeTracker = value;
559566
}
560567

568+
private static void AddInterceptorsToDataContext(DbContext efContext, IDataContext dc)
569+
{
570+
var contextOptions = ((IInfrastructure<IServiceProvider>)efContext.Database)?
571+
.Instance?.GetService(typeof(IDbContextOptions)) as IDbContextOptions;
572+
573+
AddInterceptorsToDataContext(contextOptions, dc);
574+
}
575+
576+
private static void AddInterceptorsToDataContext(IDbContextOptions? contextOptions,
577+
IDataContext dc)
578+
{
579+
var registeredInterceptors = contextOptions?.GetLinq2DbInterceptors();
580+
581+
if (registeredInterceptors != null
582+
583+
&& dc != null )
584+
{
585+
foreach (var interceptor in registeredInterceptors)
586+
{
587+
dc.AddInterceptor(interceptor);
588+
}
589+
}
590+
}
561591
}
562592
}

Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFToolsImplDefault.cs

-1
Original file line numberDiff line numberDiff line change
@@ -1180,6 +1180,5 @@ public virtual void LogConnectionTrace(TraceInfo info, ILogger logger)
11801180
/// Entities will be attached only if AsNoTracking() is not used in query and DbContext is configured to track entities.
11811181
/// </summary>
11821182
public virtual bool EnableChangeTracker { get; set; } = true;
1183-
11841183
}
11851184
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
using System.Data;
2+
using System.Data.Common;
3+
using System.Threading;
4+
using System.Threading.Tasks;
5+
using LinqToDB.Common;
6+
using LinqToDB.Interceptors;
7+
8+
namespace LinqToDB.EntityFrameworkCore.BaseTests.Interceptors
9+
{
10+
public class TestCommandInterceptor : TestInterceptor, ICommandInterceptor
11+
{
12+
public void AfterExecuteReader(CommandEventData eventData, DbCommand command, CommandBehavior commandBehavior, DbDataReader dataReader)
13+
{
14+
HasInterceptorBeenInvoked = true;
15+
}
16+
17+
public void BeforeReaderDispose(CommandEventData eventData, DbCommand? command, DbDataReader dataReader)
18+
{
19+
HasInterceptorBeenInvoked = true;
20+
}
21+
22+
public Task BeforeReaderDisposeAsync(CommandEventData eventData, DbCommand? command, DbDataReader dataReader)
23+
{
24+
HasInterceptorBeenInvoked = true;
25+
return Task.CompletedTask;
26+
}
27+
28+
public DbCommand CommandInitialized(CommandEventData eventData, DbCommand command)
29+
{
30+
HasInterceptorBeenInvoked = true;
31+
return command;
32+
}
33+
34+
public Option<int> ExecuteNonQuery(CommandEventData eventData, DbCommand command, Option<int> result)
35+
{
36+
HasInterceptorBeenInvoked = true;
37+
return result;
38+
}
39+
40+
public Task<Option<int>> ExecuteNonQueryAsync(CommandEventData eventData, DbCommand command, Option<int> result, CancellationToken cancellationToken)
41+
{
42+
HasInterceptorBeenInvoked = true;
43+
return Task.FromResult(result);
44+
}
45+
46+
public Option<DbDataReader> ExecuteReader(CommandEventData eventData, DbCommand command, CommandBehavior commandBehavior, Option<DbDataReader> result)
47+
{
48+
HasInterceptorBeenInvoked = true;
49+
return result;
50+
}
51+
52+
public Task<Option<DbDataReader>> ExecuteReaderAsync(CommandEventData eventData, DbCommand command, CommandBehavior commandBehavior, Option<DbDataReader> result, CancellationToken cancellationToken)
53+
{
54+
HasInterceptorBeenInvoked = true;
55+
return Task.FromResult(result);
56+
}
57+
58+
public Option<object?> ExecuteScalar(CommandEventData eventData, DbCommand command, Option<object?> result)
59+
{
60+
HasInterceptorBeenInvoked = true;
61+
return result;
62+
}
63+
64+
public Task<Option<object?>> ExecuteScalarAsync(CommandEventData eventData, DbCommand command, Option<object?> result, CancellationToken cancellationToken)
65+
{
66+
HasInterceptorBeenInvoked = true;
67+
return Task.FromResult(result);
68+
}
69+
}
70+
}

0 commit comments

Comments
 (0)