Skip to content

Commit

Permalink
Added possibility to define default interceptors for Linq2Db. (#259)
Browse files Browse the repository at this point in the history
* 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]>
  • Loading branch information
plukawski and sdanyliv authored Nov 12, 2022
1 parent d1fd30f commit df56f7f
Show file tree
Hide file tree
Showing 15 changed files with 822 additions and 2 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,17 @@ LinqToDBForEFTools.Initialize();

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

You can also register additional options (like interceptors) for LinqToDB during EF context registration, here is an example:

```cs
var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();
optionsBuilder.UseSqlite();
optionsBuilder.UseLinqToDb(builder =>
{
builder.AddInterceptor(new MyCommandInterceptor());
});
```

There are many extensions for CRUD Operations missing in vanilla EF ([watch our video](https://www.youtube.com/watch?v=m--oX73EGeQ)):

```cs
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
using System.Collections.Generic;
using System.Linq;

using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.DependencyInjection;

namespace LinqToDB.EntityFrameworkCore.Internal
{
using Interceptors;

/// <summary>
/// Model containing LinqToDB related context options
/// </summary>
public class LinqToDBOptionsExtension : IDbContextOptionsExtension
{
private DbContextOptionsExtensionInfo? _info;
private IList<IInterceptor>? _interceptors;

/// <summary>
/// Context options extension info object
/// </summary>
public DbContextOptionsExtensionInfo Info
=> _info ??= new LinqToDBExtensionInfo(this);

/// <summary>
/// List of registered LinqToDB interceptors
/// </summary>
public virtual IList<IInterceptor> Interceptors
=> _interceptors ??= new List<IInterceptor>();

/// <summary>
/// .ctor
/// </summary>
public LinqToDBOptionsExtension()
{
}

/// <summary>
/// .ctor
/// </summary>
/// <param name="copyFrom"></param>
protected LinqToDBOptionsExtension(LinqToDBOptionsExtension copyFrom)
{
_interceptors = copyFrom._interceptors;
}

/// Adds the services required to make the selected options work. This is used when
/// there is no external System.IServiceProvider and EF is maintaining its own service
/// provider internally. This allows database providers (and other extensions) to
/// register their required services when EF is creating an service provider.
/// <param name="services">The collection to add services to</param>
public void ApplyServices(IServiceCollection services)
{
;
}

/// <summary>
/// Gives the extension a chance to validate that all options in the extension are
/// valid. Most extensions do not have invalid combinations and so this will be a
/// no-op. If options are invalid, then an exception should be thrown.
/// </summary>
/// <param name="options"></param>
public void Validate(IDbContextOptions options)
{
;
}

private sealed class LinqToDBExtensionInfo : DbContextOptionsExtensionInfo
{
private string? _logFragment;

public LinqToDBExtensionInfo(IDbContextOptionsExtension extension)
: base(extension)
{
}

private new LinqToDBOptionsExtension Extension
=> (LinqToDBOptionsExtension)base.Extension;

public override bool IsDatabaseProvider
=> false;

public override string LogFragment
{
get
{
if (_logFragment == null)
{
string logFragment = string.Empty;

if (Extension.Interceptors.Any())
{
logFragment += $"Interceptors count: {Extension.Interceptors.Count}";
}

_logFragment = logFragment;
}

return _logFragment;
}
}

public override long GetServiceProviderHashCode()
{
return 0;
}

public override void PopulateDebugInfo(IDictionary<string, string> debugInfo)
=> debugInfo["Linq2Db"] = "1";
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using Microsoft.EntityFrameworkCore;

namespace LinqToDB.EntityFrameworkCore
{
using Internal;
using Interceptors;

/// <summary>
/// LinqToDB context options builder
/// </summary>
public class LinqToDBContextOptionsBuilder
{
private readonly LinqToDBOptionsExtension _extension;

/// <summary>
/// Db context options
/// </summary>
public DbContextOptions DbContextOptions { get; private set; }

/// <summary>
/// .ctor
/// </summary>
/// <param name="optionsBuilder"></param>
public LinqToDBContextOptionsBuilder(DbContextOptionsBuilder optionsBuilder)
{
_extension = optionsBuilder.Options.FindExtension<LinqToDBOptionsExtension>();
DbContextOptions = optionsBuilder.Options;
}

/// <summary>
/// Registers LinqToDb interceptor
/// </summary>
/// <param name="interceptor">The interceptor instance to register</param>
/// <returns></returns>
public LinqToDBContextOptionsBuilder AddInterceptor(IInterceptor interceptor)
{
_extension.Interceptors.Add(interceptor);
return this;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;

using JetBrains.Annotations;

namespace LinqToDB.EntityFrameworkCore
{
using Data;
using Linq;
using Internal;
using Interceptors;

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

return context.CreateLinqToDbContext().GetTable<T>();
}


#endregion

#region Interceptors

/// <summary>
/// Returns list of registered Linq2Db interceptors from EF Context
/// </summary>
/// <returns>Db context object</returns>
public static IList<IInterceptor>? GetLinq2DbInterceptors(this DbContext context)

{
if (context == null) throw new ArgumentNullException(nameof(context));

var contextOptions = ((IInfrastructure<IServiceProvider>)context.Database)?
.Instance?.GetService(typeof(IDbContextOptions)) as IDbContextOptions;

return contextOptions?.GetLinq2DbInterceptors();
}

/// <summary>
/// Returns list of registered Linq2Db interceptors from EF Context options
/// </summary>
/// <returns>Db context options</returns>
public static IList<IInterceptor> GetLinq2DbInterceptors(this IDbContextOptions contextOptions)
{
if (contextOptions == null) throw new ArgumentNullException(nameof(contextOptions));

var linq2DbExtension = contextOptions?.FindExtension<LinqToDBOptionsExtension>();
List<IInterceptor> interceptors = new List<IInterceptor>();
if (linq2DbExtension?.Interceptors != null)
{
interceptors.AddRange(linq2DbExtension.Interceptors);
}

return interceptors;
}

#endregion
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;

namespace LinqToDB.EntityFrameworkCore
{
using Internal;

public static partial class LinqToDBForEFTools
{
/// <summary>
/// Registers custom options related to LinqToDB provider
/// </summary>
/// <param name="optionsBuilder"></param>
/// <param name="linq2DbOptionsAction">Custom options action</param>
/// <returns></returns>
public static DbContextOptionsBuilder UseLinqToDb(
this DbContextOptionsBuilder optionsBuilder,
Action<LinqToDBContextOptionsBuilder>? linq2DbOptionsAction = null)
{
((IDbContextOptionsBuilderInfrastructure)optionsBuilder)
.AddOrUpdateExtension(GetOrCreateExtension(optionsBuilder));

linq2DbOptionsAction?.Invoke(new LinqToDBContextOptionsBuilder(optionsBuilder));

return optionsBuilder;
}

private static LinqToDBOptionsExtension GetOrCreateExtension(DbContextOptionsBuilder options)
=> options.Options.FindExtension<LinqToDBOptionsExtension>()
?? new LinqToDBOptionsExtension();
}
}
30 changes: 30 additions & 0 deletions Source/LinqToDB.EntityFrameworkCore/LinqToDBForEFTools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ public static DataConnection CreateLinqToDbConnection(this DbContext context,
if (mappingSchema != null)
dc.AddMappingSchema(mappingSchema);

AddInterceptorsToDataContext(context, dc);

return dc;
}

Expand Down Expand Up @@ -374,6 +376,8 @@ public static IDataContext CreateLinqToDbContext(this DbContext context,
EnableTracing(dc, logger);
}

AddInterceptorsToDataContext(context, dc);

return dc;
}

Expand Down Expand Up @@ -499,6 +503,8 @@ public static DataConnection CreateLinqToDbConnection(this DbContextOptions opti
dc.AddMappingSchema(mappingSchema);
}

AddInterceptorsToDataContext(options, dc);

return dc;
}

Expand All @@ -515,6 +521,7 @@ public static IQueryable<T> ToLinqToDB<T>(this IQueryable<T> query, IDataContext
if (context == null)
throw new LinqToDBForEFToolsException("Can not evaluate current context from query");

AddInterceptorsToDataContext(context, dc);
return new LinqToDBForEFQueryProvider<T>(dc, query.Expression);
}

Expand Down Expand Up @@ -560,5 +567,28 @@ public static bool EnableChangeTracker
set => Implementation.EnableChangeTracker = value;
}

private static void AddInterceptorsToDataContext(DbContext efContext, IDataContext dc)
{
var contextOptions = ((IInfrastructure<IServiceProvider>)efContext.Database)?
.Instance?.GetService(typeof(IDbContextOptions)) as IDbContextOptions;

AddInterceptorsToDataContext(contextOptions, dc);
}

private static void AddInterceptorsToDataContext(IDbContextOptions? contextOptions,
IDataContext dc)
{
var registeredInterceptors = contextOptions?.GetLinq2DbInterceptors();

if (registeredInterceptors != null

&& dc != null )
{
foreach (var interceptor in registeredInterceptors)
{
dc.AddInterceptor(interceptor);
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,5 @@ public virtual void LogConnectionTrace(TraceInfo info, ILogger logger)
/// Entities will be attached only if AsNoTracking() is not used in query and DbContext is configured to track entities.
/// </summary>
public virtual bool EnableChangeTracker { get; set; } = true;

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using System.Linq;
using LinqToDB.Interceptors;
using Microsoft.EntityFrameworkCore.Infrastructure;

namespace LinqToDB.EntityFrameworkCore.BaseTests.Interceptors.Extensions
{
public static class LinqToDBContextOptionsBuilderExtensions
{
public static void UseEfCoreRegisteredInterceptorsIfPossible(this LinqToDBContextOptionsBuilder builder)
{
var coreEfExtension = builder.DbContextOptions.FindExtension<CoreOptionsExtension>();
if (coreEfExtension?.Interceptors != null)
{
foreach (var comboInterceptor in coreEfExtension.Interceptors.OfType<IInterceptor>())
{
builder.AddInterceptor(comboInterceptor);
}
}
}
}
}
Loading

0 comments on commit df56f7f

Please sign in to comment.