linq2db / linq2db

Linq to database provider.
MIT License
3k stars 457 forks source link

Writing values of 'TenderId' (struct) is not supported for parameters having NpgsqlDbType 'Uuid' 6.0.0 preview-1 #4539

Open timbze opened 5 months ago

timbze commented 5 months ago

Describe your issue

I have this issue in 6.0.0-preview.1. I did not have the issue in 5.4.1.

My query

var tenderIds = new List() { TenderId1, TenderId2 };
await _context.Tender.Where(i => tenderIds.Contains(i.Id.Value)).AnyAsync()

TenderId struct

partial struct TenderId
{
    public Guid Value { get; }

    // this converter is called on startup
    internal static void LinqToDbMapping(LinqToDB.Mapping.MappingSchema ms)
    {
        ms.SetConverter<TenderId, Guid>(id => id.Value);
        ms.SetConverter<TenderId, Guid?>(id => id.Value);
        ms.SetConverter<TenderId?, Guid>(id => id == (Guid?) null ? default : id.Value.Value);
        ms.SetConverter<TenderId?, Guid?>(id => id?.Value);
        ms.SetConverter<Guid, TenderId>(TenderId.From);
        ms.SetConverter<Guid, TenderId?>(g => TenderId.From(g));
        ms.SetConverter<Guid?, TenderId>(g => g == null ? default : TenderId.From((Guid) g));
        ms.SetConverter<Guid?, TenderId?>(TenderId.From);

        ms.SetConverter<TenderId, LinqToDB.Data.DataParameter>(id => new LinqToDB.Data.DataParameter {DataType = LinqToDB.DataType.Guid, Value = id.Value});
        ms.SetConverter<TenderId?, LinqToDB.Data.DataParameter>(id => new LinqToDB.Data.DataParameter {DataType = LinqToDB.DataType.Guid, Value = id?.Value});
    }
}

Generated SQL shows up for 6.0.0-preview.1 (looks correct)

--  PostgreSQL.9.5 PostgreSQL (asynchronously)
DECLARE @value Uuid -- Guid
SET     @value = 0c2b2437-8760-4fcd-9a65-ce2c87c6d60a

SELECT
    CASE
        WHEN EXISTS(
            SELECT
                *
            FROM
                tender i
            WHERE
                i.id IN (:value)
        )
            THEN True
        ELSE False
    END

If you are seeing an exception, include the full exceptions details (message and stack trace).

Exception message: System.InvalidCastException: Writing values of 'TenderId' is not supported for parameters having NpgsqlDbType 'Uuid'
Stack trace:
at Npgsql.Internal.AdoSerializerHelpers.<GetTypeInfoForWriting>g__ThrowWritingNotSupported|1_0(Type type, PgSerializerOptions options, Nullable`1 pgTypeId, Nullable`1 npgsqlDbType, Exception inner)
   at Npgsql.Internal.AdoSerializerHelpers.GetTypeInfoForWriting(Type type, Nullable`1 pgTypeId, PgSerializerOptions options, Nullable`1 npgsqlDbType)
   at Npgsql.NpgsqlParameter.ResolveTypeInfo(PgSerializerOptions options)
   at Npgsql.NpgsqlParameterCollection.ProcessParameters(PgSerializerOptions options, Boolean validateValues, CommandType commandType)
   at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteReader(Boolean async, CommandBehavior behavior, CancellationToken cancellationToken)
   at Npgsql.NpgsqlCommand.ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken)
   at LinqToDB.Data.DataConnection.ExecuteReaderAsync(CommandBehavior commandBehavior, CancellationToken cancellationToken)
   at LinqToDB.Data.DataConnection.ExecuteReaderAsync(CommandBehavior commandBehavior, CancellationToken cancellationToken)
   at LinqToDB.Data.DataConnection.ExecuteDataReaderAsync(CommandBehavior commandBehavior, CancellationToken cancellationToken)
   at LinqToDB.Data.DataConnection.ExecuteDataReaderAsync(CommandBehavior commandBehavior, CancellationToken cancellationToken)
   at LinqToDB.Data.DataConnection.QueryRunner.ExecuteReaderAsync(CancellationToken cancellationToken)
   at LinqToDB.Linq.QueryRunner.ExecuteElementAsync[T](Query query, IDataContext dataContext, Mapper`1 mapper, Expression expression, Object[] ps, Object[] preambles, CancellationToken cancellationToken)
   at LinqToDB.Linq.QueryRunner.ExecuteElementAsync[T](Query query, IDataContext dataContext, Mapper`1 mapper, Expression expression, Object[] ps, Object[] preambles, CancellationToken cancellationToken)
   at LinqToDB.Linq.QueryRunner.ExecuteElementAsync[T](Query query, IDataContext dataContext, Mapper`1 mapper, Expression expression, Object[] ps, Object[] preambles, CancellationToken cancellationToken)
   at LinqToDB.Linq.ExpressionQuery`1.LinqToDB.Async.IQueryProviderAsync.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at LinqToDB.Linq.ExpressionQuery`1.LinqToDB.Async.IQueryProviderAsync.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at myapp

Environment details

Linq To DB version: 6.0.0-preview.1

Database (with version): PostgreSQL 16

ADO.NET Provider (with version): ?

Operating system: Ubuntu 22.04

.NET Version: 8

MaceWindu commented 5 months ago

Could you provide TenderId and _context.Tender class definitions?

timbze commented 5 months ago

I did have TenderId in OP. Tender uses TenderId type as a property, where the issue is at. I can submit minimal repro Monday when I'm at my office.

MaceWindu commented 5 months ago

I'm interested in working code for v5.4.1 as I was able to reproduce issue in v6, but it also doesn't work in v5 for me

timbze commented 5 months ago

Here's repro. https://github.com/timbze/Linq2Db4539

MaceWindu commented 5 months ago

Thanks! List<Guid> is what was missing, so I used List<TenderId> instead

timbze commented 5 months ago

Thanks! List<Guid> is what was missing, so I used List<TenderId> instead

Yes, I had that wrong in OP, sorry about that

MaceWindu commented 5 months ago

FYI, preview build on azure will be available in around 30 minutes if you want to retest it

timbze commented 5 months ago

I now tested the latest build 6.0.0-rc.13437 with my minimal repro and it still has the same error...

MaceWindu commented 5 months ago

You are right, originally-reported issue is fixed, but not one from your sample app

timbze commented 5 months ago

There is another error I'm seeing in my tests but I am not sure if it's related to this same issue. I can make a new issue if you think it's separate.

In my code I have: .Where(i => request.Id == null || i.d.Id == request.Id.Value.GuidValue)

I understood that the first part request.Id == null would always have been checked locally before, but now it's trying to translate that also to SQL, but my request.Id type I never use in SQL so don't have any converters. And so now it's telling me there is no binary equal check for the request.Id type, which is true. But before I think that was checked locally.

MaceWindu commented 5 months ago

Just update your sample app with additional query. I will create tests from both issues a bit later

timbze commented 5 months ago

Thank you, added second issue to sample app