Skip to content

Commit dfab183

Browse files
Fixed bug: incompatible coalesce types (#543)
Co-authored-by: Nikita Mulyukin <[email protected]>
1 parent fd8a26a commit dfab183

File tree

8 files changed

+150
-170
lines changed

8 files changed

+150
-170
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 ([#531](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/Translators/YdbQueryableAggregateMethodTranslator.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@ public class YdbQueryableAggregateMethodTranslator(
133133
[sumSqlExpression],
134134
nullable: true,
135135
argumentsPropagateNullability: ArrayUtil.FalseArrays[1],
136-
typeof(decimal)),
136+
typeof(long)),
137137
sumInputType,
138138
sumSqlExpression.TypeMapping);
139139
}

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ public class YdbDecimalTypeMapping : DecimalTypeMapping
1010

1111
public new static YdbDecimalTypeMapping Default => new();
1212

13-
public YdbDecimalTypeMapping() : this(
13+
private YdbDecimalTypeMapping() : this(
1414
new RelationalTypeMappingParameters(
1515
new CoreTypeMappingParameters(typeof(decimal)),
1616
storeType: "Decimal",
@@ -41,4 +41,7 @@ protected override void ConfigureParameter(DbParameter parameter)
4141
if (Scale is { } s)
4242
parameter.Scale = (byte)s;
4343
}
44+
45+
protected override string GenerateNonNullSqlLiteral(object value) =>
46+
$"Decimal('{base.GenerateNonNullSqlLiteral(value)}', {Precision ?? DefaultPrecision}, {Scale ?? DefaultScale})";
4447
}

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

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)