npgsql / efcore.pg

Entity Framework Core provider for PostgreSQL
PostgreSQL License
1.52k stars 223 forks source link

`TargetInvocationException` error during query after update to .NET 8 #3050

Open JustArchi opened 8 months ago

JustArchi commented 8 months ago

Minimum working repro extracted from real application:

[Table("listed_users")]
public sealed class ListedUser {
    [Column]
    [Key]
    public ulong SteamID { get; set; }
}

public sealed class MyContext : DbContext {
    public DbSet<ListedUser> ListedUsers => Set<ListedUser>();

    public MyContext(DbContextOptions<MyContext> options) : base(options) { }
}

Dictionary<Task<bool>, ulong> tasks = new();

// Throws exception
await context.ListedUsers.Where(user => !tasks.Values.Contains(user.SteamID)).ToListAsync().ConfigureAwait(false);

// This workaround makes it work however
ICollection<ulong> workaround = tasks.Values;

await context.ListedUsers.Where(user => !workaround.Contains(user.SteamID)).ToListAsync().ConfigureAwait(false);

This worked fine in EF Core 7 (.NET 7). After update to 8.0.0, it now throws with below details:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.ArgumentException: Expression of type 'System.Threading.Tasks.Task`1[System.Boolean]' cannot be used for parameter of type 'System.UInt64' (Parameter 'arg0')
   at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index)
   at System.Linq.Expressions.Expression.Invoke(Expression expression, Expression arg0)
   at System.Linq.Expressions.Expression.Invoke(Expression expression, IEnumerable`1 arguments)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.ValueConversion.NpgsqlArrayConverter`3.ArrayConversionExpression[TInput,TOutput,TConcreteOutput](LambdaExpression elementConversionExpression)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.ValueConversion.NpgsqlArrayConverter`3..ctor(ValueConverter elementConverter)
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   --- End of inner exception stack trace ---
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping.NpgsqlArrayTypeMapping`3.CreateParameters(String storeType, RelationalTypeMapping elementMapping)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping.NpgsqlArrayTypeMapping`3..ctor(String storeType, RelationalTypeMapping elementTypeMapping)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping.NpgsqlArrayTypeMapping`3..ctor(RelationalTypeMapping elementTypeMapping)
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   --- End of inner exception stack trace ---
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlTypeMappingSource.FindCollectionMapping(RelationalTypeMappingInfo info, Type modelType, Type providerType, CoreTypeMapping elementMapping)
   at Microsoft.EntityFrameworkCore.Storage.RelationalTypeMappingSource.<>c.<FindMappingWithConversion>b__8_0(ValueTuple`4 k, RelationalTypeMappingSource self)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd[TArg](TKey key, Func`3 valueFactory, TArg factoryArgument)
   at Microsoft.EntityFrameworkCore.Storage.RelationalTypeMappingSource.FindMappingWithConversion(RelationalTypeMappingInfo mappingInfo, Type providerClrType, ValueConverter customConverter)
   at Microsoft.EntityFrameworkCore.Storage.RelationalTypeMappingSource.FindMapping(Type type, IModel model, CoreTypeMapping elementMapping)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Query.NpgsqlSqlExpressionFactory.ApplyTypeMappingsOnItemAndArray(SqlExpression itemExpression, SqlExpression arrayExpression)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal.NpgsqlQueryableMethodTranslatingExpressionVisitor.TranslateContains(ShapedQueryExpression source, Expression item)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.TranslateSubquery(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal.NpgsqlSqlTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.VisitUnary(UnaryExpression unaryExpression)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal.NpgsqlSqlTranslatingExpressionVisitor.VisitUnary(UnaryExpression unaryExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.TranslateInternal(Expression expression, Boolean applyDefaultTypeMapping)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.Translate(Expression expression, Boolean applyDefaultTypeMapping)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateExpression(Expression expression, Boolean applyDefaultTypeMapping)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateLambdaExpression(ShapedQueryExpression shapedQueryExpression, LambdaExpression lambdaExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateWhere(ShapedQueryExpression source, LambdaExpression predicate)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
   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 ArchiSteamFarmBackend.Program.Init(String[] args) in /home/archi/git/JustArchiNET/ArchiSteamFarmBackend/ArchiSteamFarmBackend/Program.cs:line 279
   at ArchiSteamFarmBackend.Program.Init(String[] args) in /home/archi/git/JustArchiNET/ArchiSteamFarmBackend/ArchiSteamFarmBackend/Program.cs:line 280

For reference, I checked latest upcoming patch version in addition to 8.0.0: 8.0.1-ci.20240105T091620.

The workaround I found suggests this should be easy to fix, although I'm not an expert :slightly_smiling_face:

/cc @roji

dagophil commented 6 months ago

I am getting a similar exception after the update to .NET 8. However, I am not sure if it is for the same root cause. My entites use strongly typed keys, e.g., there is a struct EntityId<T> which has a value converter to long.

The query is similar to yours, e.g., I try to query by some collection and there is a value conversion involved:

public class Book
{
    public EntityId<Book> Id { get; set; }
    public string Title { get; set; } = "";
    public EntityId<Author>? AuthorId { get; set; }
}

ICollection<EntityId<Author>> authorIds = new List<EntityId<Author>> { new(1) };
var books = await context.Set<Book>()
    .Where(o => o.AuthorId != null && authorIds.Contains(o.AuthorId.Value))
    .ToListAsync();

In .NET 7 with Npgsql.EntityFrameworkCore.PostgreSQL 7.0.11 and Microsoft.AspNetCore.Identity.EntityFrameworkCore 7.0.16, everything works fine. In .NET 8 with Npgsql.EntityFrameworkCore.PostgreSQL 8.0.0 and Microsoft.AspNetCore.Identity.EntityFrameworkCore 8.0.2, the following exception is thrown:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation.
 ---> System.ArgumentException: Expression of type 'MyApp.EntityId`1[MyApp.Author]' cannot be used for parameter of type 'System.Nullable`1[MyApp.EntityId`1[MyApp.Author]]' (Parameter 'arg0')
   at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index)
   at System.Linq.Expressions.Expression.Invoke(Expression expression, Expression arg0)
   at System.Linq.Expressions.Expression.Invoke(Expression expression, IEnumerable`1 arguments)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.ValueConversion.NpgsqlArrayConverter`3.ArrayConversionExpression[TInput,TOutput,TConcreteOutput](LambdaExpression elementConversionExpression)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.ValueConversion.NpgsqlArrayConverter`3..ctor(ValueConverter elementConverter)
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   --- End of inner exception stack trace ---
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture)   at System.Activator.CreateInstance(Type type, Object[] args)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping.NpgsqlArrayTypeMapping`3.CreateParameters(String storeType, RelationalTypeMapping elementMapping)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping.NpgsqlArrayTypeMapping`3..ctor(String storeType, RelationalTypeMapping elementTypeMapping)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.Mapping.NpgsqlArrayTypeMapping`3..ctor(RelationalTypeMapping elementTypeMapping)
   at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor)
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   --- End of inner exception stack trace ---
   at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr)
   at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.Reflection.RuntimeConstructorInfo.Invoke(BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
   at System.RuntimeType.CreateInstanceImpl(BindingFlags bindingAttr, Binder binder, Object[] args, CultureInfo culture)   at System.Activator.CreateInstance(Type type, Object[] args)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Storage.Internal.NpgsqlTypeMappingSource.FindCollectionMapping(RelationalTypeMappingInfo info, Type modelType, Type providerType, CoreTypeMapping elementMapping)
   at Microsoft.EntityFrameworkCore.Storage.RelationalTypeMappingSource.<>c.<FindMappingWithConversion>b__8_0(ValueTuple`4 k, RelationalTypeMappingSource self)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd[TArg](TKey key, Func`3 valueFactory, TArg factoryArgument)
   at Microsoft.EntityFrameworkCore.Storage.RelationalTypeMappingSource.FindMappingWithConversion(RelationalTypeMappingInfo mappingInfo, Type providerClrType, ValueConverter customConverter)
   at Microsoft.EntityFrameworkCore.Storage.RelationalTypeMappingSource.FindMapping(Type type, IModel model, CoreTypeMapping elementMapping)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Query.NpgsqlSqlExpressionFactory.ApplyTypeMappingsOnItemAndArray(SqlExpression itemExpression, SqlExpression arrayExpression)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal.NpgsqlQueryableMethodTranslatingExpressionVisitor.TranslateContains(ShapedQueryExpression source, Expression item)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.TranslateSubquery(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal.NpgsqlSqlTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.VisitBinary(BinaryExpression binaryExpression)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Query.Internal.NpgsqlSqlTranslatingExpressionVisitor.VisitBinary(BinaryExpression binaryExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.TranslateInternal(Expression expression, Boolean applyDefaultTypeMapping)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.Translate(Expression expression, Boolean applyDefaultTypeMapping)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateExpression(Expression expression, Boolean applyDefaultTypeMapping)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateLambdaExpression(ShapedQueryExpression shapedQueryExpression, LambdaExpression lambdaExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateWhere(ShapedQueryExpression source, LambdaExpression predicate)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
   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 Program.<Main>$(String[] args) in C:\Code\test_postgres_exception\MyApp\Program.cs:line 23
   at Program.<Main>(String[] args)

Here is a small example that reproduces the exception: postgres_exception.zip

roji commented 6 months ago

Thanks for the reports and the repro - I'll try to find some time in the coming week or two to take a look.

dagophil commented 6 months ago

Thank you :)

dagophil commented 6 months ago

@roji I think I found some problematic code in the the NpgsqlArrayConverter that could be related to our problem. See line 142.

I used the code from the first comment, e.g., a local variable Dictionary<Task<bool>, ulong> tasks = new(); that is used in the query with .Where(o => !tasks.Values.Contains(o.Id)). This caused the NpgsqlArrayConverter to be called with

Say the input type is an IEnumerable<T>. Then the code tries to construct a type IList<T> using the following code:

var iListType = typeof(IList<>).MakeGenericType(typeof(TInput).GetGenericArguments()[0]);

This makes the assumption that the concrete type TInput uses its first generic argument as type for IEnumerable<>. This is not always true. For example, for Dictionary<TKey, TValue>.ValueCollection, the first argument is TKey, but it uses the second argument TValue for IEnumerable<TValue>. Maybe one needs something like this:

var valueType = typeof(TInput).GetInterfaces()
    .First(o => o.IsGenericType && o.GetGenericTypeDefinition() == typeof(IEnumerable<>))
    .GetGenericArguments()[0];
var iListType = typeof(IList<>).MakeGenericType(valueType);

After making these changes locally, the code continues instead of throwing above exception. However, it calls the converter a second time:

Then, in line 165-169, the code fails with an exception because it cannot find a suitable Dictionary<TKey, TValue>.ValueCollection constructor. I have no idea how to continue from here.

I wonder why a converter is built here at all. After all, the code only needs to write the values from the enumerable into the query. It should not need to perform any back- and forth-conversion of the underlying collection.

dagophil commented 6 months ago

@roji Another possibly problematic code is line 85-87.

The comment says:

// elementConversionExpression is always over non-nullable value types.

Is that so? In first comment, I used a model with a nullable property, e.g., public EntityId<Author>? AuthorId { get; set; }. The according value converter looks like this:

public class NullableEntityIdToLongConverter<T> : ValueConverter<EntityId<T>?, long?>
{
    public NullableEntityIdToLongConverter()
        : base(
            o => o.HasValue ? o.Value.Value : null,
            o => o.HasValue ? new EntityId<T>(o.Value) : null)
    {
    }
}

I think nullable value types are allowed in converters, arent they? Maybe the NpgsqlArrayConverter needs to update the elementConversionExpression accordingly.

roji commented 6 months ago

@dagophil thanks for the added detail - I still have too many other things going on, but this definitely will be in my list of things to look into for 8.0.3. Sorry for the delay and thanks for your patience...

dagophil commented 6 months ago

@roji No problem, thank you for looking into this :)

roji commented 5 months ago

@dagophil I took a first look here - I could indeed repro the problem (see below for a minimal distilled code sample).

My first question where would be why you have a NullableEntityIdToLongConverter; simply using EntityIdToLongConverter makes the error go away in 8.0, and should work fine (EF handles nulls around value converters, so that converters don't typically need to worry about these).

Note that I'm not saying there's no bug in 8.0 - this code did use to work in 7.0 - but I'm curious whether there's a specific reason you have NullableEntityIdToLongConverter.

Repro ```c# await using var context = new BlogContext(); await context.Database.EnsureDeletedAsync(); await context.Database.EnsureCreatedAsync(); ICollection authorIds = new List { new(1), new(2) }; var books = await context.Books.Where(o => authorIds.Contains(o.AuthorId.Value)).ToListAsync(); public class BlogContext : DbContext { public DbSet Books { get; set; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder .UseNpgsql("Host=localhost;Username=test;Password=test") .LogTo(Console.WriteLine, LogLevel.Information) .EnableSensitiveDataLogging(); protected override void OnModelCreating(ModelBuilder modelBuilder) { var authorBuilder = modelBuilder.Entity(); authorBuilder.Property(o => o.Id).HasConversion().ValueGeneratedOnAdd(); var bookBuilder = modelBuilder.Entity(); // Changing the following to EntityIdToLongConverter makes the error go away bookBuilder.Property(o => o.AuthorId).HasConversion(); } public class EntityIdToLongConverter() : ValueConverter( o => o.Value, o => new EntityId(o)); public class NullableEntityIdToLongConverter() : ValueConverter( o => o.HasValue ? o.Value.Value : null, o => o.HasValue ? new EntityId(o.Value) : null); } public class Book { public int Id { get; set; } public string Title { get; set; } = ""; public EntityId? AuthorId { get; set; } } public class Author { public EntityId Id { get; set; } public string Name { get; set; } = ""; } public record struct EntityId(long Value); ```
dagophil commented 5 months ago

@roji

My first question where would be why you have a NullableEntityIdToLongConverter; simply using EntityIdToLongConverter makes the error go away in 8.0, and should work fine (EF handles nulls around value converters, so that converters don't typically need to worry about these).

Well, I did not know that. I guess I overlooked the relevant note in the value converter documentation, but now I found it. I threw away those unnecessary nullable-converters and now everything works as expected. .NET 8 here I come :)

Thank you for your advice!

Alas, I dont think this helps the original poster with their problem, because there are no custom value converters involved.

JustArchi commented 5 months ago

Yeah, the original issue still stands and I don't believe there is any user error there, not only because it worked fine before, but also because I don't see anything to change or improve in that code in order to solve the exception thrown :slightly_smiling_face:.