-
Notifications
You must be signed in to change notification settings - Fork 28
test: add EF decimal parameterization test #522
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 14 commits
f06f8e2
7dc882c
2d391b0
7e86899
a8dcb20
859cc3b
351293a
5ccf50c
e1a5a30
934ee53
e959dbd
cbdf698
f8b1a3a
7690920
46be20d
f02d583
a1ff208
12dc451
1f5355e
d194d84
aa02060
292fc7d
c385bbe
2231913
28277ea
4189555
7cd18a3
46cfdc0
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| using EntityFrameworkCore.Ydb.FunctionalTests.TestUtilities; | ||
| using Microsoft.EntityFrameworkCore; | ||
| using Microsoft.EntityFrameworkCore.TestUtilities; | ||
|
|
||
| namespace EntityFrameworkCore.Ydb.FunctionalTests.Query; | ||
|
|
||
| public class DecimalParameterQueryYdbFixture : SharedStoreFixtureBase<DecimalParameterQueryYdbFixture.TestContext> | ||
| { | ||
| protected override string StoreName => "DecimalParameterTest"; | ||
|
|
||
| protected override ITestStoreFactory TestStoreFactory => YdbTestStoreFactory.Instance; | ||
|
|
||
| public class TestContext(DbContextOptions options) : DbContext(options) | ||
| { | ||
| protected override void OnModelCreating(ModelBuilder modelBuilder) | ||
| { | ||
| modelBuilder.Entity<ItemDefault>(b => | ||
| { | ||
| b.HasKey(x => x.Id); | ||
| b.Property(x => x.Price); | ||
| }); | ||
|
|
||
| modelBuilder.Entity<ItemExplicit>(b => | ||
| { | ||
| b.HasKey(x => x.Id); | ||
| b.Property(x => x.Price).HasPrecision(22, 9); | ||
| }); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| public class ItemDefault | ||
| { | ||
| public int Id { get; set; } | ||
| public decimal Price { get; set; } | ||
| } | ||
|
|
||
| public class ItemExplicit | ||
| { | ||
| public int Id { get; set; } | ||
| public decimal Price { get; set; } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,126 @@ | ||
| using EntityFrameworkCore.Ydb.Extensions; | ||
| using Microsoft.EntityFrameworkCore; | ||
| using Microsoft.EntityFrameworkCore.Infrastructure; | ||
| using Microsoft.EntityFrameworkCore.Storage; | ||
| using Xunit; | ||
|
|
||
| namespace EntityFrameworkCore.Ydb.FunctionalTests.Query; | ||
|
|
||
| public class DecimalParameterYdbTest(DecimalParameterQueryYdbFixture fixture) | ||
| : IClassFixture<DecimalParameterQueryYdbFixture> | ||
| { | ||
| private DecimalParameterQueryYdbFixture Fixture { get; } = fixture; | ||
|
|
||
| [ConditionalFact] | ||
| public async Task Parameter_decimal_uses_default_22_9_and_roundtrips() | ||
| { | ||
| await using var ctx = Fixture.CreateContext(); | ||
| await ctx.Database.EnsureCreatedAsync(); | ||
|
|
||
| var v = 1.23456789m; | ||
| ctx.Add(new ItemDefault { Price = v }); | ||
| await ctx.SaveChangesAsync(); | ||
|
|
||
| var got = await ctx.Set<ItemDefault>().SingleAsync(x => x.Price == v); | ||
| Assert.Equal(v, got.Price); | ||
| } | ||
|
|
||
| [ConditionalFact] | ||
| public async Task Parameter_decimal_respects_explicit_22_9_and_roundtrips() | ||
| { | ||
| await using var ctx = Fixture.CreateContext(); | ||
| await ctx.Database.EnsureCreatedAsync(); | ||
|
|
||
| var v = 123.456789012m; | ||
| ctx.Add(new ItemExplicit { Price = v }); | ||
| await ctx.SaveChangesAsync(); | ||
|
|
||
| var got = await ctx.Set<ItemExplicit>().SingleAsync(x => x.Price == v); | ||
| Assert.Equal(v, got.Price); | ||
| } | ||
|
|
||
| [ConditionalFact] | ||
| public async Task Decimal_out_of_range_bubbles_up() | ||
| { | ||
| await using var ctx = Fixture.CreateContext(); | ||
| await ctx.Database.EnsureCreatedAsync(); | ||
|
|
||
| var tooBig = new ItemExplicit { Price = 10_000_000_000_000m }; | ||
| ctx.Add(tooBig); | ||
|
|
||
| var ex = await Assert.ThrowsAsync<DbUpdateException>(() => ctx.SaveChangesAsync()); | ||
| Assert.Contains("Decimal", ex.InnerException?.Message ?? ""); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Type_mapping_default_decimal_is_22_9() | ||
| { | ||
| using var ctx = Fixture.CreateContext(); | ||
| var tms = ctx.GetService<IRelationalTypeMappingSource>(); | ||
| var mapping = tms.FindMapping(typeof(decimal))!; | ||
| Assert.Equal("Decimal(22, 9)", mapping.StoreType); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Type_mapping_custom_decimal_is_30_10() | ||
| { | ||
| var opts = new DbContextOptionsBuilder<MappingOnlyContext>() | ||
| .UseYdb("Host=localhost;Database=/local") | ||
| .Options; | ||
|
|
||
| using var ctx = new MappingOnlyContext(opts); | ||
| var tms = ctx.GetService<IRelationalTypeMappingSource>(); | ||
|
|
||
| var et = ctx.Model.FindEntityType(typeof(MappingEntity))!; | ||
| var prop = et.FindProperty(nameof(MappingEntity.Price))!; | ||
| var mapping = tms.FindMapping(prop)!; | ||
|
|
||
| Assert.Equal("Decimal(30, 10)", mapping.StoreType); | ||
| } | ||
|
|
||
| [ConditionalFact] | ||
| public async Task Parameter_decimal_respects_custom_30_10_and_roundtrips_if_supported() | ||
| { | ||
| var cs = Environment.GetEnvironmentVariable("YDB_EF_CONN"); | ||
| if (string.IsNullOrWhiteSpace(cs)) return; | ||
|
|
||
| var opts = new DbContextOptionsBuilder<MappingOnlyContext>() | ||
| .UseYdb(cs) | ||
| .Options; | ||
|
|
||
| await using var ctx = new MappingOnlyContext(opts); | ||
|
|
||
| try | ||
| { | ||
| await ctx.Database.EnsureCreatedAsync(); | ||
| } | ||
| catch (Exception ex) when (ex.ToString() | ||
| .Contains("EnableParameterizedDecimal", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| var v = 123.4567890123m; | ||
| ctx.Add(new MappingEntity { Price = v }); | ||
| await ctx.SaveChangesAsync(); | ||
|
|
||
| var got = await ctx.Set<MappingEntity>().SingleAsync(x => x.Price == v); | ||
| Assert.Equal(v, got.Price); | ||
| } | ||
|
|
||
| private sealed class MappingOnlyContext(DbContextOptions<MappingOnlyContext> options) : DbContext(options) | ||
| { | ||
| protected override void OnModelCreating(ModelBuilder b) | ||
| => b.Entity<MappingEntity>(e => | ||
| { | ||
| e.HasKey(x => x.Id); | ||
| e.Property(x => x.Price).HasPrecision(30, 10); | ||
| }); | ||
| } | ||
|
|
||
| private sealed class MappingEntity | ||
| { | ||
| public int Id { get; set; } | ||
| public decimal Price { get; set; } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| using EntityFrameworkCore.Ydb.Extensions; | ||
| using Microsoft.EntityFrameworkCore; | ||
| using Microsoft.EntityFrameworkCore.Infrastructure; | ||
| using Microsoft.EntityFrameworkCore.Storage; | ||
| using Xunit; | ||
|
|
||
| namespace EntityFrameworkCore.Ydb.FunctionalTests.Query; | ||
|
|
||
| public class DecimalParameterizedYdbTheoryTest(DecimalParameterQueryYdbFixture fixture) | ||
| : IClassFixture<DecimalParameterQueryYdbFixture> | ||
| { | ||
| private DbContextOptions<ParametricDecimalContext> BuildOptions() | ||
| { | ||
| using var baseCtx = fixture.CreateContext(); | ||
| var cs = baseCtx.Database.GetDbConnection().ConnectionString; | ||
|
|
||
| return new DbContextOptionsBuilder<ParametricDecimalContext>() | ||
| .UseYdb(cs) | ||
| .Options; | ||
| } | ||
|
|
||
| public static IEnumerable<object[]> AdoLikeCases => | ||
| [ | ||
| [22, 9, 1.23456789m], | ||
| [30, 10, 123.4567890123m], | ||
| [18, 2, 1.239m] | ||
| ]; | ||
|
|
||
| public static IEnumerable<object[]> OverflowCases => | ||
| [ | ||
| [15, 2, 123456789012345.67m], | ||
| [10, 0, 12345678901m], | ||
| [22, 9, 1.0000000001m] | ||
| ]; | ||
|
|
||
| private ParametricDecimalContext NewCtx(int p, int s) | ||
| => new(BuildOptions(), p, s); | ||
|
|
||
| [Theory] | ||
| [MemberData(nameof(AdoLikeCases))] | ||
| public async Task Decimal_roundtrips_or_rounds_like_ado(int p, int s, decimal value) | ||
| { | ||
| await using var ctx = NewCtx(p, s); | ||
|
|
||
| try | ||
| { | ||
| var e = new ParamItem { Price = value }; | ||
| ctx.Add(e); | ||
| await ctx.SaveChangesAsync(); | ||
|
|
||
| var got = await ctx.Items.AsNoTracking().SingleAsync(x => x.Id == e.Id); | ||
|
|
||
| var expected = Math.Round(value, s, MidpointRounding.ToEven); | ||
| Assert.Equal(expected, got.Price); | ||
|
|
||
| var tms = ctx.GetService<IRelationalTypeMappingSource>(); | ||
| var et = ctx.Model.FindEntityType(typeof(ParamItem))!; | ||
| var prop = et.FindProperty(nameof(ParamItem.Price))!; | ||
| var mapping = tms.FindMapping(prop)!; | ||
| Assert.Equal($"Decimal({p}, {s})", mapping.StoreType); | ||
| } | ||
| catch (DbUpdateException ex) when ((ex.InnerException?.Message ?? "").Contains("Cannot find table", | ||
| StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| } | ||
| catch (Exception ex) when (ex.ToString() | ||
| .Contains("EnableParameterizedDecimal", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| } | ||
|
Check failure on line 69 in src/EFCore.Ydb/test/EntityFrameworkCore.Ydb.FunctionalTests/Query/DecimalParameterizedYdbTheoryTest.cs
|
||
LiamHamsters marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| [Theory] | ||
| [MemberData(nameof(OverflowCases))] | ||
| public async Task Decimal_overflow_bubbles_up(int p, int s, decimal value) | ||
| { | ||
| await using var ctx = NewCtx(p, s); | ||
|
|
||
| try | ||
| { | ||
| ctx.Add(new ParamItem { Price = value }); | ||
| await Assert.ThrowsAsync<DbUpdateException>(() => ctx.SaveChangesAsync()); | ||
| } | ||
| catch (DbUpdateException ex) when ((ex.InnerException?.Message ?? "").Contains("Cannot find table", | ||
| StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| } | ||
| catch (Exception ex) when (ex.ToString() | ||
| .Contains("EnableParameterizedDecimal", StringComparison.OrdinalIgnoreCase)) | ||
| { | ||
| } | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| using Microsoft.EntityFrameworkCore; | ||
|
|
||
| namespace EntityFrameworkCore.Ydb.FunctionalTests.Query; | ||
|
|
||
| public sealed class ParametricDecimalContext : DbContext | ||
| { | ||
| private readonly int _p; | ||
| private readonly int _s; | ||
|
|
||
| public ParametricDecimalContext(DbContextOptions<ParametricDecimalContext> options, int p, int s) | ||
| : base(options) | ||
| { | ||
| _p = p; | ||
| _s = s; | ||
| } | ||
|
|
||
| public DbSet<ParamItem> Items => Set<ParamItem>(); | ||
|
|
||
| protected override void OnModelCreating(ModelBuilder modelBuilder) => | ||
| modelBuilder.Entity<ParamItem>(b => | ||
| { | ||
| b.HasKey(x => x.Id); | ||
| b.Property(x => x.Price).HasPrecision(_p, _s); | ||
| }); | ||
| } | ||
|
|
||
| public sealed class ParamItem | ||
| { | ||
| public int Id { get; set; } | ||
| public decimal Price { get; set; } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.