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

Commit 5dafa52

Browse files
MaceWinduplukawski
andauthored
Backport interceptors to v3 (#270)
* 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 <[email protected]> * Update Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.ContextExtensions.cs Co-authored-by: Svyatoslav Danyliv <[email protected]> * cosmetics * Make traversing EF core interceptors configurable and disabled by default. * Update Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.cs Co-authored-by: Svyatoslav Danyliv <[email protected]> * Update Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.ContextExtensions.cs Co-authored-by: Svyatoslav Danyliv <[email protected]> * 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 <[email protected]> (cherry picked from commit df56f7f) * v3 changes Co-authored-by: Przemysław Łukawski <[email protected]>
1 parent 6576004 commit 5dafa52

15 files changed

+824
-2
lines changed

README.md

Lines changed: 11 additions & 0 deletions
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
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
3+
4+
using Microsoft.EntityFrameworkCore.Infrastructure;
5+
using Microsoft.Extensions.DependencyInjection;
6+
7+
namespace LinqToDB.EntityFrameworkCore.Internal
8+
{
9+
using Interceptors;
10+
11+
/// <summary>
12+
/// Model containing LinqToDB related context options
13+
/// </summary>
14+
public class LinqToDBOptionsExtension : IDbContextOptionsExtension
15+
{
16+
private DbContextOptionsExtensionInfo? _info;
17+
private IList<IInterceptor>? _interceptors;
18+
19+
/// <summary>
20+
/// Context options extension info object
21+
/// </summary>
22+
public DbContextOptionsExtensionInfo Info
23+
=> _info ??= new LinqToDBExtensionInfo(this);
24+
25+
/// <summary>
26+
/// List of registered LinqToDB interceptors
27+
/// </summary>
28+
public virtual IList<IInterceptor> Interceptors
29+
=> _interceptors ??= new List<IInterceptor>();
30+
31+
/// <summary>
32+
/// .ctor
33+
/// </summary>
34+
public LinqToDBOptionsExtension()
35+
{
36+
}
37+
38+
/// <summary>
39+
/// .ctor
40+
/// </summary>
41+
/// <param name="copyFrom"></param>
42+
protected LinqToDBOptionsExtension(LinqToDBOptionsExtension copyFrom)
43+
{
44+
_interceptors = copyFrom._interceptors;
45+
}
46+
47+
/// Adds the services required to make the selected options work. This is used when
48+
/// there is no external System.IServiceProvider and EF is maintaining its own service
49+
/// provider internally. This allows database providers (and other extensions) to
50+
/// register their required services when EF is creating an service provider.
51+
/// <param name="services">The collection to add services to</param>
52+
public void ApplyServices(IServiceCollection services)
53+
{
54+
;
55+
}
56+
57+
/// <summary>
58+
/// Gives the extension a chance to validate that all options in the extension are
59+
/// valid. Most extensions do not have invalid combinations and so this will be a
60+
/// no-op. If options are invalid, then an exception should be thrown.
61+
/// </summary>
62+
/// <param name="options"></param>
63+
public void Validate(IDbContextOptions options)
64+
{
65+
;
66+
}
67+
68+
private sealed class LinqToDBExtensionInfo : DbContextOptionsExtensionInfo
69+
{
70+
private string? _logFragment;
71+
72+
public LinqToDBExtensionInfo(IDbContextOptionsExtension extension)
73+
: base(extension)
74+
{
75+
}
76+
77+
private new LinqToDBOptionsExtension Extension
78+
=> (LinqToDBOptionsExtension)base.Extension;
79+
80+
public override bool IsDatabaseProvider
81+
=> false;
82+
83+
public override string LogFragment
84+
{
85+
get
86+
{
87+
if (_logFragment == null)
88+
{
89+
string logFragment = string.Empty;
90+
91+
if (Extension.Interceptors.Any())
92+
{
93+
logFragment += $"Interceptors count: {Extension.Interceptors.Count}";
94+
}
95+
96+
_logFragment = logFragment;
97+
}
98+
99+
return _logFragment;
100+
}
101+
}
102+
103+
public override long GetServiceProviderHashCode()
104+
{
105+
return 0;
106+
}
107+
108+
public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
109+
=> debugInfo["Linq2Db"] = "1";
110+
}
111+
}
112+
}
Lines changed: 41 additions & 0 deletions
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

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@
33
using System.Threading;
44
using System.Threading.Tasks;
55
using Microsoft.EntityFrameworkCore;
6+
using Microsoft.EntityFrameworkCore.Infrastructure;
67

78
using JetBrains.Annotations;
89

910
namespace LinqToDB.EntityFrameworkCore
1011
{
1112
using Data;
1213
using Linq;
14+
using Internal;
15+
using Interceptors;
1316

1417
public static partial class LinqToDBForEFTools
1518
{
@@ -257,7 +260,44 @@ public static ITable<T> GetTable<T>(this DbContext context)
257260

258261
return context.CreateLinqToDbContext().GetTable<T>();
259262
}
260-
263+
264+
#endregion
265+
266+
#region Interceptors
267+
268+
/// <summary>
269+
/// Returns list of registered Linq2Db interceptors from EF Context
270+
/// </summary>
271+
/// <returns>Db context object</returns>
272+
public static IList<IInterceptor>? GetLinq2DbInterceptors(this DbContext context)
273+
274+
{
275+
if (context == null) throw new ArgumentNullException(nameof(context));
276+
277+
var contextOptions = ((IInfrastructure<IServiceProvider>)context.Database)?
278+
.Instance?.GetService(typeof(IDbContextOptions)) as IDbContextOptions;
279+
280+
return contextOptions?.GetLinq2DbInterceptors();
281+
}
282+
283+
/// <summary>
284+
/// Returns list of registered Linq2Db interceptors from EF Context options
285+
/// </summary>
286+
/// <returns>Db context options</returns>
287+
public static IList<IInterceptor> GetLinq2DbInterceptors(this IDbContextOptions contextOptions)
288+
{
289+
if (contextOptions == null) throw new ArgumentNullException(nameof(contextOptions));
290+
291+
var linq2DbExtension = contextOptions?.FindExtension<LinqToDBOptionsExtension>();
292+
List<IInterceptor> interceptors = new List<IInterceptor>();
293+
if (linq2DbExtension?.Interceptors != null)
294+
{
295+
interceptors.AddRange(linq2DbExtension.Interceptors);
296+
}
297+
298+
return interceptors;
299+
}
300+
261301
#endregion
262302
}
263303
}
Lines changed: 33 additions & 0 deletions
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

Lines changed: 30 additions & 0 deletions
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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1156,6 +1156,5 @@ public virtual void LogConnectionTrace(TraceInfo info, ILogger logger)
11561156
/// Entities will be attached only if AsNoTracking() is not used in query and DbContext is configured to track entities.
11571157
/// </summary>
11581158
public virtual bool EnableChangeTracker { get; set; } = true;
1159-
11601159
}
11611160
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
using System.Linq;
2+
using LinqToDB.Interceptors;
3+
using Microsoft.EntityFrameworkCore.Infrastructure;
4+
5+
namespace LinqToDB.EntityFrameworkCore.BaseTests.Interceptors.Extensions
6+
{
7+
public static class LinqToDBContextOptionsBuilderExtensions
8+
{
9+
public static void UseEfCoreRegisteredInterceptorsIfPossible(this LinqToDBContextOptionsBuilder builder)
10+
{
11+
var coreEfExtension = builder.DbContextOptions.FindExtension<CoreOptionsExtension>();
12+
if (coreEfExtension?.Interceptors != null)
13+
{
14+
foreach (var comboInterceptor in coreEfExtension.Interceptors.OfType<IInterceptor>())
15+
{
16+
builder.AddInterceptor(comboInterceptor);
17+
}
18+
}
19+
}
20+
}
21+
}

0 commit comments

Comments
 (0)