linq2db.EntityFrameworkCore
linq2db.EntityFrameworkCore copied to clipboard
Library broken with Projectables
I understand that this issue might be outside the scope and you might not care to support it. I'm trying to use the library in combination with this. It fails in the code that uses reflection to extract DbContext
from IQueryable
because of an invalid cast to QueryCompiler
(that lib defines a custom query compiler). Here it is.
Should we perhaps push the EF Core team to add API's for this?
I guess a workaround that I could do is that I could override that method in a custom impl of that class, since the method's virtual.
Just tried making a hack to make it work:
#nullable enable
using System;
using System.Linq;
using System.Reflection;
using EntityFrameworkCore.Projectables.Infrastructure.Internal;
using LinqToDB.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.Internal;
#pragma warning disable EF1001 // Internal API use
// https://github.com/linq2db/linq2db.EntityFrameworkCore/issues/364
public sealed class LinqToDbEfCoreImplWithProjectableSupport : LinqToDBForEFToolsImplDefault
{
public static readonly LinqToDbEfCoreImplWithProjectableSupport Instance = new();
private static readonly FieldInfo QueryCompilerField;
private static readonly FieldInfo QueryContextFactoryField;
private static readonly Func<RelationalQueryContextFactory, QueryContextDependencies> GetDependenciesFunc;
// https://github.com/koenbeuk/EntityFrameworkCore.Projectables/blob/4a8390565d5a8987ea6612c5c555afd35e2266dc/src/EntityFrameworkCore.Projectables/Infrastructure/Internal/CustomQueryCompiler.cs#L16
private static readonly FieldInfo ProjectablesDecoratedQueryField;
static LinqToDbEfCoreImplWithProjectableSupport()
{
FieldInfo GetPrivateField(Type type, string name)
{
var field = type.GetField(name, BindingFlags.NonPublic | BindingFlags.Instance);
if (field == null)
throw new LinqToDBForEFToolsException($"Can't find {name} field.");
return field;
}
QueryCompilerField = GetPrivateField(typeof(EntityQueryProvider), "_queryCompiler");
QueryContextFactoryField = GetPrivateField(typeof(QueryCompiler), "_queryContextFactory");
ProjectablesDecoratedQueryField = GetPrivateField(typeof(CustomQueryCompiler), "_decoratedQueryCompiler");
{
var dependenciesProperty = typeof(RelationalQueryContextFactory)
.GetProperty("Dependencies", BindingFlags.NonPublic | BindingFlags.Instance)!
?? throw new LinqToDBForEFToolsException("Can't find Dependencies property.");
var getDependenciesMethod = dependenciesProperty
.GetMethod!
?? throw new LinqToDBForEFToolsException("Can't find Dependencies property getter.");
GetDependenciesFunc = getDependenciesMethod
.CreateDelegate<Func<RelationalQueryContextFactory, QueryContextDependencies>>();
}
}
public override DbContext? GetCurrentContext(IQueryable query)
{
var compiler = QueryCompilerField.GetValue(query.Provider);
while (true)
{
if (compiler is QueryCompiler efCoreQueryCompiler)
{
if (QueryContextFactoryField.GetValue(efCoreQueryCompiler) is not RelationalQueryContextFactory queryContextFactory)
throw new LinqToDBForEFToolsException("LinqToDB Tools for EFCore support only Relational Databases.");
var dependencies = GetDependenciesFunc(queryContextFactory);
return dependencies.CurrentContext.Context;
}
if (compiler is CustomQueryCompiler projectableCompiler)
{
compiler = ProjectablesDecoratedQueryField.GetValue(projectableCompiler);
continue;
}
return null;
}
}
}
And the set that as the implementation in Main
:
LinqToDBForEFTools.Initialize();
LinqToDBForEFTools.Implementation = LinqToDbEfCoreImplWithProjectableSupport.Instance;
However, it still kept its own instance? How do I make it work? Ideally Initialize
should take a parameter with the impl, perhaps? For some reason the implementation property doesn't work.
@sdanyliv , it looks to me that LinqToDBForEFTools.Implementation
assignment should trigger re-initialize, which is not possible with current code due to Lazy already triggered.
@sdanyliv , it looks to me that
LinqToDBForEFTools.Implementation
assignment should trigger re-initialize, which is not possible with current code due to Lazy already triggered.
No, sorry, it works. I was instantiating the wrong class, that's why. I've fixed the code in the comment.