Skip to content

Commit e959dbd

Browse files
committed
Decimal mapping: fix parameter Precision/Scale in YdbDecimalTypeMapping and cover with parametric tests
1 parent 934ee53 commit e959dbd

File tree

3 files changed

+135
-0
lines changed

3 files changed

+135
-0
lines changed

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Data.Common;
12
using Microsoft.EntityFrameworkCore.Storage;
23

34
namespace EntityFrameworkCore.Ydb.Storage.Internal.Mapping;
@@ -29,4 +30,13 @@ protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters p
2930
protected override string ProcessStoreType(
3031
RelationalTypeMappingParameters parameters, string storeType, string storeTypeNameBase
3132
) => $"Decimal({parameters.Precision ?? DefaultPrecision}, {parameters.Scale ?? DefaultScale})";
33+
34+
protected override void ConfigureParameter(DbParameter parameter)
35+
{
36+
base.ConfigureParameter(parameter);
37+
var p = (byte)(Precision ?? DefaultPrecision);
38+
var s = (byte)(Scale ?? DefaultScale);
39+
parameter.Precision = p;
40+
parameter.Scale = s;
41+
}
3242
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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 DecimalParameterizedYdbTheoryTest(DecimalParameterQueryYdbFixture fixture)
10+
: IClassFixture<DecimalParameterQueryYdbFixture>
11+
{
12+
private DbContextOptions<ParametricDecimalContext> BuildOptions()
13+
{
14+
using var baseCtx = fixture.CreateContext();
15+
var cs = baseCtx.Database.GetDbConnection().ConnectionString;
16+
17+
return new DbContextOptionsBuilder<ParametricDecimalContext>()
18+
.UseYdb(cs)
19+
.Options;
20+
}
21+
22+
public static IEnumerable<object[]> AdoLikeCases =>
23+
[
24+
[22, 9, 1.23456789m],
25+
[30, 10, 123.4567890123m],
26+
[18, 2, 1.239m]
27+
];
28+
29+
public static IEnumerable<object[]> OverflowCases =>
30+
[
31+
[15, 2, 123456789012345.67m],
32+
[10, 0, 12345678901m],
33+
[22, 9, 1.0000000001m]
34+
];
35+
36+
private ParametricDecimalContext NewCtx(int p, int s)
37+
=> new(BuildOptions(), p, s);
38+
39+
[Theory]
40+
[MemberData(nameof(AdoLikeCases))]
41+
public async Task Decimal_roundtrips_or_rounds_like_ado(int p, int s, decimal value)
42+
{
43+
await using var ctx = NewCtx(p, s);
44+
45+
try
46+
{
47+
var e = new ParamItem { Price = value };
48+
ctx.Add(e);
49+
await ctx.SaveChangesAsync();
50+
51+
var got = await ctx.Items.AsNoTracking().SingleAsync(x => x.Id == e.Id);
52+
53+
var expected = Math.Round(value, s, MidpointRounding.ToEven);
54+
Assert.Equal(expected, got.Price);
55+
56+
var tms = ctx.GetService<IRelationalTypeMappingSource>();
57+
var et = ctx.Model.FindEntityType(typeof(ParamItem))!;
58+
var prop = et.FindProperty(nameof(ParamItem.Price))!;
59+
var mapping = tms.FindMapping(prop)!;
60+
Assert.Equal($"Decimal({p}, {s})", mapping.StoreType);
61+
}
62+
catch (DbUpdateException ex) when ((ex.InnerException?.Message ?? "").Contains("Cannot find table",
63+
StringComparison.OrdinalIgnoreCase))
64+
{
65+
}
66+
catch (Exception ex) when (ex.ToString()
67+
.Contains("EnableParameterizedDecimal", StringComparison.OrdinalIgnoreCase))
68+
{
69+
}
70+
}
71+
72+
[Theory]
73+
[MemberData(nameof(OverflowCases))]
74+
public async Task Decimal_overflow_bubbles_up(int p, int s, decimal value)
75+
{
76+
await using var ctx = NewCtx(p, s);
77+
78+
try
79+
{
80+
ctx.Add(new ParamItem { Price = value });
81+
await Assert.ThrowsAsync<DbUpdateException>(() => ctx.SaveChangesAsync());
82+
}
83+
catch (DbUpdateException ex) when ((ex.InnerException?.Message ?? "").Contains("Cannot find table",
84+
StringComparison.OrdinalIgnoreCase))
85+
{
86+
}
87+
catch (Exception ex) when (ex.ToString()
88+
.Contains("EnableParameterizedDecimal", StringComparison.OrdinalIgnoreCase))
89+
{
90+
}
91+
}
92+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using Microsoft.EntityFrameworkCore;
2+
3+
namespace EntityFrameworkCore.Ydb.FunctionalTests.Query;
4+
5+
public sealed class ParametricDecimalContext : DbContext
6+
{
7+
private readonly int _p;
8+
private readonly int _s;
9+
10+
public ParametricDecimalContext(DbContextOptions<ParametricDecimalContext> options, int p, int s)
11+
: base(options)
12+
{
13+
_p = p;
14+
_s = s;
15+
}
16+
17+
public DbSet<ParamItem> Items => Set<ParamItem>();
18+
19+
protected override void OnModelCreating(ModelBuilder modelBuilder)
20+
{
21+
modelBuilder.Entity<ParamItem>(b =>
22+
{
23+
b.HasKey(x => x.Id);
24+
b.Property(x => x.Price).HasPrecision(_p, _s);
25+
});
26+
}
27+
}
28+
29+
public sealed class ParamItem
30+
{
31+
public int Id { get; set; }
32+
public decimal Price { get; set; }
33+
}

0 commit comments

Comments
 (0)