linq2db.EntityFrameworkCore icon indicating copy to clipboard operation
linq2db.EntityFrameworkCore copied to clipboard

Library broken with Projectables

Open AntonC9018 opened this issue 1 year ago • 3 comments

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.

AntonC9018 avatar Oct 25 '23 19:10 AntonC9018

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.

AntonC9018 avatar Oct 25 '23 19:10 AntonC9018

@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.

MaceWindu avatar Oct 26 '23 07:10 MaceWindu

@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.

AntonC9018 avatar Oct 29 '23 16:10 AntonC9018