diff --git a/test/EFCore.Cosmos.FunctionalTests/Types/CosmosMiscellaneousTypeTest.cs b/test/EFCore.Cosmos.FunctionalTests/Types/CosmosMiscellaneousTypeTest.cs index 00fdc1e67e1..9166b5e7461 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Types/CosmosMiscellaneousTypeTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Types/CosmosMiscellaneousTypeTest.cs @@ -1,13 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore.Types; +namespace Microsoft.EntityFrameworkCore.Types.Miscellaneous; public class BoolTypeTest(BoolTypeTest.BoolTypeFixture fixture) : TypeTestBase(fixture) { - public class BoolTypeFixture() : TypeTestFixture(true, false) + public class BoolTypeFixture : TypeTestFixture { + public override bool Value { get; } = true; + public override bool OtherValue { get; } = false; + protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance; public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) @@ -18,8 +21,11 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build public class StringTypeTest(StringTypeTest.StringTypeFixture fixture) : TypeTestBase(fixture) { - public class StringTypeFixture() : TypeTestFixture("foo", "bar") + public class StringTypeFixture : TypeTestFixture { + public override string Value { get; } = "foo"; + public override string OtherValue { get; } = "bar"; + protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance; public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) @@ -30,10 +36,11 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build public class GuidTypeTest(GuidTypeTest.GuidTypeFixture fixture) : TypeTestBase(fixture) { - public class GuidTypeFixture() : TypeTestFixture( - new Guid("8f7331d6-cde9-44fb-8611-81fff686f280"), - new Guid("ae192c36-9004-49b2-b785-8be10d169627")) + public class GuidTypeFixture : TypeTestFixture { + public override Guid Value { get; } = new("8f7331d6-cde9-44fb-8611-81fff686f280"); + public override Guid OtherValue { get; } = new("ae192c36-9004-49b2-b785-8be10d169627"); + protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance; public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) @@ -44,8 +51,11 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build public class ByteArrayTypeTest(ByteArrayTypeTest.ByteArrayTypeFixture fixture) : TypeTestBase(fixture) { - public class ByteArrayTypeFixture() : TypeTestFixture([1, 2, 3], [4, 5, 6]) + public class ByteArrayTypeFixture : TypeTestFixture { + public override byte[] Value { get; } = [1, 2, 3]; + public override byte[] OtherValue { get; } = [4, 5, 6, 7]; + protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance; public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) diff --git a/test/EFCore.Cosmos.FunctionalTests/Types/CosmosNumericTypeTest.cs b/test/EFCore.Cosmos.FunctionalTests/Types/CosmosNumericTypeTest.cs index b2a5b4a42c7..ab7772b2d5d 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Types/CosmosNumericTypeTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Types/CosmosNumericTypeTest.cs @@ -1,12 +1,15 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore.Types; +namespace Microsoft.EntityFrameworkCore.Types.Numeric; public class ByteTypeTest(ByteTypeTest.ByteTypeFixture fixture) : TypeTestBase(fixture) { - public class ByteTypeFixture() : TypeTestFixture(byte.MinValue, byte.MaxValue) + public class ByteTypeFixture : TypeTestFixture { + public override byte Value { get; } = byte.MinValue; + public override byte OtherValue { get; } = byte.MaxValue; + protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance; public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) @@ -16,8 +19,11 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build public class ShortTypeTest(ShortTypeTest.ShortTypeFixture fixture) : TypeTestBase(fixture) { - public class ShortTypeFixture() : TypeTestFixture(short.MinValue, short.MaxValue) + public class ShortTypeFixture : TypeTestFixture { + public override short Value { get; } = short.MinValue; + public override short OtherValue { get; } = short.MaxValue; + protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance; public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) @@ -27,8 +33,11 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build public class IntTypeTest(IntTypeTest.IntTypeFixture fixture) : TypeTestBase(fixture) { - public class IntTypeFixture() : TypeTestFixture(int.MinValue, int.MaxValue) + public class IntTypeFixture : TypeTestFixture { + public override int Value { get; } = int.MinValue; + public override int OtherValue { get; } = int.MaxValue; + protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance; public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) @@ -38,8 +47,11 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build public class LongTypeTest(LongTypeTest.LongTypeFixture fixture) : TypeTestBase(fixture) { - public class LongTypeFixture() : TypeTestFixture(long.MinValue, long.MaxValue) + public class LongTypeFixture : TypeTestFixture { + public override long Value { get; } = long.MinValue; + public override long OtherValue { get; } = long.MaxValue; + protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance; public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) @@ -49,8 +61,11 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build public class DecimalTypeTest(DecimalTypeTest.DecimalTypeFixture fixture) : TypeTestBase(fixture) { - public class DecimalTypeFixture() : TypeTestFixture(30.5m, 30m) + public class DecimalTypeFixture : TypeTestFixture { + public override decimal Value { get; } = 30.5m; + public override decimal OtherValue { get; } = 30m; + protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance; public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) @@ -60,8 +75,11 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build public class DoubleTypeTest(DoubleTypeTest.DoubleTypeFixture fixture) : TypeTestBase(fixture) { - public class DoubleTypeFixture() : TypeTestFixture(30.5d, 30d) + public class DoubleTypeFixture : TypeTestFixture { + public override double Value { get; } = 30.5d; + public override double OtherValue { get; } = 30d; + protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance; public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) @@ -71,8 +89,11 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build public class FloatTypeTest(FloatTypeTest.FloatTypeFixture fixture) : TypeTestBase(fixture) { - public class FloatTypeFixture() : TypeTestFixture(30.5f, 30f) + public class FloatTypeFixture : TypeTestFixture { + public override float Value { get; } = 30.5f; + public override float OtherValue { get; } = 30f; + protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance; public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) diff --git a/test/EFCore.Cosmos.FunctionalTests/Types/CosmosTemporalTypeTest.cs b/test/EFCore.Cosmos.FunctionalTests/Types/CosmosTemporalTypeTest.cs index 24e63d06f42..147cc2f2e8d 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Types/CosmosTemporalTypeTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Types/CosmosTemporalTypeTest.cs @@ -1,15 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore.Types; +namespace Microsoft.EntityFrameworkCore.Types.Temporal; public class DateTimeTypeTest(DateTimeTypeTest.DateTimeTypeFixture fixture) : TypeTestBase(fixture) { - public class DateTimeTypeFixture() : TypeTestFixture( - new DateTime(2020, 1, 5, 12, 30, 45, DateTimeKind.Unspecified), - new DateTime(2022, 5, 3, 0, 0, 0, DateTimeKind.Unspecified)) + public class DateTimeTypeFixture : TypeTestFixture { + public override DateTime Value { get; } = new DateTime(2020, 1, 5, 12, 30, 45, DateTimeKind.Unspecified); + public override DateTime OtherValue { get; } = new DateTime(2022, 5, 3, 0, 0, 0, DateTimeKind.Unspecified); + protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance; public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) @@ -20,10 +21,11 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build public class DateTimeOffsetTypeTest(DateTimeOffsetTypeTest.DateTimeOffsetTypeFixture fixture) : TypeTestBase(fixture) { - public class DateTimeOffsetTypeFixture() : TypeTestFixture( - new DateTimeOffset(2020, 1, 5, 12, 30, 45, TimeSpan.FromHours(2)), - new DateTimeOffset(2020, 1, 5, 12, 30, 45, TimeSpan.FromHours(3))) + public class DateTimeOffsetTypeFixture : TypeTestFixture { + public override DateTimeOffset Value { get; } = new DateTimeOffset(2020, 1, 5, 12, 30, 45, TimeSpan.FromHours(2)); + public override DateTimeOffset OtherValue { get; } = new DateTimeOffset(2020, 1, 5, 12, 30, 45, TimeSpan.FromHours(3)); + protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance; public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) @@ -33,10 +35,11 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build public class DateOnlyTypeTest(DateOnlyTypeTest.DateTypeFixture fixture) : TypeTestBase(fixture) { - public class DateTypeFixture() : TypeTestFixture( - new DateOnly(2020, 1, 5), - new DateOnly(2022, 5, 3)) + public class DateTypeFixture : TypeTestFixture { + public override DateOnly Value { get; } = new DateOnly(2020, 1, 5); + public override DateOnly OtherValue { get; } = new DateOnly(2022, 5, 3); + protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance; public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) @@ -47,10 +50,11 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build public class TimeOnlyTypeTest(TimeOnlyTypeTest.TimeTypeFixture fixture) : TypeTestBase(fixture) { - public class TimeTypeFixture() : TypeTestFixture( - new TimeOnly(12, 30, 45), - new TimeOnly(14, 0, 0)) + public class TimeTypeFixture : TypeTestFixture { + public override TimeOnly Value { get; } = new TimeOnly(12, 30, 45); + public override TimeOnly OtherValue { get; } = new TimeOnly(14, 0, 0); + protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance; public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) @@ -60,10 +64,11 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build public class TimeSpanTypeTest(TimeSpanTypeTest.TimeSpanTypeFixture fixture) : TypeTestBase(fixture) { - public class TimeSpanTypeFixture() : TypeTestFixture( - new TimeSpan(12, 30, 45), - new TimeSpan(14, 0, 0)) + public class TimeSpanTypeFixture : TypeTestFixture { + public override TimeSpan Value { get; } = new TimeSpan(12, 30, 45); + public override TimeSpan OtherValue { get; } = new TimeSpan(14, 0, 0); + protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance; public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) diff --git a/test/EFCore.Relational.Specification.Tests/RelationalTypeTestBase.cs b/test/EFCore.Relational.Specification.Tests/RelationalTypeTestBase.cs index fb0625048e2..57236e96ce7 100644 --- a/test/EFCore.Relational.Specification.Tests/RelationalTypeTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/RelationalTypeTestBase.cs @@ -7,6 +7,43 @@ public abstract class RelationalTypeTestBase(TFixture fixture) : Ty where TFixture : RelationalTypeTestBase.RelationalTypeTestFixture where T : notnull { + public RelationalTypeTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) + : this(fixture) + { + Fixture.TestSqlLoggerFactory.Clear(); + Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); + } + + #region SaveChanges + + [ConditionalFact] + public virtual async Task SaveChanges_within_json() + => await TestHelpers.ExecuteWithStrategyInTransactionAsync( + Fixture.CreateContext, + Fixture.UseTransaction, + async context => + { + JsonTypeEntity entity; + + using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents()) + { + entity = await context.Set().SingleAsync(e => e.Id == 1); + } + + entity.JsonContainer.Value = Fixture.OtherValue; + await context.SaveChangesAsync(); + + using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents()) + { + var result = await context.Set().Where(e => e.Id == 1).SingleAsync(); + Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer); + } + }); + + #endregion SaveChanges + + #region ExecuteUpdate + [ConditionalFact] public virtual async Task ExecuteUpdate_within_json_to_parameter() => await TestHelpers.ExecuteWithStrategyInTransactionAsync( @@ -15,8 +52,12 @@ public virtual async Task ExecuteUpdate_within_json_to_parameter() async context => { await context.Set().ExecuteUpdateAsync(s => s.SetProperty(e => e.JsonContainer.Value, e => Fixture.OtherValue)); - var result = await context.Set().Where(e => e.Id == 1).SingleAsync(); - Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer); + + using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents()) + { + var result = await context.Set().Where(e => e.Id == 1).SingleAsync(); + Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer); + } }); [ConditionalFact] @@ -33,8 +74,12 @@ public virtual async Task ExecuteUpdate_within_json_to_constant() parameter); await context.Set().ExecuteUpdateAsync(s => s.SetProperty(e => e.JsonContainer.Value, valueExpression)); - var result = await context.Set().Where(e => e.Id == 1).SingleAsync(); - Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer); + + using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents()) + { + var result = await context.Set().Where(e => e.Id == 1).SingleAsync(); + Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer); + } }); [ConditionalFact] @@ -45,8 +90,12 @@ public virtual async Task ExecuteUpdate_within_json_to_another_json_property() async context => { await context.Set().ExecuteUpdateAsync(s => s.SetProperty(e => e.JsonContainer.Value, e => e.JsonContainer.OtherValue)); - var result = await context.Set().Where(e => e.Id == 1).SingleAsync(); - Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer); + + using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents()) + { + var result = await context.Set().Where(e => e.Id == 1).SingleAsync(); + Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer); + } }); [ConditionalFact] @@ -57,10 +106,16 @@ public virtual async Task ExecuteUpdate_within_json_to_nonjson_column() async context => { await context.Set().ExecuteUpdateAsync(s => s.SetProperty(e => e.JsonContainer.Value, e => e.OtherValue)); - var result = await context.Set().Where(e => e.Id == 1).SingleAsync(); - Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer); + + using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents()) + { + var result = await context.Set().Where(e => e.Id == 1).SingleAsync(); + Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer); + } }); + #endregion ExecuteUpdate + protected class JsonTypeEntity { public int Id { get; set; } @@ -77,17 +132,37 @@ public class JsonContainer public required T OtherValue { get; set; } } - public abstract class RelationalTypeTestFixture(T value, T otherValue) - : TypeTestFixture(value, otherValue) + protected void AssertSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); + + protected void AssertExecuteUpdateSql(params string[] expected) + => Fixture.TestSqlLoggerFactory.AssertBaseline(expected, forUpdate: true); + + public abstract class RelationalTypeTestFixture : TypeTestFixture, ITestSqlLoggerFactory { + public virtual string? StoreType => null; + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { base.OnModelCreating(modelBuilder, context); + modelBuilder.Entity(b => + { + b.Property(e => e.Value).HasColumnType(StoreType); + b.Property(e => e.OtherValue).HasColumnType(StoreType); + }); + modelBuilder.Entity(b => { modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); - b.ComplexProperty(e => e.JsonContainer, cb => cb.ToJson()); + + b.ComplexProperty(e => e.JsonContainer, jc => + { + jc.ToJson(); + + jc.Property(e => e.Value).HasColumnType(StoreType); + jc.Property(e => e.OtherValue).HasColumnType(StoreType); + }); }); } @@ -122,6 +197,9 @@ protected override async Task SeedAsync(DbContext context) await context.SaveChangesAsync(); } + public TestSqlLoggerFactory TestSqlLoggerFactory + => (TestSqlLoggerFactory)ListLoggerFactory; + public virtual void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); } diff --git a/test/EFCore.Specification.Tests/SharedStoreFixtureBase.cs b/test/EFCore.Specification.Tests/SharedStoreFixtureBase.cs index adcab6c6467..3f19ea6aa2f 100644 --- a/test/EFCore.Specification.Tests/SharedStoreFixtureBase.cs +++ b/test/EFCore.Specification.Tests/SharedStoreFixtureBase.cs @@ -76,6 +76,8 @@ public virtual async Task InitializeAsync() _serviceProvider = services.BuildServiceProvider(validateScopes: true); await TestStore.InitializeAsync(ServiceProvider, CreateContext, c => SeedAsync((TContext)c), CleanAsync); + + ListLoggerFactory.Clear(); } public virtual TContext CreateContext() diff --git a/test/EFCore.Specification.Tests/TypeTestBase.cs b/test/EFCore.Specification.Tests/TypeTestBase.cs index 5a52c717705..ef7e764171a 100644 --- a/test/EFCore.Specification.Tests/TypeTestBase.cs +++ b/test/EFCore.Specification.Tests/TypeTestBase.cs @@ -9,7 +9,7 @@ public abstract class TypeTestBase(TFixture fixture) : IClassFixtur where T : notnull { [ConditionalFact] - public async Task Equality_in_query() + public async virtual Task Equality_in_query() { await using var context = Fixture.CreateContext(); @@ -28,13 +28,19 @@ protected class TypeEntity protected TFixture Fixture { get; } = fixture; - public abstract class TypeTestFixture(T value, T otherValue) - : SharedStoreFixtureBase + public abstract class TypeTestFixture : SharedStoreFixtureBase { - protected override string StoreName => "TypeTest"; + /// + /// The main value used in the tests. + /// + public abstract T Value { get; } + + /// + /// An additional value that is different from . + /// + public abstract T OtherValue { get; } - public T Value { get; } = value; - public T OtherValue { get; } = otherValue; + protected override string StoreName => "TypeTest"; public virtual Func Comparer { get; } = EqualityComparer.Default.Equals; diff --git a/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerGeographyTypeTest.cs b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerGeographyTypeTest.cs new file mode 100644 index 00000000000..b20b8b17d7b --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerGeographyTypeTest.cs @@ -0,0 +1,222 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using NetTopologySuite.Geometries; + +namespace Microsoft.EntityFrameworkCore.Types.Geography; + +public abstract class GeographyTypeTestBase(TFixture fixture) : RelationalTypeTestBase(fixture) + where T : NetTopologySuite.Geometries.Geometry + where TFixture : GeographyTypeTestBase.GeographyTypeFixture +{ + // SQL Server doesn't support the equality operator on geography, override to use EqualsTopologically + public override async Task Equality_in_query() + { + await using var context = Fixture.CreateContext(); + + var result = await context.Set().Where(e => e.Value.EqualsTopologically(Fixture.Value)).SingleAsync(); + + Assert.Equal(Fixture.Value, result.Value, Fixture.Comparer); + } + + public override async Task ExecuteUpdate_within_json_to_nonjson_column() + { + // See #36688 for supporting this for SQL Server types other than string/numeric/bool + var exception = await Assert.ThrowsAsync(() => base.ExecuteUpdate_within_json_to_nonjson_column()); + Assert.Equal(RelationalStrings.ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn, exception.Message); + } + + public abstract class GeographyTypeFixture() : RelationalTypeTestFixture + { + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder).UseSqlServer(o => o.UseNetTopologySuite()); + + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; + } +} + +public class PointTypeTest(PointTypeTest.PointTypeFixture fixture) + : GeographyTypeTestBase(fixture) +{ + public class PointTypeFixture() : GeographyTypeFixture + { + public override Point Value { get; } = new(-122.34877, 47.6233355) { SRID = 4326 }; + public override Point OtherValue { get; } = new(-121.7500, 46.2500) { SRID = 4326 }; + } +} + +public class LineStringTypeTest(LineStringTypeTest.LineStringTypeFixture fixture) + : GeographyTypeTestBase(fixture) +{ + public class LineStringTypeFixture() : GeographyTypeFixture + { + public override LineString Value { get; } = new( + [ + new Coordinate(-122.34877, 47.6233355), + new Coordinate(-122.3308366, 47.5978429) + ]) { SRID = 4326 }; + + public override LineString OtherValue { get; } = new( + [ + new Coordinate(-121.5000, 46.9000), + new Coordinate(-121.2000, 46.6500), + new Coordinate(-121.0000, 46.4000) + ]) { SRID = 4326 }; + } +} + + public class PolygonTypeTest(PolygonTypeTest.PolygonTypeFixture fixture) + : GeographyTypeTestBase(fixture) + { + public class PolygonTypeFixture() : GeographyTypeFixture + { + // Simple rectangle + public override Polygon Value { get; } = new( + new LinearRing([ + new Coordinate(-122.3500, 47.6200), // NW + new Coordinate(-122.3500, 47.6100), // SW + new Coordinate(-122.3400, 47.6100), // SE + new Coordinate(-122.3400, 47.6200), // NE + new Coordinate(-122.3500, 47.6200) // Close + ])) { SRID = 4326 }; + + // Shifted rectangle; different area so not topologically equal + public override Polygon OtherValue { get; } = new( + new LinearRing([ + new Coordinate(-121.3000, 46.6000), // NW + new Coordinate(-121.3000, 46.5900), // SW + new Coordinate(-121.2800, 46.5900), // SE + new Coordinate(-121.2800, 46.6000), // NE + new Coordinate(-121.3000, 46.6000) + ])) { SRID = 4326 }; + } + } + + public class MultiPointTypeTest(MultiPointTypeTest.MultiPointTypeFixture fixture) + : GeographyTypeTestBase(fixture) + { + public class MultiPointTypeFixture() : GeographyTypeFixture + { + public override MultiPoint Value { get; } = new([ + new Point(-122.3500, 47.6200) { SRID = 4326 }, + new Point(-122.3450, 47.6150) { SRID = 4326 } + ]) { SRID = 4326 }; + + public override MultiPoint OtherValue { get; } = new([ + new Point(-121.9000, 46.9500) { SRID = 4326 }, + new Point(-121.5000, 46.6000) { SRID = 4326 }, + new Point(-121.2000, 46.3000) { SRID = 4326 } + ]) { SRID = 4326 }; + } + } + + public class MultiLineStringTypeTest(MultiLineStringTypeTest.MultiLineStringTypeFixture fixture) + : GeographyTypeTestBase(fixture) + { + public class MultiLineStringTypeFixture() : GeographyTypeFixture + { + public override MultiLineString Value { get; } = new([ + new LineString([ + new Coordinate(-122.3500, 47.6200), + new Coordinate(-122.3450, 47.6150) + ]) { SRID = 4326 }, + new LineString([ + new Coordinate(-122.3480, 47.6180), + new Coordinate(-122.3420, 47.6130) + ]) { SRID = 4326 } + ]) { SRID = 4326 }; + + public override MultiLineString OtherValue { get; } = new([ + new LineString([ + new Coordinate(-121.9000, 46.9500), + new Coordinate(-121.6000, 46.8200) + ]) { SRID = 4326 }, + new LineString([ + new Coordinate(-121.7000, 46.7800), + new Coordinate(-121.4000, 46.5500) + ]) { SRID = 4326 } + ]) { SRID = 4326 }; + } + } + + public class MultiPolygonTypeTest(MultiPolygonTypeTest.MultiPolygonTypeFixture fixture) + : GeographyTypeTestBase(fixture) + { + public class MultiPolygonTypeFixture() : GeographyTypeFixture + { + public override MultiPolygon Value { get; } = new( + [ + new Polygon(new LinearRing([ + new Coordinate(-122.3500, 47.6200), // NW + new Coordinate(-122.3500, 47.6150), // SW + new Coordinate(-122.3450, 47.6150), // SE + new Coordinate(-122.3450, 47.6200), // NE + new Coordinate(-122.3500, 47.6200) + ])) { SRID = 4326 }, + new Polygon(new LinearRing([ + new Coordinate(-122.3525, 47.6230), // NW + new Coordinate(-122.3525, 47.6215), // SW + new Coordinate(-122.3510, 47.6215), // SE + new Coordinate(-122.3510, 47.6230), // NE + new Coordinate(-122.3525, 47.6230) + ])) { SRID = 4326 } + ]) { SRID = 4326 }; + + public override MultiPolygon OtherValue { get; } = new( + [ + new Polygon(new LinearRing([ + new Coordinate(-121.3600, 46.6250), // NW + new Coordinate(-121.3600, 46.6200), // SW + new Coordinate(-121.3550, 46.6200), // SE + new Coordinate(-121.3550, 46.6250), // NE + new Coordinate(-121.3600, 46.6250) + ])) { SRID = 4326 }, + new Polygon(new LinearRing([ + new Coordinate(-121.3540, 46.6240), // NW + new Coordinate(-121.3540, 46.6220), // SW + new Coordinate(-121.3525, 46.6220), // SE + new Coordinate(-121.3525, 46.6240), // NE + new Coordinate(-121.3540, 46.6240) + ])) { SRID = 4326 } + ]) { SRID = 4326 }; + } + } + + public class GeometryCollectionTypeTest(GeometryCollectionTypeTest.GeometryCollectionTypeFixture fixture) + : GeographyTypeTestBase(fixture) + { + public class GeometryCollectionTypeFixture() : GeographyTypeFixture + { + public override GeometryCollection Value { get; } = new( + [ + new Point(-122.3500, 47.6200) { SRID = 4326 }, + new LineString([ + new Coordinate(-122.3500, 47.6200), + new Coordinate(-122.3450, 47.6150) + ]) { SRID = 4326 }, + new Polygon(new LinearRing([ + new Coordinate(-122.3480, 47.6190), // NW + new Coordinate(-122.3480, 47.6170), // SW + new Coordinate(-122.3460, 47.6170), // SE + new Coordinate(-122.3460, 47.6190), // NE + new Coordinate(-122.3480, 47.6190) + ])) { SRID = 4326 } + ]) { SRID = 4326 }; + + public override GeometryCollection OtherValue { get; } = new( + [ + new Point(-121.9000, 46.9500) { SRID = 4326 }, + new LineString([ + new Coordinate(-121.9000, 46.9500), + new Coordinate(-121.6000, 46.8200) + ]) { SRID = 4326 }, + new Polygon(new LinearRing([ + new Coordinate(-121.8800, 46.9400), // NW + new Coordinate(-121.8800, 46.9200), // SW + new Coordinate(-121.8600, 46.9200), // SE + new Coordinate(-121.8600, 46.9400), // NE + new Coordinate(-121.8800, 46.9400) + ])) { SRID = 4326 } + ]) { SRID = 4326 }; + } + } diff --git a/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerGeometryTypeTest.cs b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerGeometryTypeTest.cs new file mode 100644 index 00000000000..d6b4a463e0e --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerGeometryTypeTest.cs @@ -0,0 +1,243 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using NetTopologySuite.Geometries; + +namespace Microsoft.EntityFrameworkCore.Types.Geometry; + +public abstract class GeometryTypeTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) + : RelationalTypeTestBase(fixture, testOutputHelper) + where T : NetTopologySuite.Geometries.Geometry + where TFixture : GeometryTypeTestBase.GeometryTypeFixture +{ + // SQL Server doesn't support the equality operator on geometry, override to use EqualsTopologically + public override async Task Equality_in_query() + { + await using var context = Fixture.CreateContext(); + + var result = await context.Set().Where(e => e.Value.EqualsTopologically(Fixture.Value)).SingleAsync(); + + Assert.Equal(Fixture.Value, result.Value, Fixture.Comparer); + } + + public override async Task ExecuteUpdate_within_json_to_nonjson_column() + { + // See #36688 for supporting this for SQL Server types other than string/numeric/bool + var exception = await Assert.ThrowsAsync(() => base.ExecuteUpdate_within_json_to_nonjson_column()); + Assert.Equal(RelationalStrings.ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn, exception.Message); + } + + public abstract class GeometryTypeFixture : RelationalTypeTestFixture + { + public override string? StoreType => "geometry"; + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder).UseSqlServer(o => o.UseNetTopologySuite()); + + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; + } +} + +public class PointTypeTest(PointTypeTest.PointTypeFixture fixture, ITestOutputHelper testOutputHelper) + : GeometryTypeTestBase(fixture, testOutputHelper) +{ + public class PointTypeFixture() : GeometryTypeFixture + { + public override Point Value { get; } = new(-122.34877, 47.6233355) { SRID = 4326 }; + public override Point OtherValue { get; } = new(-121.7500, 46.2500) { SRID = 4326 }; + } +} + +public class LineStringTypeTest(LineStringTypeTest.LineStringTypeFixture fixture, ITestOutputHelper testOutputHelper) + : GeometryTypeTestBase(fixture, testOutputHelper) +{ + public class LineStringTypeFixture() : GeometryTypeFixture + { + public override LineString Value { get; } = new( + [ + new Coordinate(-122.34877, 47.6233355), + new Coordinate(-122.3308366, 47.5978429) + ]) { SRID = 4326 }; + + public override LineString OtherValue { get; } = new( + [ + new Coordinate(-120.5000, 46.9000), + new Coordinate(-119.8000, 46.7000), + new Coordinate(-118.6000, 46.4000) + ]) { SRID = 4326 }; + } +} + +public class PolygonTypeTest(PolygonTypeTest.PolygonTypeFixture fixture, ITestOutputHelper testOutputHelper) + : GeometryTypeTestBase(fixture, testOutputHelper) +{ + public class PolygonTypeFixture() : GeometryTypeFixture + { + public override Polygon Value { get; } = new( + new LinearRing( + [ + new Coordinate(-122.3500, 47.6200), // NW + new Coordinate(-122.3500, 47.6100), // SW + new Coordinate(-122.3400, 47.6100), // SE + new Coordinate(-122.3400, 47.6200), // NE + new Coordinate(-122.3500, 47.6200) + ])) { SRID = 4326 }; + + public override Polygon OtherValue { get; } = new( + new LinearRing( + [ + new Coordinate(-119.3000, 45.8800), // NW + new Coordinate(-119.3000, 45.8600), // SW + new Coordinate(-119.1500, 45.8600), // SE + new Coordinate(-119.1500, 45.8800), // NE + new Coordinate(-119.3000, 45.8800) + ])) { SRID = 4326 }; + } +} + +public class MultiPointTypeTest(MultiPointTypeTest.MultiPointTypeFixture fixture, ITestOutputHelper testOutputHelper) + : GeometryTypeTestBase(fixture, testOutputHelper) +{ + public class MultiPointTypeFixture() : GeometryTypeFixture + { + public override MultiPoint Value { get; } = new MultiPoint( + [ + new Point(-122.3500, 47.6200) { SRID = 4326 }, + new Point(-122.3450, 47.6150) { SRID = 4326 } + ]) { SRID = 4326 }; + + public override MultiPoint OtherValue { get; } = new MultiPoint( + [ + new Point(-121.9000, 46.9500) { SRID = 4326 }, + new Point(-121.5000, 46.6000) { SRID = 4326 }, + new Point(-121.2000, 46.3000) { SRID = 4326 } + ]) { SRID = 4326 }; + } +} + +public class MultiLineStringTypeTest(MultiLineStringTypeTest.MultiLineStringTypeFixture fixture, ITestOutputHelper testOutputHelper) + : GeometryTypeTestBase(fixture, testOutputHelper) +{ + public class MultiLineStringTypeFixture() : GeometryTypeFixture + { + public override MultiLineString Value { get; } = new MultiLineString( + [ + new LineString([ + new Coordinate(-122.3500, 47.6200), + new Coordinate(-122.3450, 47.6150) + ]) { SRID = 4326 }, + new LineString([ + new Coordinate(-122.3480, 47.6180), + new Coordinate(-122.3420, 47.6130) + ]) { SRID = 4326 } + ]) { SRID = 4326 }; + + public override MultiLineString OtherValue { get; } = new MultiLineString( + [ + new LineString([ + new Coordinate(-120.9000, 46.9500), + new Coordinate(-120.4000, 46.8200) + ]) { SRID = 4326 }, + new LineString([ + new Coordinate(-120.7000, 46.7800), + new Coordinate(-120.2000, 46.5500) + ]) { SRID = 4326 } + ]) { SRID = 4326 }; + } +} + +public class MultiPolygonTypeTest(MultiPolygonTypeTest.MultiPolygonTypeFixture fixture, ITestOutputHelper testOutputHelper) + : GeometryTypeTestBase(fixture, testOutputHelper) +{ + public class MultiPolygonTypeFixture() : GeometryTypeFixture + { + public override MultiPolygon Value { get; } = new MultiPolygon( + [ + new Polygon(new LinearRing([ + new Coordinate(-122.3500, 47.6200), // NW + new Coordinate(-122.3500, 47.6150), // SW + new Coordinate(-122.3450, 47.6150), // SE + new Coordinate(-122.3450, 47.6200), // NE + new Coordinate(-122.3500, 47.6200) + ])) { SRID = 4326 }, + new Polygon(new LinearRing([ + new Coordinate(-122.3400, 47.6240), // NW + new Coordinate(-122.3400, 47.6220), // SW + new Coordinate(-122.3380, 47.6220), // SE + new Coordinate(-122.3380, 47.6240), // NE + new Coordinate(-122.3400, 47.6240) + ])) { SRID = 4326 } + ]) { SRID = 4326 }; + + public override MultiPolygon OtherValue { get; } = new MultiPolygon( + [ + new Polygon(new LinearRing([ + new Coordinate(-119.8000, 45.9000), // NW + new Coordinate(-119.8000, 45.8800), // SW + new Coordinate(-119.6500, 45.8800), // SE + new Coordinate(-119.6500, 45.9000), // NE + new Coordinate(-119.8000, 45.9000) + ])) { SRID = 4326 }, + new Polygon(new LinearRing([ + new Coordinate(-119.6000, 45.8950), // NW + new Coordinate(-119.6000, 45.8850), // SW + new Coordinate(-119.5800, 45.8850), // SE + new Coordinate(-119.5800, 45.8950), // NE + new Coordinate(-119.6000, 45.8950) + ])) { SRID = 4326 } + ]) { SRID = 4326 }; + } +} + +public class GeometryCollectionTypeTest(GeometryCollectionTypeTest.GeometryCollectionTypeFixture fixture, ITestOutputHelper testOutputHelper) + : GeometryTypeTestBase(fixture, testOutputHelper) +{ + public override async Task ExecuteUpdate_within_json_to_constant() + { + await base.ExecuteUpdate_within_json_to_constant(); + + AssertSql( + """ +UPDATE [j] +SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', N'GEOMETRYCOLLECTION (POINT (-120.9 46.95), LINESTRING (-120.9 46.95, -120.4 46.82), POLYGON ((-120.8 46.94, -120.8 46.92, -120.78 46.92, -120.78 46.94, -120.8 46.94)))') +FROM [JsonTypeEntity] AS [j] +"""); + } + + public class GeometryCollectionTypeFixture() : GeometryTypeFixture + { + public override GeometryCollection Value { get; } = new GeometryCollection( + [ + new Point(-122.3500, 47.6200) { SRID = 4326 }, + new LineString([ + new Coordinate(-122.3500, 47.6200), + new Coordinate(-122.3450, 47.6150) + ]) { SRID = 4326 }, + new Polygon(new LinearRing([ + new Coordinate(-122.3480, 47.6190), // NW + new Coordinate(-122.3480, 47.6170), // SW + new Coordinate(-122.3460, 47.6170), // SE + new Coordinate(-122.3460, 47.6190), // NE + new Coordinate(-122.3480, 47.6190) + ])) { SRID = 4326 } + ]) + { SRID = 4326 }; + + public override GeometryCollection OtherValue { get; } = new GeometryCollection( + [ + new Point(-120.9000, 46.9500) { SRID = 4326 }, + new LineString([ + new Coordinate(-120.9000, 46.9500), + new Coordinate(-120.4000, 46.8200) + ]) { SRID = 4326 }, + new Polygon(new LinearRing([ + new Coordinate(-120.8000, 46.9400), // NW + new Coordinate(-120.8000, 46.9200), // SW + new Coordinate(-120.7800, 46.9200), // SE + new Coordinate(-120.7800, 46.9400), // NE + new Coordinate(-120.8000, 46.9400) + ])) { SRID = 4326 } + ]) + { SRID = 4326 }; + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerMiscellaneousTypeTest.cs b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerMiscellaneousTypeTest.cs index 3127839e0b2..e78701dead3 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerMiscellaneousTypeTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerMiscellaneousTypeTest.cs @@ -1,13 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore.Types; +namespace Microsoft.EntityFrameworkCore.Types.Miscellaneous; public class BoolTypeTest(BoolTypeTest.BoolTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class BoolTypeFixture() : RelationalTypeTestFixture(true, false) + public class BoolTypeFixture : RelationalTypeTestFixture { + public override bool Value { get; } = true; + public override bool OtherValue { get; } = false; + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; } } @@ -15,8 +18,11 @@ public class BoolTypeFixture() : RelationalTypeTestFixture(true, false) public class StringTypeTest(StringTypeTest.StringTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class StringTypeFixture() : RelationalTypeTestFixture("foo", "bar") + public class StringTypeFixture : RelationalTypeTestFixture { + public override string Value { get; } = "foo"; + public override string OtherValue { get; } = "bar"; + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; } } @@ -31,10 +37,11 @@ public override async Task ExecuteUpdate_within_json_to_nonjson_column() Assert.Equal(RelationalStrings.ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn, exception.Message); } - public class GuidTypeFixture() : RelationalTypeTestFixture( - new Guid("8f7331d6-cde9-44fb-8611-81fff686f280"), - new Guid("ae192c36-9004-49b2-b785-8be10d169627")) + public class GuidTypeFixture : RelationalTypeTestFixture { + public override Guid Value { get; } = new("8f7331d6-cde9-44fb-8611-81fff686f280"); + public override Guid OtherValue { get; } = new("ae192c36-9004-49b2-b785-8be10d169627"); + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; } } @@ -49,10 +56,13 @@ public override async Task ExecuteUpdate_within_json_to_nonjson_column() Assert.Equal(RelationalStrings.ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn, exception.Message); } - public class ByteArrayTypeFixture() : RelationalTypeTestFixture([1, 2, 3], [4, 5, 6]) + public class ByteArrayTypeFixture() : RelationalTypeTestFixture { - protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; + public override byte[] Value { get; } = [1, 2, 3]; + public override byte[] OtherValue { get; } = [4, 5, 6, 7]; public override Func Comparer { get; } = (a, b) => a.SequenceEqual(b); + + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerNumericTypeTest.cs b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerNumericTypeTest.cs index 62e0be9ddbc..cace1fd6d13 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerNumericTypeTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerNumericTypeTest.cs @@ -1,44 +1,59 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore.Types; +namespace Microsoft.EntityFrameworkCore.Types.Numeric; public class ByteTypeTest(ByteTypeTest.ByteTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class ByteTypeFixture() : RelationalTypeTestFixture(byte.MinValue, byte.MaxValue) + public class ByteTypeFixture : RelationalTypeTestFixture { + public override byte Value { get; } = byte.MinValue; + public override byte OtherValue { get; } = byte.MaxValue; + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; } } public class ShortTypeTest(ShortTypeTest.ShortTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class ShortTypeFixture() : RelationalTypeTestFixture(short.MinValue, short.MaxValue) + public class ShortTypeFixture : RelationalTypeTestFixture { + public override short Value { get; } = short.MinValue; + public override short OtherValue { get; } = short.MaxValue; + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; } } public class IntTypeTest(IntTypeTest.IntTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class IntTypeFixture() : RelationalTypeTestFixture(int.MinValue, int.MaxValue) + public class IntTypeFixture : RelationalTypeTestFixture { + public override int Value { get; } = int.MinValue; + public override int OtherValue { get; } = int.MaxValue; + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; } } public class LongTypeTest(LongTypeTest.LongTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class LongTypeFixture() : RelationalTypeTestFixture(long.MinValue, long.MaxValue) + public class LongTypeFixture : RelationalTypeTestFixture { + public override long Value { get; } = long.MinValue; + public override long OtherValue { get; } = long.MaxValue; + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; } } public class DecimalTypeTest(DecimalTypeTest.DecimalTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class DecimalTypeFixture() : RelationalTypeTestFixture(30.5m, 30m) + public class DecimalTypeFixture : RelationalTypeTestFixture { + public override decimal Value { get; } = 30.5m; + public override decimal OtherValue { get; } = 30m; + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) @@ -49,16 +64,22 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build public class DoubleTypeTest(DoubleTypeTest.DoubleTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class DoubleTypeFixture() : RelationalTypeTestFixture(30.5d, 30d) + public class DoubleTypeFixture : RelationalTypeTestFixture { + public override double Value { get; } = 30.5d; + public override double OtherValue { get; } = 30d; + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; } } public class FloatTypeTest(FloatTypeTest.FloatTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class FloatTypeFixture() : RelationalTypeTestFixture(30.5f, 30f) + public class FloatTypeFixture : RelationalTypeTestFixture { + public override float Value { get; } = 30.5f; + public override float OtherValue { get; } = 30f; + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerTemporalTypeTest.cs b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerTemporalTypeTest.cs index 8face594837..e9fde6eb679 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerTemporalTypeTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerTemporalTypeTest.cs @@ -1,18 +1,48 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore.Types; +namespace Microsoft.EntityFrameworkCore.Types.Temporal; -public class DateTimeTypeTest(DateTimeTypeTest.DateTimeTypeFixture fixture) - : RelationalTypeTestBase(fixture) +public class DateTimeTypeTest(DateTimeTypeTest.DateTimeTypeFixture fixture, ITestOutputHelper testOutputHelper) + : RelationalTypeTestBase(fixture, testOutputHelper) { - public class DateTimeTypeFixture() : RelationalTypeTestFixture( - new DateTime(2020, 1, 5, 12, 30, 45, DateTimeKind.Unspecified), - new DateTime(2022, 5, 3, 0, 0, 0, DateTimeKind.Unspecified)) + public class DateTimeTypeFixture : RelationalTypeTestFixture { + public override DateTime Value { get; } = new DateTime(2020, 1, 5, 12, 30, 45, DateTimeKind.Unspecified); + public override DateTime OtherValue { get; } = new DateTime(2022, 5, 3, 0, 0, 0, DateTimeKind.Unspecified); + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; } + public override async Task SaveChanges_within_json() + { + await base.SaveChanges_within_json(); + + AssertSql( + """ +@p0='{"OtherValue":"2022-05-03T00:00:00","Value":"2022-05-03T00:00:00"}' (Nullable = false) (Size = 66) +@p1='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonTypeEntity] SET [JsonContainer] = @p0 +OUTPUT 1 +WHERE [Id] = @p1; +"""); + } + + public override async Task ExecuteUpdate_within_json_to_constant() + { + await base.ExecuteUpdate_within_json_to_constant(); + + AssertSql( + """ +UPDATE [j] +SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', N'2022-05-03T00:00:00') +FROM [JsonTypeEntity] AS [j] +"""); + } + public override async Task ExecuteUpdate_within_json_to_nonjson_column() { // See #36688 for supporting this for SQL Server types other than string/numeric/bool @@ -21,9 +51,38 @@ public override async Task ExecuteUpdate_within_json_to_nonjson_column() } } -public class DateTimeOffsetTypeTest(DateTimeOffsetTypeTest.DateTimeOffsetTypeFixture fixture) - : RelationalTypeTestBase(fixture) +public class DateTimeOffsetTypeTest(DateTimeOffsetTypeTest.DateTimeOffsetTypeFixture fixture, ITestOutputHelper testOutputHelper) + : RelationalTypeTestBase(fixture, testOutputHelper) { + public override async Task SaveChanges_within_json() + { + await base.SaveChanges_within_json(); + + AssertSql( + """ +@p0='{"OtherValue":"2020-01-05T12:30:45+03:00","Value":"2020-01-05T12:30:45+03:00"}' (Nullable = false) (Size = 78) +@p1='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonTypeEntity] SET [JsonContainer] = @p0 +OUTPUT 1 +WHERE [Id] = @p1; +"""); + } + + public override async Task ExecuteUpdate_within_json_to_constant() + { + await base.ExecuteUpdate_within_json_to_constant(); + + AssertSql( + """ +UPDATE [j] +SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', N'2020-01-05T12:30:45+03:00') +FROM [JsonTypeEntity] AS [j] +"""); + } + public override async Task ExecuteUpdate_within_json_to_nonjson_column() { // See #36688 for supporting this for SQL Server types other than string/numeric/bool @@ -31,16 +90,47 @@ public override async Task ExecuteUpdate_within_json_to_nonjson_column() Assert.Equal(RelationalStrings.ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn, exception.Message); } - public class DateTimeOffsetTypeFixture() : RelationalTypeTestFixture( - new DateTimeOffset(2020, 1, 5, 12, 30, 45, TimeSpan.FromHours(2)), - new DateTimeOffset(2020, 1, 5, 12, 30, 45, TimeSpan.FromHours(3))) + public class DateTimeOffsetTypeFixture : RelationalTypeTestFixture { + public override DateTimeOffset Value { get; } = new DateTimeOffset(2020, 1, 5, 12, 30, 45, TimeSpan.FromHours(2)); + public override DateTimeOffset OtherValue { get; } = new DateTimeOffset(2020, 1, 5, 12, 30, 45, TimeSpan.FromHours(3)); + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; } } -public class DateOnlyTypeTest(DateOnlyTypeTest.DateTypeFixture fixture) : RelationalTypeTestBase(fixture) +public class DateOnlyTypeTest(DateOnlyTypeTest.DateTypeFixture fixture, ITestOutputHelper testOutputHelper) + : RelationalTypeTestBase(fixture, testOutputHelper) { + public override async Task SaveChanges_within_json() + { + await base.SaveChanges_within_json(); + + AssertSql( + """ +@p0='{"OtherValue":"2022-05-03","Value":"2022-05-03"}' (Nullable = false) (Size = 48) +@p1='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonTypeEntity] SET [JsonContainer] = @p0 +OUTPUT 1 +WHERE [Id] = @p1; +"""); + } + + public override async Task ExecuteUpdate_within_json_to_constant() + { + await base.ExecuteUpdate_within_json_to_constant(); + + AssertSql( + """ +UPDATE [j] +SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', N'2022-05-03') +FROM [JsonTypeEntity] AS [j] +"""); + } + public override async Task ExecuteUpdate_within_json_to_nonjson_column() { // See #36688 for supporting this for SQL Server types other than string/numeric/bool @@ -48,17 +138,47 @@ public override async Task ExecuteUpdate_within_json_to_nonjson_column() Assert.Equal(RelationalStrings.ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn, exception.Message); } - public class DateTypeFixture() : RelationalTypeTestFixture( - new DateOnly(2020, 1, 5), - new DateOnly(2022, 5, 3)) + public class DateTypeFixture : RelationalTypeTestFixture { + public override DateOnly Value { get; } = new DateOnly(2020, 1, 5); + public override DateOnly OtherValue { get; } = new DateOnly(2022, 5, 3); + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; } } -public class TimeOnlyTypeTest(TimeOnlyTypeTest.TimeTypeFixture fixture) - : RelationalTypeTestBase(fixture) +public class TimeOnlyTypeTest(TimeOnlyTypeTest.TimeTypeFixture fixture, ITestOutputHelper testOutputHelper) + : RelationalTypeTestBase(fixture, testOutputHelper) { + public override async Task SaveChanges_within_json() + { + await base.SaveChanges_within_json(); + + AssertSql( + """ +@p0='{"OtherValue":"14:00:00.0000000","Value":"14:00:00.0000000"}' (Nullable = false) (Size = 60) +@p1='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonTypeEntity] SET [JsonContainer] = @p0 +OUTPUT 1 +WHERE [Id] = @p1; +"""); + } + + public override async Task ExecuteUpdate_within_json_to_constant() + { + await base.ExecuteUpdate_within_json_to_constant(); + + AssertSql( + """ +UPDATE [j] +SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', N'14:00:00.0000000') +FROM [JsonTypeEntity] AS [j] +"""); + } + public override async Task ExecuteUpdate_within_json_to_nonjson_column() { // See #36688 for supporting this for SQL Server types other than string/numeric/bool @@ -66,16 +186,47 @@ public override async Task ExecuteUpdate_within_json_to_nonjson_column() Assert.Equal(RelationalStrings.ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn, exception.Message); } - public class TimeTypeFixture() : RelationalTypeTestFixture( - new TimeOnly(12, 30, 45), - new TimeOnly(14, 0, 0)) + public class TimeTypeFixture : RelationalTypeTestFixture { + public override TimeOnly Value { get; } = new TimeOnly(12, 30, 45); + public override TimeOnly OtherValue { get; } = new TimeOnly(14, 0, 0); + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; } } -public class TimeSpanTypeTest(TimeSpanTypeTest.TimeSpanTypeFixture fixture) : RelationalTypeTestBase(fixture) +public class TimeSpanTypeTest(TimeSpanTypeTest.TimeSpanTypeFixture fixture, ITestOutputHelper testOutputHelper) + : RelationalTypeTestBase(fixture, testOutputHelper) { + public override async Task SaveChanges_within_json() + { + await base.SaveChanges_within_json(); + + AssertSql( + """ +@p0='{"OtherValue":"14:00:00","Value":"14:00:00"}' (Nullable = false) (Size = 44) +@p1='1' + +SET IMPLICIT_TRANSACTIONS OFF; +SET NOCOUNT ON; +UPDATE [JsonTypeEntity] SET [JsonContainer] = @p0 +OUTPUT 1 +WHERE [Id] = @p1; +"""); + } + + public override async Task ExecuteUpdate_within_json_to_constant() + { + await base.ExecuteUpdate_within_json_to_constant(); + + AssertSql( + """ +UPDATE [j] +SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', N'14:00:00') +FROM [JsonTypeEntity] AS [j] +"""); + } + public override async Task ExecuteUpdate_within_json_to_nonjson_column() { // See #36688 for supporting this for SQL Server types other than string/numeric/bool @@ -83,10 +234,11 @@ public override async Task ExecuteUpdate_within_json_to_nonjson_column() Assert.Equal(RelationalStrings.ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn, exception.Message); } - public class TimeSpanTypeFixture() : RelationalTypeTestFixture( - new TimeSpan(12, 30, 45), - new TimeSpan(14, 0, 0)) + public class TimeSpanTypeFixture : RelationalTypeTestFixture { + public override TimeSpan Value { get; } = new TimeSpan(12, 30, 45); + public override TimeSpan OtherValue { get; } = new TimeSpan(14, 0, 0); + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; } } diff --git a/test/EFCore.Sqlite.FunctionalTests/Types/SqliteMiscellaneousTypeTest.cs b/test/EFCore.Sqlite.FunctionalTests/Types/SqliteMiscellaneousTypeTest.cs index a62d2bc8a15..c33a42acd12 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Types/SqliteMiscellaneousTypeTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Types/SqliteMiscellaneousTypeTest.cs @@ -1,13 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore.Types; +namespace Microsoft.EntityFrameworkCore.Types.Miscellaneous; public class BoolTypeTest(BoolTypeTest.BoolTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class BoolTypeFixture() : RelationalTypeTestFixture(true, false) + public class BoolTypeFixture : RelationalTypeTestFixture { + public override bool Value { get; } = true; + public override bool OtherValue { get; } = false; + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; } } @@ -15,8 +18,11 @@ public class BoolTypeFixture() : RelationalTypeTestFixture(true, false) public class StringTypeTest(StringTypeTest.StringTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class StringTypeFixture() : RelationalTypeTestFixture("foo", "bar") + public class StringTypeFixture : RelationalTypeTestFixture { + public override string Value { get; } = "foo"; + public override string OtherValue { get; } = "bar"; + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; } } @@ -31,10 +37,11 @@ public override async Task ExecuteUpdate_within_json_to_nonjson_column() Assert.Equal(RelationalStrings.ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn, exception.Message); } - public class GuidTypeFixture() : RelationalTypeTestFixture( - new Guid("8f7331d6-cde9-44fb-8611-81fff686f280"), - new Guid("ae192c36-9004-49b2-b785-8be10d169627")) + public class GuidTypeFixture : RelationalTypeTestFixture { + public override Guid Value { get; } = new("8f7331d6-cde9-44fb-8611-81fff686f280"); + public override Guid OtherValue { get; } = new("ae192c36-9004-49b2-b785-8be10d169627"); + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; } } @@ -49,10 +56,13 @@ public override async Task ExecuteUpdate_within_json_to_nonjson_column() Assert.Equal(RelationalStrings.ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn, exception.Message); } - public class ByteArrayTypeFixture() : RelationalTypeTestFixture([1, 2, 3], [4, 5, 6]) + public class ByteArrayTypeFixture : RelationalTypeTestFixture { - protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; + public override byte[] Value { get; } = [1, 2, 3]; + public override byte[] OtherValue { get; } = [4, 5, 6, 7]; public override Func Comparer { get; } = (a, b) => a.SequenceEqual(b); + + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; } } diff --git a/test/EFCore.Sqlite.FunctionalTests/Types/SqliteNumericTypeTest.cs b/test/EFCore.Sqlite.FunctionalTests/Types/SqliteNumericTypeTest.cs index 28e54613bb7..36a83927998 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Types/SqliteNumericTypeTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Types/SqliteNumericTypeTest.cs @@ -1,60 +1,81 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore.Types; +namespace Microsoft.EntityFrameworkCore.Types.Numeric; public class ByteTypeTest(ByteTypeTest.ByteTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class ByteTypeFixture() : RelationalTypeTestFixture(byte.MinValue, byte.MaxValue) + public class ByteTypeFixture : RelationalTypeTestFixture { + public override byte Value { get; } = byte.MinValue; + public override byte OtherValue { get; } = byte.MaxValue; + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; } } public class ShortTypeTest(ShortTypeTest.ShortTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class ShortTypeFixture() : RelationalTypeTestFixture(short.MinValue, short.MaxValue) + public class ShortTypeFixture : RelationalTypeTestFixture { + public override short Value { get; } = short.MinValue; + public override short OtherValue { get; } = short.MaxValue; + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; } } public class IntTypeTest(IntTypeTest.IntTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class IntTypeFixture() : RelationalTypeTestFixture(int.MinValue, int.MaxValue) + public class IntTypeFixture : RelationalTypeTestFixture { + public override int Value { get; } = int.MinValue; + public override int OtherValue { get; } = int.MaxValue; + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; } } public class LongTypeTest(LongTypeTest.LongTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class LongTypeFixture() : RelationalTypeTestFixture(long.MinValue, long.MaxValue) + public class LongTypeFixture : RelationalTypeTestFixture { + public override long Value { get; } = long.MinValue; + public override long OtherValue { get; } = long.MaxValue; + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; } } public class DecimalTypeTest(DecimalTypeTest.DecimalTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class DecimalTypeFixture() : RelationalTypeTestFixture(30.5m, 30m) + public class DecimalTypeFixture : RelationalTypeTestFixture { + public override decimal Value { get; } = 30.5m; + public override decimal OtherValue { get; } = 30m; + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; } } public class DoubleTypeTest(DoubleTypeTest.DoubleTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class DoubleTypeFixture() : RelationalTypeTestFixture(30.5d, 30d) + public class DoubleTypeFixture : RelationalTypeTestFixture { + public override double Value { get; } = 30.5d; + public override double OtherValue { get; } = 30d; + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; } } public class FloatTypeTest(FloatTypeTest.FloatTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class FloatTypeFixture() : RelationalTypeTestFixture(30.5f, 30f) + public class FloatTypeFixture : RelationalTypeTestFixture { + public override float Value { get; } = 30.5f; + public override float OtherValue { get; } = 30f; + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; } } diff --git a/test/EFCore.Sqlite.FunctionalTests/Types/SqliteTemporalTypeTest.cs b/test/EFCore.Sqlite.FunctionalTests/Types/SqliteTemporalTypeTest.cs index 57f13e7a45b..80e0b361aed 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Types/SqliteTemporalTypeTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Types/SqliteTemporalTypeTest.cs @@ -1,15 +1,16 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -namespace Microsoft.EntityFrameworkCore.Types; +namespace Microsoft.EntityFrameworkCore.Types.Temporal; public class DateTimeTypeTest(DateTimeTypeTest.DateTimeTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class DateTimeTypeFixture() : RelationalTypeTestFixture( - new DateTime(2020, 1, 5, 12, 30, 45, DateTimeKind.Unspecified), - new DateTime(2022, 5, 3, 0, 0, 0, DateTimeKind.Unspecified)) + public class DateTimeTypeFixture : RelationalTypeTestFixture { + public override DateTime Value { get; } = new DateTime(2020, 1, 5, 12, 30, 45, DateTimeKind.Unspecified); + public override DateTime OtherValue { get; } = new DateTime(2022, 5, 3, 0, 0, 0, DateTimeKind.Unspecified); + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; } @@ -31,10 +32,11 @@ public override async Task ExecuteUpdate_within_json_to_nonjson_column() Assert.Equal(RelationalStrings.ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn, exception.Message); } - public class DateTimeOffsetTypeFixture() : RelationalTypeTestFixture( - new DateTimeOffset(2020, 1, 5, 12, 30, 45, TimeSpan.FromHours(2)), - new DateTimeOffset(2020, 1, 5, 12, 30, 45, TimeSpan.FromHours(3))) + public class DateTimeOffsetTypeFixture : RelationalTypeTestFixture { + public override DateTimeOffset Value { get; } = new DateTimeOffset(2020, 1, 5, 12, 30, 45, TimeSpan.FromHours(2)); + public override DateTimeOffset OtherValue { get; } = new DateTimeOffset(2020, 1, 5, 12, 30, 45, TimeSpan.FromHours(3)); + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; } } @@ -48,10 +50,11 @@ public override async Task ExecuteUpdate_within_json_to_nonjson_column() Assert.Equal(RelationalStrings.ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn, exception.Message); } - public class DateTypeFixture() : RelationalTypeTestFixture( - new DateOnly(2020, 1, 5), - new DateOnly(2022, 5, 3)) + public class DateTypeFixture : RelationalTypeTestFixture { + public override DateOnly Value { get; } = new DateOnly(2020, 1, 5); + public override DateOnly OtherValue { get; } = new DateOnly(2022, 5, 3); + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; } } @@ -66,10 +69,11 @@ public override async Task ExecuteUpdate_within_json_to_nonjson_column() Assert.Equal(RelationalStrings.ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn, exception.Message); } - public class TimeTypeFixture() : RelationalTypeTestFixture( - new TimeOnly(12, 30, 45), - new TimeOnly(14, 0, 0)) + public class TimeTypeFixture : RelationalTypeTestFixture { + public override TimeOnly Value { get; } = new TimeOnly(12, 30, 45); + public override TimeOnly OtherValue { get; } = new TimeOnly(14, 0, 0); + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; } } @@ -83,10 +87,11 @@ public override async Task ExecuteUpdate_within_json_to_nonjson_column() Assert.Equal(RelationalStrings.ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn, exception.Message); } - public class TimeSpanTypeFixture() : RelationalTypeTestFixture( - new TimeSpan(12, 30, 45), - new TimeSpan(14, 0, 0)) + public class TimeSpanTypeFixture : RelationalTypeTestFixture { + public override TimeSpan Value { get; } = new TimeSpan(12, 30, 45); + public override TimeSpan OtherValue { get; } = new TimeSpan(14, 0, 0); + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; } }