Numeric casts round instead of truncating, `(byte)` casts don't wrap, and `BigInteger` division isn't integer division

Ouverte
#4,095 0 commentaires 0 réactions 0 personnes assignées Voir sur GitHub

Les mainteneurs répondent en général sous 1 jour

Personne n'a encore pris cette issue.

Évaluation

Difficulté
3/5
Temps estimé
1-2 jours
Accessibilité débutants
70/100
Type d'issue
Bug
Clarté
Clairement spécifiée
Activité
Active
Stack technique
csharp, postgresql
Domaine
database

Piste de recherche

Start in NpgsqlQuerySqlGenerator.cs (L603-626) where integer casts render as ::int; switch to trunc(x)::int to match .NET truncation. Then fix NpgsqlConvertTranslator.cs L36 and NpgsqlBigIntegerTypeMapping.cs L30 (use div for division). Run the issue's repro to confirm all four LINQ queries return identical .NET and database results.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Description

Environment
PostgreSQL 18.6 (postgres:18), session TimeZone Etc/UTC
.NET 10 runtime 10.0.11, Npgsql.EntityFrameworkCore.PostgreSQL 10.0.3, EF Core 10.0.4
.NET 11 runtime 11.0.0-rc.2, Npgsql.EntityFrameworkCore.PostgreSQL 11.0.0-rc.2, EF Core 11.0.0-rc.2
Also Npgsql 10.0.3 on both lines, macOS arm64

Both lines print the same output. main at 150011d has the same code.

LINQ Row .NET PostgreSQL SQL
(int)r.Decimal 2.7m 2 3 r."Decimal"::int
(long)r.Double 2.7 2 3 r."Double"::bigint
(byte)r.Int == 249 −7 match no match r."Int"::smallint = 249
r.Big / r.Big2 == 3 7 / 2 match no match r."Big" / r."Big2" = 3
Repro
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System.Numerics;

await using var context = new TestContext();
await context.Database.EnsureDeletedAsync();
await context.Database.EnsureCreatedAsync();

context.Rows.Add(new Row { Decimal = 2.7m, Double = 2.7, Int = -7, Big = 7, Big2 = 2 });
await context.SaveChangesAsync();
var row = await context.Rows.AsNoTracking().SingleAsync();

// 1. C# casts truncate toward zero; ::int rounds
Console.WriteLine($"(int)2.7m: .NET {(int)row.Decimal}, database {await context.Rows.Select(r => (int)r.Decimal).SingleAsync()}");
Console.WriteLine($"(long)2.7: .NET {(long)row.Double}, database {await context.Rows.Select(r => (long)r.Double).SingleAsync()}");

// 2. An unchecked byte cast wraps; ::smallint keeps -7
Console.WriteLine($"(byte)-7 == 249: .NET {((byte)row.Int == 249 ? 1 : 0)}, database {await context.Rows.CountAsync(r => (byte)r.Int == 249)}");

// 3. BigInteger division is integer division
Console.WriteLine($"Big / Big2 == 3 (7 / 2): .NET {(row.Big / row.Big2 == 3 ? 1 : 0)}, database {await context.Rows.CountAsync(r => r.Big / r.Big2 == 3)}");

public class TestContext : DbContext
{
    public DbSet<Row> Rows => Set<Row>();

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseNpgsql(Environment.GetEnvironmentVariable("Test__Npgsql__DefaultConnection"), o => o.UseNodaTime())
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();
}

public class Row
{
    public int Id { get; set; }
    public decimal Decimal { get; set; }
    public double Double { get; set; }
    public int Int { get; set; }
    public BigInteger Big { get; set; }
    public BigInteger Big2 { get; set; }
}

Output, the same on both lines:

(int)2.7m: .NET 2, database 3
(long)2.7: .NET 2, database 3
(byte)-7 == 249: .NET 1, database 0
Big / Big2 == 3 (7 / 2): .NET 1, database 0
  • Casts. C# casts from decimal, double and float to an integer type truncate toward zero. PostgreSQL's casts round: half away from zero for numeric, half to even for float8. SQL Server's CAST truncates, so this is specific to this provider. The cast is rendered as ::int (NpgsqlQuerySqlGenerator.cs#L603-L626); trunc(x)::int would match .NET. Convert.ToInt32(decimal) goes the same way (NpgsqlConvertTranslator.cs#L36) and rounds half away from zero, where .NET rounds half to even.
  • byte maps to smallint, so an unchecked (byte) cast keeps −7 instead of wrapping to 249.
  • BigInteger maps to numeric (NpgsqlBigIntegerTypeMapping.cs#L30), so / is numeric division (3.5). div(a, b) matches BigInteger's truncating division.
Langage dominant
C#
Étoiles
1.8k
Forks
268
Merge moyen
2 h 35 min
PR mergées (30 j)
136

Préparer son environnement

Ce projet ne fournit ni conteneur de développement, ni Dockerfile, ni guide de contribution : l'installation est à votre charge. Commencez par son README, et consultez notre guide de la première contribution pour les étapes générales.

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Autres issues de npgsql/efcore.pg

Toutes les issues de npgsql/efcore.pg

Issues similaires

Plus d'issues C#

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.