Skip to content

Commit eace600

Browse files
Fixed bug: incompatible coalesce types #534
1 parent 974a99f commit eace600

File tree

8 files changed

+147
-179
lines changed

8 files changed

+147
-179
lines changed

src/EFCore.Ydb/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
- Fixed bug: incompatible coalesce types ([#534](https://github.com/ydb-platform/ydb-dotnet-sdk/issues/531)).
12
- Upgraded ADO.NET provider version: `0.17.0``0.24.0`.
23
- Fixed Decimal precision/scale mapping in EF provider.
34
- Supported Guid (Uuid YDB type).

src/EFCore.Ydb/src/Query/Internal/YdbSqlExpressionFactory.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,9 @@ public override SqlExpression Coalesce(SqlExpression left, SqlExpression right,
1818
RelationalTypeMapping? typeMapping = null)
1919
{
2020
// For .Sum(x => x.Decimal) EF generates coalesce(sum(x.Decimal), 0.0)) because SUM must have value
21-
2221
if (left is SqlFunctionExpression funcExpression
2322
&&
24-
right is SqlConstantExpression constExpression && constExpression.TypeMapping != null
23+
right is SqlConstantExpression { TypeMapping: not null } constExpression
2524
&&
2625
funcExpression.Name.Equals("SUM", StringComparison.OrdinalIgnoreCase)
2726
&&
@@ -35,7 +34,7 @@ public override SqlExpression Coalesce(SqlExpression left, SqlExpression right,
3534
var columnExpression = funcExpression.Arguments[0] as ColumnExpression;
3635

3736
var correctRight = new SqlConstantExpression(constExpression.Value,
38-
YdbDecimalTypeMapping.GetWithMaxPrecision(columnExpression?.TypeMapping?.Scale));
37+
YdbDecimalTypeMapping.CreateMaxPrecision(columnExpression?.TypeMapping?.Scale));
3938

4039
return base.Coalesce(left, correctRight, typeMapping);
4140
}

src/EFCore.Ydb/src/Storage/Internal/Mapping/YdbDecimalTypeMapping.cs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,22 +7,21 @@ public class YdbDecimalTypeMapping : DecimalTypeMapping
77
{
88
private const byte DefaultPrecision = 22;
99
private const byte DefaultScale = 9;
10-
1110
private const byte MaxPrecision = 35;
1211

1312
public new static YdbDecimalTypeMapping Default => new();
1413

15-
public static YdbDecimalTypeMapping GetWithMaxPrecision(int? scale) =>
16-
new(new RelationalTypeMappingParameters(
17-
new CoreTypeMappingParameters(
18-
typeof(decimal)),
14+
public static YdbDecimalTypeMapping CreateMaxPrecision(int? scale) => new(
15+
new RelationalTypeMappingParameters(
16+
new CoreTypeMappingParameters(typeof(decimal)),
1917
storeType: "Decimal",
2018
dbType: System.Data.DbType.Decimal,
2119
precision: MaxPrecision,
22-
scale: scale ?? DefaultScale)
23-
);
20+
scale: scale ?? DefaultScale
21+
)
22+
);
2423

25-
public YdbDecimalTypeMapping() : this(
24+
private YdbDecimalTypeMapping() : this(
2625
new RelationalTypeMappingParameters(
2726
new CoreTypeMappingParameters(typeof(decimal)),
2827
storeType: "Decimal",

src/EFCore.Ydb/src/Storage/Internal/YdbTypeMappingSource.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -125,12 +125,7 @@ RelationalTypeMappingSourceDependencies relationalDependencies
125125
var clrType = mappingInfo.ClrType;
126126
var storeTypeName = mappingInfo.StoreTypeName;
127127

128-
if (storeTypeName is null)
129-
{
130-
return clrType is null ? null : ClrTypeMapping.GetValueOrDefault(clrType);
131-
}
132-
133-
if (!StoreTypeMapping.TryGetValue(storeTypeName, out var mappings))
128+
if (storeTypeName is null || !StoreTypeMapping.TryGetValue(storeTypeName, out var mappings))
134129
{
135130
return clrType is null ? null : ClrTypeMapping.GetValueOrDefault(clrType);
136131
}

src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/DecimalParameterQueryYdbFixture.cs

Lines changed: 0 additions & 14 deletions
This file was deleted.
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
using EntityFrameworkCore.Ydb.Extensions;
2+
using Microsoft.EntityFrameworkCore;
3+
using Microsoft.EntityFrameworkCore.Infrastructure;
4+
using Microsoft.EntityFrameworkCore.Storage;
5+
using Xunit;
6+
7+
namespace EntityFrameworkCore.Ydb.FunctionalTests.Query;
8+
9+
public class DecimalParameterizedYdbTest
10+
{
11+
private static DbContextOptions<ParametricDecimalContext> BuildOptions() =>
12+
new DbContextOptionsBuilder<ParametricDecimalContext>()
13+
.UseYdb("Host=localhost;Port=2136")
14+
.EnableServiceProviderCaching(false)
15+
.Options;
16+
17+
public static TheoryData<int, int, decimal> SuccessCases => new()
18+
{
19+
{ 22, 9, 1.23456789m },
20+
{ 30, 10, 123.4567890123m },
21+
{ 18, 2, 12345678.91m },
22+
{ 10, 0, 9999999999m },
23+
{ 22, 9, -0.123456789m },
24+
{ 5, 2, 12.34m },
25+
{ 30, 10, 0.0000000001m }
26+
};
27+
28+
public static TheoryData<int, int, decimal> OverflowCases => new()
29+
{
30+
{ 15, 2, 123456789012345.67m },
31+
{ 10, 0, 12345678901m },
32+
{ 22, 9, 1.0000000001m },
33+
{ 18, 2, 1.239m },
34+
{ 18, 2, 100000000000000000m },
35+
{ 22, 9, 12345678901234567890.123456789m },
36+
{ 22, 9, -12345678901234567890.123456789m },
37+
{ 4, 2, 123.456m },
38+
{ 1, 0, 10m },
39+
{ 5, 0, 100000m }
40+
};
41+
42+
private static ParametricDecimalContext NewCtx(int p, int s) => new(BuildOptions(), p, s);
43+
44+
[Theory]
45+
[MemberData(nameof(SuccessCases))]
46+
public async Task Should_RoundtripDecimal_When_ValueFitsPrecisionAndScale(int p, int s, decimal value)
47+
{
48+
await using var ctx = NewCtx(p, s);
49+
await ctx.Database.EnsureCreatedAsync();
50+
51+
try
52+
{
53+
var e = new ParamItem { Price = value };
54+
ctx.Add(e);
55+
await ctx.SaveChangesAsync();
56+
57+
var got = await ctx.Items.SingleAsync(x => x.Id == e.Id);
58+
59+
Assert.Equal(value, got.Price);
60+
61+
var tms = ctx.GetService<IRelationalTypeMappingSource>();
62+
var et = ctx.Model.FindEntityType(typeof(ParamItem))!;
63+
var prop = et.FindProperty(nameof(ParamItem.Price))!;
64+
var mapping = tms.FindMapping(prop)!;
65+
Assert.Equal($"Decimal({p}, {s})", mapping.StoreType);
66+
}
67+
finally
68+
{
69+
await ctx.Database.EnsureDeletedAsync();
70+
}
71+
}
72+
73+
[Theory]
74+
[MemberData(nameof(OverflowCases))]
75+
public async Task Should_ThrowOverflow_When_ValueExceedsPrecisionOrScale(int p, int s, decimal value)
76+
{
77+
await using var ctx = NewCtx(p, s);
78+
await ctx.Database.EnsureCreatedAsync();
79+
try
80+
{
81+
ctx.Add(new ParamItem { Price = value });
82+
await Assert.ThrowsAsync<DbUpdateException>(() => ctx.SaveChangesAsync());
83+
}
84+
finally
85+
{
86+
await ctx.Database.EnsureDeletedAsync();
87+
}
88+
}
89+
90+
[Theory]
91+
[MemberData(nameof(SuccessCases))]
92+
public async Task Should_SumDecimal_When_ValueFitsPrecisionAndScale(int p, int s, decimal value)
93+
{
94+
const int multiplier = 5;
95+
await using var ctx = NewCtx(p, s);
96+
await ctx.Database.EnsureCreatedAsync();
97+
try
98+
{
99+
for (var i = 0; i < multiplier; i++)
100+
ctx.Add(new ParamItem { Price = value });
101+
await ctx.SaveChangesAsync();
102+
var got = await ctx.Items.SumAsync(x => x.Price);
103+
104+
Assert.Equal(value * multiplier, got);
105+
106+
var tms = ctx.GetService<IRelationalTypeMappingSource>();
107+
var et = ctx.Model.FindEntityType(typeof(ParamItem))!;
108+
var prop = et.FindProperty(nameof(ParamItem.Price))!;
109+
var mapping = tms.FindMapping(prop)!;
110+
Assert.Equal($"Decimal({p}, {s})", mapping.StoreType);
111+
}
112+
finally
113+
{
114+
await ctx.Database.EnsureDeletedAsync();
115+
}
116+
}
117+
118+
public sealed class ParametricDecimalContext(DbContextOptions<ParametricDecimalContext> options, int p, int s)
119+
: DbContext(options)
120+
{
121+
public DbSet<ParamItem> Items => Set<ParamItem>();
122+
123+
protected override void OnModelCreating(ModelBuilder modelBuilder) => modelBuilder.Entity<ParamItem>(b =>
124+
{
125+
b.ToTable($"Items_{p}_{s}");
126+
b.HasKey(x => x.Id);
127+
b.Property(x => x.Price).HasPrecision(p, s);
128+
});
129+
}
130+
131+
public sealed class ParamItem
132+
{
133+
public int Id { get; init; }
134+
public decimal Price { get; init; }
135+
}
136+
}

src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/DecimalParameterizedYdbTheoryTest.cs

Lines changed: 0 additions & 106 deletions
This file was deleted.

src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/ParametricDecimalContext.cs

Lines changed: 0 additions & 42 deletions
This file was deleted.

0 commit comments

Comments
 (0)