axelheer / nein-linq

NeinLinq provides helpful extensions for using LINQ providers such as Entity Framework that support only a minor subset of .NET functions, reusing functions, rewriting queries, even making them null-safe, and building dynamic queries using translatable predicates and selectors.
MIT License
521 stars 22 forks source link

Support For Entity Framework Core 6 #27

Closed warappa closed 3 years ago

warappa commented 3 years ago

Issue

I installed NeinLinq.EntityFramework.Core 5.1.0 to a net6.0 project using Microsoft.EntityFrameworkCore 6.0.0-rc.1.21452.10. As the major versions differs I was already prepared it might not work - and so it was.

An exception was thrown, obviously due to added members in EF Core 6:

Unhandled exception. System.TypeLoadException: Method 'GetServiceProviderHashCode' in type 'ExtensionInfo' from assembly 'NeinLinq.EntityFrameworkCore, Version=5.1.0.0, Culture=neutral, PublicKeyToken=be449d2bc6d51b0b' does not have an implementation.
   at NeinLinq.RewriteDbContextOptionsExtension.get_Info()
   at Microsoft.EntityFrameworkCore.DbContextOptions.GetHashCode()
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd[TArg](TKey key, Func`3 valueFactory, TArg factoryArgument)
   at Microsoft.EntityFrameworkCore.Internal.ServiceProviderCache.GetOrAdd(IDbContextOptions options, Boolean providerRequired)
   at Microsoft.EntityFrameworkCore.DbContext..ctor(DbContextOptions options)
   at ConsoleApp.TestDbContext..ctor(DbContextOptions options) in some\path\ConsoleApp\TestDbContext.cs:line 9
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Span`1& arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitCache(ServiceCallSite callSite, RuntimeResolverContext context, ServiceProviderEngineScope serviceProviderEngine, RuntimeResolverLock lockType)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.VisitScopeCache(ServiceCallSite callSite, RuntimeResolverContext context)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteVisitor`2.VisitCallSite(ServiceCallSite callSite, TArgument argument)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteRuntimeResolver.Resolve(ServiceCallSite callSite, ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.DynamicServiceProviderEngine.<>c__DisplayClass2_0.<RealizeService>b__0(ServiceProviderEngineScope scope)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngineScope.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService(IServiceProvider provider, Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetRequiredService[T](IServiceProvider provider)
   at ConsoleApp.Program.Main() in some\path\Program.cs:line 60
   at ConsoleApp.Program.<Main>()

Question

Can you say if and when EF Core 6 will be supported?

Environment

.NET: .NET 6 RC1 Project: net6.0 EF Core: Microsoft.EntityFrameworkCore 6.0.0-rc.1.21452.10

axelheer commented 3 years ago

Thanks for the issue - hoped for no breaking change, but here it is. 🙃

I've planned at least a working build for .NET 6.0 RC1, but didn't find the time yet. You can expect something within the next few days / next week.

Stay tuned. 😎

warappa commented 3 years ago

Wow, you're kidding me 😯

That's a fast fix! Can't wait for the released package! Awesome, thank you! 👍

PS: I manually used InjectableQueryRewriter for now and after some little knowledge-gathering (re-reading the README - but slower and more precisely) it worked as advertised! Great project!

axelheer commented 3 years ago

You can use the prerelease package as well. 🤞

warappa commented 3 years ago

Thank you! This new package now works with EF Core 6! 🎉

Unfortunately, the global DbContext registration (WithLambdaInjection()) does not kick in. But I can work around that by using ToInjectable() or ToEntityInjectable on the IQueryable<T>.

Exception without ToInjectable() but with WithLambdaInjection():

Unhandled exception. System.InvalidOperationException: The LINQ expression 'ShapedQueryExpression:
    QueryExpression:
        Projection Mapping:
            EmptyProjectionMember -> EntityProjectionExpression: LocalizedStringValue
        SELECT 1
        FROM LocalizedStringValues AS l
        WHERE l.Id == l.LocalizedStringId
    ShaperExpression: EntityShaperExpression:
            LocalizedStringPlayground.LocalizedStringValue
            ValueBufferExpression:
                ProjectionBindingExpression: EmptyProjectionMember
            IsNullable: False

    .Where(namelessParameter{0} => object.Equals(
        objA: (object)EF.Property<Guid>(EntityShaperExpression:
            LocalizedStringPlayground.LocalizedString
            ValueBufferExpression:
                ProjectionBindingExpression: Outer.Outer.Outer.Outer.Inner
            IsNullable: True
        , "Id"),
        objB: (object)EF.Property<Guid>(namelessParameter{0}, "LocalizedStringId")))
    .AsQueryable()
    .Where(o => o.Culture == CultureInfo.CurrentCulture.Name)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.<VisitMethodCall>g__CheckTranslated|15_0(ShapedQueryExpression translated, <>c__DisplayClass15_0& )
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateSubquery(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.TranslateInternal(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.Translate(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitNew(NewExpression newExpression)
   at System.Linq.Expressions.NewExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Translate(SelectExpression selectExpression, Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateSelect(ShapedQueryExpression source, LambdaExpression selector)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass12_0`1.<ExecuteAsync>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at LocalizedStringPlayground.Program.Main() in some-path\LocalizedStringPlayground\Program.cs:line 119
   at LocalizedStringPlayground.Program.<Main>()
axelheer commented 3 years ago

I've some basic integration tests for WithLambdaInjection, where it does kick in (see EntityFrameworkCoreExtensionTest). Thus, we have to dig deeper a little bit. Do you have a repro for this exception for me?

warappa commented 3 years ago

I will try to provide a repo later today!

warappa commented 3 years ago

Here is a gist that shows the issue: https://gist.github.com/warappa/7b36297f604b4e5774db2e79cf5d0296

warappa commented 3 years ago

I just tested it and I can confirm it's fixed! Thank you for your work! 👍

space-alien commented 2 years ago

I am still having trouble with this - .WithLambdaInjection() does not seem to work, individual .ToEntityInjectable() calls are still required. I'm running .NET 6.0.3

axelheer commented 2 years ago

Can you provide a repro?

space-alien commented 2 years ago

I've found the problem.

I wanted to call .WithLambdaInjection() in the OnConfiguring() override in my DbContext.

It seems that the call to .WithLambdaInjection() must come after the call to UseSqlServer().

Maybe worth a mention in the docs (assuming this isn't something that could be fixed)?

axelheer commented 2 years ago

Interesting. I'll look into that. Thanks for the research!

axelheer commented 2 years ago

I just added one more test Query_WithLambdaInjectionUsingOverride_ResolvesLambdaInjection. This seems to work... (?)

Actually, I'm doing the same within one of my projects:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    => optionsBuilder.WithLambdaInjection();

Maybe there's something else?

space-alien commented 2 years ago

Quick thoughts off the top of my head:

axelheer commented 2 years ago
axelheer commented 2 years ago

Yeah, .WithLambdaInjection().UseSqlite() breaks it

axelheer commented 2 years ago

Okay, within the next versionRewriteDbContextOptionsExtension will throw an error here - thx!

space-alien commented 2 years ago

Nice one 👍