diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs index f3843a9dbcb..21893c72895 100644 --- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs +++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs @@ -861,6 +861,7 @@ static SqlExpression ProcessJsonQuery(JsonQueryExpression jsonQuery) /// update function. /// /// A scalar expression ready to be integrated into an UPDATE statement setter. + [Experimental(EFDiagnostics.ProviderExperimentalApi)] // TODO: This should probably move into the type mappings, #36729 protected virtual bool TrySerializeScalarToJson( JsonScalarExpression target, SqlExpression value, diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/ISqlServerSingletonOptions.cs b/src/EFCore.SqlServer/Infrastructure/Internal/ISqlServerSingletonOptions.cs index 1f96dec0ebf..677a0d3d17b 100644 --- a/src/EFCore.SqlServer/Infrastructure/Internal/ISqlServerSingletonOptions.cs +++ b/src/EFCore.SqlServer/Infrastructure/Internal/ISqlServerSingletonOptions.cs @@ -51,6 +51,18 @@ public interface ISqlServerSingletonOptions : ISingletonOptions /// public bool SupportsJsonFunctions { get; } + /// + /// Whether the JSON_OBJECT() and JSON_ARRAY() functions are supported by the targeted + /// SQL Server engine. + /// + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public bool SupportsJsonObjectArray { get; } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerSingletonOptions.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerSingletonOptions.cs index 98c5f8727ee..dc3f3736e3c 100644 --- a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerSingletonOptions.cs +++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerSingletonOptions.cs @@ -60,6 +60,22 @@ public virtual bool SupportsJsonFunctions _ => throw new UnreachableException() }; + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool SupportsJsonObjectArray + => EngineType switch + { + SqlServerEngineType.SqlServer => SqlServerCompatibilityLevel >= 160, + SqlServerEngineType.AzureSql => AzureSqlCompatibilityLevel >= 160, + SqlServerEngineType.AzureSynapse => false, + SqlServerEngineType.Unknown => false, // TODO: We shouldn't observe Unknown here, #36477 + _ => throw new UnreachableException() + }; + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs index e46b6abdb81..9851615076b 100644 --- a/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs +++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs @@ -155,6 +155,12 @@ public static string DuplicateKeyMismatchedClustering(object? key1, object? enti GetString("DuplicateKeyMismatchedClustering", nameof(key1), nameof(entityType1), nameof(key2), nameof(entityType2), nameof(table), nameof(keyName)), key1, entityType1, key2, entityType2, table, keyName); + /// + /// 'ExecuteUpdate' cannot set a property in a JSON column to an expression containing a column on SQL Server versions before 2022. If you're on SQL Server 2022 and above, your compatibility level may be set to a lower value; consider raising it. + /// + public static string ExecuteUpdateCannotSetJsonPropertyOnOldSqlServer + => GetString("ExecuteUpdateCannotSetJsonPropertyOnOldSqlServer"); + /// /// Identity value generation cannot be used for the property '{property}' on entity type '{entityType}' because the property type is '{propertyType}'. Identity value generation can only be used with signed integer properties. /// diff --git a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx index 79128716e20..6a92672c5c0 100644 --- a/src/EFCore.SqlServer/Properties/SqlServerStrings.resx +++ b/src/EFCore.SqlServer/Properties/SqlServerStrings.resx @@ -168,6 +168,9 @@ The keys {key1} on '{entityType1}' and {key2} on '{entityType2}' are both mapped to '{table}.{keyName}', but have different clustering configurations. + + 'ExecuteUpdate' cannot set a property in a JSON column to an expression containing a column on SQL Server versions before 2022. If you're on SQL Server 2022 and above, your compatibility level may be set to a lower value; consider raising it. + Identity value generation cannot be used for the property '{property}' on entity type '{entityType}' because the property type is '{propertyType}'. Identity value generation can only be used with signed integer properties. diff --git a/src/EFCore.SqlServer/Query/Internal/SqlExpressions/SqlServerJsonObjectExpression.cs b/src/EFCore.SqlServer/Query/Internal/SqlExpressions/SqlServerJsonObjectExpression.cs new file mode 100644 index 00000000000..0a64bde7c95 --- /dev/null +++ b/src/EFCore.SqlServer/Query/Internal/SqlExpressions/SqlServerJsonObjectExpression.cs @@ -0,0 +1,123 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.EntityFrameworkCore.Query.SqlExpressions; +using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; + +namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal.SqlExpressions; + +/// +/// An expression that represents a SQL Server JSON_OBJECT() function call in a SQL tree. +/// +/// +/// +/// See JSON_OBJECT (Transact-SQL) +/// for more information and examples. +/// +/// +/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to +/// the same compatibility standards as public APIs. It may be changed or removed without notice in +/// any release. You should only use it directly in your code with extreme caution and knowing that +/// doing so can result in application failures when updating to a new Entity Framework Core release. +/// +/// +public sealed class SqlServerJsonObjectExpression : SqlFunctionExpression +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public SqlServerJsonObjectExpression( + IReadOnlyList propertyNames, + IReadOnlyList propertyValues, + RelationalTypeMapping typeMapping) + : base( + "JSON_OBJECT", + arguments: propertyValues, + nullable: false, + argumentsPropagateNullability: Enumerable.Repeat(false, propertyValues.Count).ToList(), + typeof(string), + typeMapping) + { + if (propertyNames.Count != propertyValues.Count) + { + throw new ArgumentException("The number of property names must match the number of property values."); + } + + if (!typeMapping.StoreType.Equals("nvarchar(max)", StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException("Invalid type mapping for JSON_OBJECT."); + } + + PropertyNames = propertyNames; + } + + private static ConstructorInfo? _quotingConstructor; + + /// + /// The JSON properties the object consists of. + /// + public IReadOnlyList PropertyNames { get; } + + /// + public override Expression Quote() + => New( + _quotingConstructor ??= typeof(SqlServerJsonObjectExpression).GetConstructor( + [ + typeof(IReadOnlyList), + typeof(IReadOnlyList), + typeof(SqlServerStringTypeMapping), + ])!, + NewArrayInit(typeof(string), initializers: PropertyNames.Select(Constant)), + Arguments is null + ? Constant(null, typeof(IEnumerable)) + : NewArrayInit(typeof(SqlExpression), initializers: Arguments.Select(a => a.Quote())), + RelationalExpressionQuotingUtilities.QuoteTypeMapping(TypeMapping)); + + /// + protected override void Print(ExpressionPrinter expressionPrinter) + { + expressionPrinter.Append("JSON_OBJECT("); + + for (var i = 0; i < PropertyNames.Count; i++) + { + var name = PropertyNames[i]; + var value = Arguments![i]; + if (i > 0) + { + expressionPrinter.Append(", "); + } + + expressionPrinter.Append(name).Append(": "); + expressionPrinter.Visit(value); + } + + expressionPrinter.Append(")"); + } + + /// + public override bool Equals(object? obj) + => obj != null + && (ReferenceEquals(this, obj) + || obj is SqlServerJsonObjectExpression other + && Equals(other)); + + private bool Equals(SqlServerJsonObjectExpression other) + => base.Equals(other) && PropertyNames.SequenceEqual(other.PropertyNames); + + /// + public override int GetHashCode() + { + var hashCode = new HashCode(); + hashCode.Add(base.GetHashCode()); + + foreach (var name in PropertyNames) + { + hashCode.Add(name); + } + + return hashCode.ToHashCode(); + } +} diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs index d4790b81704..edbd7b8d778 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQuerySqlGenerator.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal.SqlExpressions; using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; @@ -292,6 +293,26 @@ when string.Equals(sqlFunctionExpression.Name, "COALESCE", StringComparison.Ordi return base.VisitSqlFunction(sqlFunctionExpression); } + case SqlServerJsonObjectExpression jsonObject: + { + Sql.Append("JSON_OBJECT("); + + for (var i = 0; i < jsonObject.PropertyNames.Count; i++) + { + if (i > 0) + { + Sql.Append(", "); + } + + Sql.Append("'").Append(jsonObject.PropertyNames[i]).Append("': "); + Visit(jsonObject.Arguments![i]); + } + + Sql.Append(")"); + + return sqlFunctionExpression; + } + // SQL Server 2025 modify method (https://learn.microsoft.com/sql/t-sql/data-types/json-data-type#modify-method) // We get here only from within UPDATE setters. // We generate the syntax here manually rather than just using the regular function visitation logic since diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs index d4c634aea00..71805ab0516 100644 --- a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs @@ -6,6 +6,8 @@ using Microsoft.EntityFrameworkCore.SqlServer.Infrastructure.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Internal; using Microsoft.EntityFrameworkCore.SqlServer.Metadata.Internal; +using Microsoft.EntityFrameworkCore.SqlServer.Query.Internal.SqlExpressions; +using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal; using Microsoft.VisualBasic; namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal; @@ -570,6 +572,64 @@ protected override bool TryTranslateSetters( } #pragma warning restore EF1001 // Internal EF Core API usage. + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + protected override bool TrySerializeScalarToJson( + JsonScalarExpression target, + SqlExpression value, + [NotNullWhen(true)] out SqlExpression? jsonValue) + { +#pragma warning disable EF9002 // TrySerializeScalarToJson is experimental + // The base implementation handles the types natively supported in JSON (int, string, bool), as well + // as constants/parameters. + if (base.TrySerializeScalarToJson(target, value, out jsonValue)) + { + return true; + } +#pragma warning restore EF9002 + + // geometry/geography are "user-defined types" and therefore not supported by JSON_OBJECT(), which we + // use below for serializing arbitrary relational expressions to JSON. Special-case them and serialize + // as WKT. + if (value.TypeMapping?.StoreType is "geometry" or "geography") + { + jsonValue = _sqlExpressionFactory.Function( + instance: value, + "STAsText", + arguments: [], + nullable: true, + instancePropagatesNullability: true, + argumentsPropagateNullability: [], + typeof(string), + _typeMappingSource.FindMapping("nvarchar(max)")); + return true; + } + + // We have some arbitrary relational expression that isn't an int/string/bool; it needs to be converted + // to JSON. Do this by generating JSON_VALUE(JSON_OBJECT('v': foo), '$.v') (supported since SQL Server 2022) + if (_sqlServerSingletonOptions.SupportsJsonObjectArray) + { + jsonValue = new JsonScalarExpression( + new SqlServerJsonObjectExpression( + propertyNames: ["v"], + propertyValues: [value], + SqlServerStructuralJsonTypeMapping.NvarcharMaxDefault), + [new("v")], + typeof(string), + _typeMappingSource.FindMapping("nvarchar(max)"), + nullable: value is ColumnExpression column ? column.IsNullable : true); + return true; + } + else + { + throw new InvalidOperationException(SqlServerStrings.ExecuteUpdateCannotSetJsonPropertyOnOldSqlServer); + } + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs index 89ee3a15224..a0bb693f889 100644 --- a/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Sqlite.Core/Query/Internal/SqliteQueryableMethodTranslatingExpressionVisitor.cs @@ -635,7 +635,9 @@ protected override bool TrySerializeScalarToJson( throw new InvalidOperationException(SqliteStrings.ExecuteUpdateJsonPartialUpdateDoesNotSupportUlong); } +#pragma warning disable EF9002 // TrySerializeScalarToJson is experimental return base.TrySerializeScalarToJson(target, value, out jsonValue); +#pragma warning restore EF9002 } /// diff --git a/test/EFCore.Cosmos.FunctionalTests/Types/CosmosMiscellaneousTypeTest.cs b/test/EFCore.Cosmos.FunctionalTests/Types/CosmosMiscellaneousTypeTest.cs index 9166b5e7461..49734c8f4c6 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Types/CosmosMiscellaneousTypeTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Types/CosmosMiscellaneousTypeTest.cs @@ -6,61 +6,49 @@ namespace Microsoft.EntityFrameworkCore.Types.Miscellaneous; public class BoolTypeTest(BoolTypeTest.BoolTypeFixture fixture) : TypeTestBase(fixture) { - public class BoolTypeFixture : TypeTestFixture + public class BoolTypeFixture : CosmosTypeFixtureBase { public override bool Value { get; } = true; public override bool OtherValue { get; } = false; protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance; - - public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) - => base.AddOptions(builder).ConfigureWarnings(c => c.Log(CosmosEventId.NoPartitionKeyDefined)); } } public class StringTypeTest(StringTypeTest.StringTypeFixture fixture) : TypeTestBase(fixture) { - public class StringTypeFixture : TypeTestFixture + public class StringTypeFixture : CosmosTypeFixtureBase { public override string Value { get; } = "foo"; public override string OtherValue { get; } = "bar"; protected override ITestStoreFactory TestStoreFactory => CosmosTestStoreFactory.Instance; - - public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) - => base.AddOptions(builder).ConfigureWarnings(c => c.Log(CosmosEventId.NoPartitionKeyDefined)); } } public class GuidTypeTest(GuidTypeTest.GuidTypeFixture fixture) : TypeTestBase(fixture) { - public class GuidTypeFixture : TypeTestFixture + public class GuidTypeFixture : CosmosTypeFixtureBase { 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) - => base.AddOptions(builder).ConfigureWarnings(c => c.Log(CosmosEventId.NoPartitionKeyDefined)); } } public class ByteArrayTypeTest(ByteArrayTypeTest.ByteArrayTypeFixture fixture) : TypeTestBase(fixture) { - public class ByteArrayTypeFixture : TypeTestFixture + public class ByteArrayTypeFixture : CosmosTypeFixtureBase { 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) - => base.AddOptions(builder).ConfigureWarnings(c => c.Log(CosmosEventId.NoPartitionKeyDefined)); - public override Func Comparer { get; } = (a, b) => a.SequenceEqual(b); } } diff --git a/test/EFCore.Cosmos.FunctionalTests/Types/CosmosNumericTypeTest.cs b/test/EFCore.Cosmos.FunctionalTests/Types/CosmosNumericTypeTest.cs index ab7772b2d5d..3aa78564b6a 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Types/CosmosNumericTypeTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Types/CosmosNumericTypeTest.cs @@ -5,98 +5,77 @@ namespace Microsoft.EntityFrameworkCore.Types.Numeric; public class ByteTypeTest(ByteTypeTest.ByteTypeFixture fixture) : TypeTestBase(fixture) { - public class ByteTypeFixture : TypeTestFixture + public class ByteTypeFixture : CosmosTypeFixtureBase { 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) - => base.AddOptions(builder).ConfigureWarnings(c => c.Log(CosmosEventId.NoPartitionKeyDefined)); } } public class ShortTypeTest(ShortTypeTest.ShortTypeFixture fixture) : TypeTestBase(fixture) { - public class ShortTypeFixture : TypeTestFixture + public class ShortTypeFixture : CosmosTypeFixtureBase { 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) - => base.AddOptions(builder).ConfigureWarnings(c => c.Log(CosmosEventId.NoPartitionKeyDefined)); } } public class IntTypeTest(IntTypeTest.IntTypeFixture fixture) : TypeTestBase(fixture) { - public class IntTypeFixture : TypeTestFixture + public class IntTypeFixture : CosmosTypeFixtureBase { 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) - => base.AddOptions(builder).ConfigureWarnings(c => c.Log(CosmosEventId.NoPartitionKeyDefined)); } } public class LongTypeTest(LongTypeTest.LongTypeFixture fixture) : TypeTestBase(fixture) { - public class LongTypeFixture : TypeTestFixture + public class LongTypeFixture : CosmosTypeFixtureBase { 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) - => base.AddOptions(builder).ConfigureWarnings(c => c.Log(CosmosEventId.NoPartitionKeyDefined)); } } public class DecimalTypeTest(DecimalTypeTest.DecimalTypeFixture fixture) : TypeTestBase(fixture) { - public class DecimalTypeFixture : TypeTestFixture + public class DecimalTypeFixture : CosmosTypeFixtureBase { 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) - => base.AddOptions(builder).ConfigureWarnings(c => c.Log(CosmosEventId.NoPartitionKeyDefined)); } } public class DoubleTypeTest(DoubleTypeTest.DoubleTypeFixture fixture) : TypeTestBase(fixture) { - public class DoubleTypeFixture : TypeTestFixture + public class DoubleTypeFixture : CosmosTypeFixtureBase { 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) - => base.AddOptions(builder).ConfigureWarnings(c => c.Log(CosmosEventId.NoPartitionKeyDefined)); } } public class FloatTypeTest(FloatTypeTest.FloatTypeFixture fixture) : TypeTestBase(fixture) { - public class FloatTypeFixture : TypeTestFixture + public class FloatTypeFixture : CosmosTypeFixtureBase { 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) - => base.AddOptions(builder).ConfigureWarnings(c => c.Log(CosmosEventId.NoPartitionKeyDefined)); } } diff --git a/test/EFCore.Cosmos.FunctionalTests/Types/CosmosTemporalTypeTest.cs b/test/EFCore.Cosmos.FunctionalTests/Types/CosmosTemporalTypeTest.cs index 147cc2f2e8d..7bcee3739c6 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Types/CosmosTemporalTypeTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Types/CosmosTemporalTypeTest.cs @@ -6,72 +6,57 @@ namespace Microsoft.EntityFrameworkCore.Types.Temporal; public class DateTimeTypeTest(DateTimeTypeTest.DateTimeTypeFixture fixture) : TypeTestBase(fixture) { - public class DateTimeTypeFixture : TypeTestFixture + public class DateTimeTypeFixture : CosmosTypeFixtureBase { 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) - => base.AddOptions(builder).ConfigureWarnings(c => c.Log(CosmosEventId.NoPartitionKeyDefined)); } } public class DateTimeOffsetTypeTest(DateTimeOffsetTypeTest.DateTimeOffsetTypeFixture fixture) : TypeTestBase(fixture) { - public class DateTimeOffsetTypeFixture : TypeTestFixture + public class DateTimeOffsetTypeFixture : CosmosTypeFixtureBase { 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) - => base.AddOptions(builder).ConfigureWarnings(c => c.Log(CosmosEventId.NoPartitionKeyDefined)); } } -public class DateOnlyTypeTest(DateOnlyTypeTest.DateTypeFixture fixture) : TypeTestBase(fixture) +public class DateOnlyTypeTest(DateOnlyTypeTest.DateOnlyTypeFixture fixture) : TypeTestBase(fixture) { - public class DateTypeFixture : TypeTestFixture + public class DateOnlyTypeFixture : CosmosTypeFixtureBase { 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) - => base.AddOptions(builder).ConfigureWarnings(c => c.Log(CosmosEventId.NoPartitionKeyDefined)); } } -public class TimeOnlyTypeTest(TimeOnlyTypeTest.TimeTypeFixture fixture) - : TypeTestBase(fixture) +public class TimeOnlyTypeTest(TimeOnlyTypeTest.TimeOnlyTypeFixture fixture) + : TypeTestBase(fixture) { - public class TimeTypeFixture : TypeTestFixture + public class TimeOnlyTypeFixture : CosmosTypeFixtureBase { 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) - => base.AddOptions(builder).ConfigureWarnings(c => c.Log(CosmosEventId.NoPartitionKeyDefined)); } } public class TimeSpanTypeTest(TimeSpanTypeTest.TimeSpanTypeFixture fixture) : TypeTestBase(fixture) { - public class TimeSpanTypeFixture : TypeTestFixture + public class TimeSpanTypeFixture : CosmosTypeFixtureBase { 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) - => base.AddOptions(builder).ConfigureWarnings(c => c.Log(CosmosEventId.NoPartitionKeyDefined)); } } diff --git a/test/EFCore.Cosmos.FunctionalTests/Types/CosmosTypeFixtureBase.cs b/test/EFCore.Cosmos.FunctionalTests/Types/CosmosTypeFixtureBase.cs new file mode 100644 index 00000000000..68784848c28 --- /dev/null +++ b/test/EFCore.Cosmos.FunctionalTests/Types/CosmosTypeFixtureBase.cs @@ -0,0 +1,10 @@ +// 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; + +public abstract class CosmosTypeFixtureBase : TypeFixtureBase +{ + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => base.AddOptions(builder).ConfigureWarnings(c => c.Log(CosmosEventId.NoPartitionKeyDefined)); +} diff --git a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs b/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs deleted file mode 100644 index 936daf32e3d..00000000000 --- a/test/EFCore.InMemory.FunctionalTests/InMemoryComplianceTest.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.EntityFrameworkCore.BulkUpdates; -using Microsoft.EntityFrameworkCore.Query.Associations; -using Microsoft.EntityFrameworkCore.Query.Associations.ComplexProperties; -using Microsoft.EntityFrameworkCore.Query.Associations.Navigations; -using Microsoft.EntityFrameworkCore.Query.Associations.OwnedNavigations; - -namespace Microsoft.EntityFrameworkCore; - -public class InMemoryComplianceTest : ComplianceTestBase -{ - protected override ICollection IgnoredTestBases { get; } = new HashSet - { - // No in-memory tests - typeof(ComplexTypeQueryTestBase<>), - typeof(AdHocComplexTypeQueryTestBase), - typeof(PrimitiveCollectionsQueryTestBase<>), - typeof(NonSharedPrimitiveCollectionsQueryTestBase), - typeof(FunkyDataQueryTestBase<>), - typeof(StoreGeneratedTestBase<>), - typeof(ConferencePlannerTestBase<>), - typeof(ManyToManyQueryTestBase<>), - typeof(BulkUpdatesTestBase<>), - typeof(FiltersInheritanceBulkUpdatesTestBase<>), - typeof(InheritanceBulkUpdatesTestBase<>), - typeof(NonSharedModelBulkUpdatesTestBase), - typeof(NorthwindBulkUpdatesTestBase<>), - typeof(JsonQueryTestBase<>), - typeof(AdHocJsonQueryTestBase), - typeof(TypeTestBase<,>), - - // Relationships tests - not implemented for InMemory - typeof(AssociationsCollectionTestBase<>), - typeof(AssociationsMiscellaneousTestBase<>), - typeof(AssociationsPrimitiveCollectionTestBase<>), - typeof(AssociationsProjectionTestBase<>), - typeof(AssociationsSetOperationsTestBase<>), - typeof(AssociationsStructuralEqualityTestBase<>), - typeof(AssociationsBulkUpdateTestBase<>), - typeof(ComplexPropertiesCollectionTestBase<>), - typeof(ComplexPropertiesMiscellaneousTestBase<>), - typeof(ComplexPropertiesPrimitiveCollectionTestBase<>), - typeof(ComplexPropertiesProjectionTestBase<>), - typeof(ComplexPropertiesSetOperationsTestBase<>), - typeof(ComplexPropertiesStructuralEqualityTestBase<>), - typeof(ComplexPropertiesBulkUpdateTestBase<>), - typeof(NavigationsCollectionTestBase<>), - typeof(NavigationsIncludeTestBase<>), - typeof(NavigationsMiscellaneousTestBase<>), - typeof(NavigationsPrimitiveCollectionTestBase<>), - typeof(NavigationsProjectionTestBase<>), - typeof(NavigationsSetOperationsTestBase<>), - typeof(NavigationsStructuralEqualityTestBase<>), - typeof(OwnedNavigationsCollectionTestBase<>), - typeof(OwnedNavigationsMiscellaneousTestBase<>), - typeof(OwnedNavigationsPrimitiveCollectionTestBase<>), - typeof(OwnedNavigationsProjectionTestBase<>), - typeof(OwnedNavigationsSetOperationsTestBase<>), - typeof(OwnedNavigationsStructuralEqualityTestBase<>) - }; - - protected override Assembly TargetAssembly { get; } = typeof(InMemoryComplianceTest).Assembly; -} diff --git a/test/EFCore.Relational.Specification.Tests/Types/JsonTypeEntity.cs b/test/EFCore.Relational.Specification.Tests/Types/JsonTypeEntity.cs new file mode 100644 index 00000000000..30cea610d32 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Types/JsonTypeEntity.cs @@ -0,0 +1,20 @@ +// 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; + +public class JsonTypeEntity +{ + public int Id { get; set; } + + public required T Value { get; set; } + public required T OtherValue { get; set; } + + public required JsonContainer JsonContainer { get; set; } +} + +public class JsonContainer +{ + public required T Value { get; set; } + public required T OtherValue { get; set; } +} diff --git a/test/EFCore.Relational.Specification.Tests/Types/RelationalTypeFixtureBase.cs b/test/EFCore.Relational.Specification.Tests/Types/RelationalTypeFixtureBase.cs new file mode 100644 index 00000000000..4d0565cf5b9 --- /dev/null +++ b/test/EFCore.Relational.Specification.Tests/Types/RelationalTypeFixtureBase.cs @@ -0,0 +1,73 @@ +// 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; + +public abstract class RelationalTypeFixtureBase : TypeFixtureBase, ITestSqlLoggerFactory +{ + public virtual string? StoreType => null; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + base.OnModelCreating(modelBuilder, context); + + modelBuilder.Entity>(b => + { + b.ToTable(nameof(TypeEntity<>)); + b.Property(e => e.Value).HasColumnType(StoreType); + b.Property(e => e.OtherValue).HasColumnType(StoreType); + }); + + modelBuilder.Entity>(b => + { + b.ToTable(nameof(JsonTypeEntity<>)); + + modelBuilder.Entity>().Property(e => e.Id).ValueGeneratedNever(); + + b.ComplexProperty(e => e.JsonContainer, jc => + { + jc.ToJson(); + + jc.Property(e => e.Value).HasColumnType(StoreType); + jc.Property(e => e.OtherValue).HasColumnType(StoreType); + }); + }); + } + + protected override async Task SeedAsync(DbContext context) + { + await base.SeedAsync(context); + + context.Set>().AddRange( + new() + { + Id = 1, + Value = Value, + OtherValue = OtherValue, + JsonContainer = new() + { + Value = Value, + OtherValue = OtherValue + } + }, + new() + { + Id = 2, + Value = OtherValue, + OtherValue = Value, + JsonContainer = new() + { + Value = OtherValue, + OtherValue = Value + } + }); + + 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.Relational.Specification.Tests/RelationalTypeTestBase.cs b/test/EFCore.Relational.Specification.Tests/Types/RelationalTypeTestBase.cs similarity index 52% rename from test/EFCore.Relational.Specification.Tests/RelationalTypeTestBase.cs rename to test/EFCore.Relational.Specification.Tests/Types/RelationalTypeTestBase.cs index 57236e96ce7..5a506bdbab1 100644 --- a/test/EFCore.Relational.Specification.Tests/RelationalTypeTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Types/RelationalTypeTestBase.cs @@ -1,10 +1,10 @@ // 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; +namespace Microsoft.EntityFrameworkCore.Types; public abstract class RelationalTypeTestBase(TFixture fixture) : TypeTestBase(fixture) - where TFixture : RelationalTypeTestBase.RelationalTypeTestFixture + where TFixture : RelationalTypeFixtureBase where T : notnull { public RelationalTypeTestBase(TFixture fixture, ITestOutputHelper testOutputHelper) @@ -23,19 +23,18 @@ public virtual async Task SaveChanges_within_json() Fixture.UseTransaction, async context => { - JsonTypeEntity entity; + JsonTypeEntity entity; - using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents()) - { - entity = await context.Set().SingleAsync(e => e.Id == 1); - } + entity = await context.Set>().SingleAsync(e => e.Id == 1); + + Fixture.TestSqlLoggerFactory.Clear(); entity.JsonContainer.Value = Fixture.OtherValue; await context.SaveChangesAsync(); using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents()) { - var result = await context.Set().Where(e => e.Id == 1).SingleAsync(); + var result = await context.Set>().Where(e => e.Id == 1).SingleAsync(); Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer); } }); @@ -51,11 +50,13 @@ public virtual async Task ExecuteUpdate_within_json_to_parameter() Fixture.UseTransaction, async context => { - await context.Set().ExecuteUpdateAsync(s => s.SetProperty(e => e.JsonContainer.Value, e => Fixture.OtherValue)); + Fixture.TestSqlLoggerFactory.Clear(); + + await context.Set>().ExecuteUpdateAsync(s => s.SetProperty(e => e.JsonContainer.Value, e => Fixture.OtherValue)); using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents()) { - var result = await context.Set().Where(e => e.Id == 1).SingleAsync(); + var result = await context.Set>().Where(e => e.Id == 1).SingleAsync(); Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer); } }); @@ -67,17 +68,19 @@ public virtual async Task ExecuteUpdate_within_json_to_constant() Fixture.UseTransaction, async context => { + Fixture.TestSqlLoggerFactory.Clear(); + // Manually inject a constant node into the query tree - var parameter = Expression.Parameter(typeof(JsonTypeEntity)); - var valueExpression = Expression.Lambda>( + var parameter = Expression.Parameter(typeof(JsonTypeEntity)); + var valueExpression = Expression.Lambda, T>>( Expression.Constant(Fixture.OtherValue, typeof(T)), parameter); - await context.Set().ExecuteUpdateAsync(s => s.SetProperty(e => e.JsonContainer.Value, valueExpression)); + await context.Set>().ExecuteUpdateAsync(s => s.SetProperty(e => e.JsonContainer.Value, valueExpression)); using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents()) { - var result = await context.Set().Where(e => e.Id == 1).SingleAsync(); + var result = await context.Set>().Where(e => e.Id == 1).SingleAsync(); Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer); } }); @@ -89,11 +92,13 @@ public virtual async Task ExecuteUpdate_within_json_to_another_json_property() Fixture.UseTransaction, async context => { - await context.Set().ExecuteUpdateAsync(s => s.SetProperty(e => e.JsonContainer.Value, e => e.JsonContainer.OtherValue)); + Fixture.TestSqlLoggerFactory.Clear(); + + await context.Set>().ExecuteUpdateAsync(s => s.SetProperty(e => e.JsonContainer.Value, e => e.JsonContainer.OtherValue)); using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents()) { - var result = await context.Set().Where(e => e.Id == 1).SingleAsync(); + var result = await context.Set>().Where(e => e.Id == 1).SingleAsync(); Assert.Equal(Fixture.OtherValue, result.JsonContainer.Value, Fixture.Comparer); } }); @@ -105,102 +110,22 @@ public virtual async Task ExecuteUpdate_within_json_to_nonjson_column() Fixture.UseTransaction, async context => { - await context.Set().ExecuteUpdateAsync(s => s.SetProperty(e => e.JsonContainer.Value, e => e.OtherValue)); + Fixture.TestSqlLoggerFactory.Clear(); + + await context.Set>().ExecuteUpdateAsync(s => s.SetProperty(e => e.JsonContainer.Value, e => e.OtherValue)); using (Fixture.TestSqlLoggerFactory.SuspendRecordingEvents()) { - var result = await context.Set().Where(e => e.Id == 1).SingleAsync(); + 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; } - - public required T Value { get; set; } - public required T OtherValue { get; set; } - - public required JsonContainer JsonContainer { get; set; } - } - - public class JsonContainer - { - public required T Value { get; set; } - public required T OtherValue { get; set; } - } - 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, jc => - { - jc.ToJson(); - - jc.Property(e => e.Value).HasColumnType(StoreType); - jc.Property(e => e.OtherValue).HasColumnType(StoreType); - }); - }); - } - - protected override async Task SeedAsync(DbContext context) - { - await base.SeedAsync(context); - - context.Set().AddRange( - new() - { - Id = 1, - Value = Value, - OtherValue = OtherValue, - JsonContainer = new() - { - Value = Value, - OtherValue = OtherValue - } - }, - new() - { - Id = 2, - Value = OtherValue, - OtherValue = Value, - JsonContainer = new() - { - Value = OtherValue, - OtherValue = Value - } - }); - - 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/TypeTestBase.cs b/test/EFCore.Specification.Tests/TypeTestBase.cs deleted file mode 100644 index ef7e764171a..00000000000 --- a/test/EFCore.Specification.Tests/TypeTestBase.cs +++ /dev/null @@ -1,74 +0,0 @@ -// 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; - -[Collection("Type tests")] -public abstract class TypeTestBase(TFixture fixture) : IClassFixture - where TFixture : TypeTestBase.TypeTestFixture - where T : notnull -{ - [ConditionalFact] - public async virtual Task Equality_in_query() - { - await using var context = Fixture.CreateContext(); - - var result = await context.Set().Where(e => e.Value.Equals(Fixture.Value)).SingleAsync(); - - Assert.Equal(Fixture.Value, result.Value, Fixture.Comparer); - } - - protected class TypeEntity - { - public int Id { get; set; } - - public required T Value { get; set; } - public required T OtherValue { get; set; } - } - - protected TFixture Fixture { get; } = fixture; - - public abstract class TypeTestFixture : SharedStoreFixtureBase - { - /// - /// The main value used in the tests. - /// - public abstract T Value { get; } - - /// - /// An additional value that is different from . - /// - public abstract T OtherValue { get; } - - protected override string StoreName => "TypeTest"; - - public virtual Func Comparer { get; } = EqualityComparer.Default.Equals; - - protected override bool RecreateStore => true; - - protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) - { - // Don't rely on database generated values, which aren't supported everywhere (e.g. Cosmos) - modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); - } - - protected override async Task SeedAsync(DbContext context) - { - context.Set().AddRange( - new() - { - Id = 1, - Value = Value, - OtherValue = OtherValue - }, - new() - { - Id = 2, - Value = OtherValue, - OtherValue = Value - }); - - await context.SaveChangesAsync(); - } - } -} diff --git a/test/EFCore.Specification.Tests/Types/TypeEntity.cs b/test/EFCore.Specification.Tests/Types/TypeEntity.cs new file mode 100644 index 00000000000..4923e60dcd3 --- /dev/null +++ b/test/EFCore.Specification.Tests/Types/TypeEntity.cs @@ -0,0 +1,12 @@ +// 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; + +public class TypeEntity +{ + public int Id { get; set; } + + public required T Value { get; set; } + public required T OtherValue { get; set; } +} diff --git a/test/EFCore.Specification.Tests/Types/TypeFixtureBase.cs b/test/EFCore.Specification.Tests/Types/TypeFixtureBase.cs new file mode 100644 index 00000000000..78e400d813c --- /dev/null +++ b/test/EFCore.Specification.Tests/Types/TypeFixtureBase.cs @@ -0,0 +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; + +public abstract class TypeFixtureBase : SharedStoreFixtureBase +{ + /// + /// The main value used in the tests. + /// + public abstract T Value { get; } + + /// + /// An additional value that is different from . + /// + public abstract T OtherValue { get; } + + protected override string StoreName => "TypeTest"; + + public virtual Func Comparer { get; } = EqualityComparer.Default.Equals; + + protected override bool RecreateStore => true; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + // Don't rely on database generated values, which aren't supported everywhere (e.g. Cosmos) + modelBuilder.Entity>().Property(e => e.Id).ValueGeneratedNever(); + } + + protected override async Task SeedAsync(DbContext context) + { + context.Set>().AddRange( + new() + { + Id = 1, + Value = Value, + OtherValue = OtherValue + }, + new() + { + Id = 2, + Value = OtherValue, + OtherValue = Value + }); + + await context.SaveChangesAsync(); + } +} diff --git a/test/EFCore.Specification.Tests/Types/TypeTestBase.cs b/test/EFCore.Specification.Tests/Types/TypeTestBase.cs new file mode 100644 index 00000000000..44878d3ae2b --- /dev/null +++ b/test/EFCore.Specification.Tests/Types/TypeTestBase.cs @@ -0,0 +1,23 @@ +// 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; + +[Collection("Type tests")] +public abstract class TypeTestBase(TFixture fixture) : IClassFixture + where TFixture : TypeFixtureBase + where T : notnull +{ + [ConditionalFact] + public async virtual Task Equality_in_query() + { + await using var context = Fixture.CreateContext(); + + var result = await context.Set>().Where(e => e.Value.Equals(Fixture.Value)).SingleAsync(); + + Assert.Equal(Fixture.Value, result.Value, Fixture.Comparer); + } + + + protected TFixture Fixture { get; } = fixture; +} diff --git a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestEnvironment.cs b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestEnvironment.cs index 0ddd04640af..53411c8a948 100644 --- a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestEnvironment.cs +++ b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/TestEnvironment.cs @@ -462,6 +462,9 @@ public static bool IsVectorTypeSupported public static byte SqlServerMajorVersion => GetProductMajorVersion(); + public static DbContextOptionsBuilder SetCompatibilityLevelFromEnvironment(DbContextOptionsBuilder builder) + => builder.UseSqlServerCompatibilityLevel(SqlServerMajorVersion * 10); + public static string? ElasticPoolName { get; } = Config["ElasticPoolName"]; public static bool? GetFlag(string key) diff --git a/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerGeographyTypeTest.cs b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerGeographyTypeTest.cs index b20b8b17d7b..f24c152ca18 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerGeographyTypeTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerGeographyTypeTest.cs @@ -14,19 +14,36 @@ 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(); + 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); + await base.ExecuteUpdate_within_json_to_nonjson_column(); + + if (Fixture.UsingJsonType) + { + AssertSql( + """ +UPDATE [j] +SET [JsonContainer].modify('$.Value', [j].[OtherValue].STAsText()) +FROM [JsonTypeEntity] AS [j] +"""); + } + else + { + AssertSql( + """ +UPDATE [j] +SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', [j].[OtherValue].STAsText()) +FROM [JsonTypeEntity] AS [j] +"""); + } } - public abstract class GeographyTypeFixture() : RelationalTypeTestFixture + public abstract class GeographyTypeFixture : SqlServerTypeFixture { public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) => base.AddOptions(builder).UseSqlServer(o => o.UseNetTopologySuite()); @@ -54,169 +71,181 @@ public class LineStringTypeFixture() : GeographyTypeFixture [ new Coordinate(-122.34877, 47.6233355), new Coordinate(-122.3308366, 47.5978429) - ]) { SRID = 4326 }; + ]) + { 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 }; + ]) + { SRID = 4326 }; } } - public class PolygonTypeTest(PolygonTypeTest.PolygonTypeFixture fixture) - : GeographyTypeTestBase(fixture) +public class PolygonTypeTest(PolygonTypeTest.PolygonTypeFixture fixture) + : GeographyTypeTestBase(fixture) +{ + public class PolygonTypeFixture() : GeographyTypeFixture { - 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 }; - } + // 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 MultiPointTypeTest(MultiPointTypeTest.MultiPointTypeFixture fixture) + : GeographyTypeTestBase(fixture) +{ + public class MultiPointTypeFixture() : GeographyTypeFixture { - 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 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 MultiLineStringTypeTest(MultiLineStringTypeTest.MultiLineStringTypeFixture fixture) + : GeographyTypeTestBase(fixture) +{ + public class MultiLineStringTypeFixture() : GeographyTypeFixture { - 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 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 MultiPolygonTypeTest(MultiPolygonTypeTest.MultiPolygonTypeFixture fixture) + : GeographyTypeTestBase(fixture) +{ + public class MultiPolygonTypeFixture() : GeographyTypeFixture { - 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 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 GeometryCollectionTypeTest(GeometryCollectionTypeTest.GeometryCollectionTypeFixture fixture) + : GeographyTypeTestBase(fixture) +{ + public class GeometryCollectionTypeFixture() : GeographyTypeFixture { - 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 }; - } + 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 index d6b4a463e0e..a9a04cc2342 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerGeometryTypeTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerGeometryTypeTest.cs @@ -15,19 +15,36 @@ 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(); + 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); + await base.ExecuteUpdate_within_json_to_nonjson_column(); + + if (Fixture.UsingJsonType) + { + AssertSql( + """ +UPDATE [j] +SET [JsonContainer].modify('$.Value', [j].[OtherValue].STAsText()) +FROM [JsonTypeEntity] AS [j] +"""); + } + else + { + AssertSql( + """ +UPDATE [j] +SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', [j].[OtherValue].STAsText()) +FROM [JsonTypeEntity] AS [j] +"""); + } } - public abstract class GeometryTypeFixture : RelationalTypeTestFixture + public abstract class GeometryTypeFixture : SqlServerTypeFixture { public override string? StoreType => "geometry"; @@ -196,12 +213,24 @@ public override async Task ExecuteUpdate_within_json_to_constant() { await base.ExecuteUpdate_within_json_to_constant(); - AssertSql( - """ + if (Fixture.UsingJsonType) + { + AssertSql( + """ +UPDATE [j] +SET [JsonContainer].modify('$.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] +"""); + } + else + { + 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 diff --git a/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerMiscellaneousTypeTest.cs b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerMiscellaneousTypeTest.cs index e78701dead3..a465ae4405d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerMiscellaneousTypeTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerMiscellaneousTypeTest.cs @@ -6,7 +6,7 @@ namespace Microsoft.EntityFrameworkCore.Types.Miscellaneous; public class BoolTypeTest(BoolTypeTest.BoolTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class BoolTypeFixture : RelationalTypeTestFixture + public class BoolTypeFixture : SqlServerTypeFixture { public override bool Value { get; } = true; public override bool OtherValue { get; } = false; @@ -18,7 +18,7 @@ public class BoolTypeFixture : RelationalTypeTestFixture public class StringTypeTest(StringTypeTest.StringTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class StringTypeFixture : RelationalTypeTestFixture + public class StringTypeFixture : SqlServerTypeFixture { public override string Value { get; } = "foo"; public override string OtherValue { get; } = "bar"; @@ -30,33 +30,85 @@ public class StringTypeFixture : RelationalTypeTestFixture public class GuidTypeTest(GuidTypeTest.GuidTypeFixture fixture) : RelationalTypeTestBase(fixture) { + [SqlServerCondition(SqlServerCondition.SupportsFunctions2022)] 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); + // TODO: Currently failing on Helix only, see #36746 + if (Environment.GetEnvironmentVariable("HELIX_WORKITEM_ROOT") is not null) + { + return; + } + + await base.ExecuteUpdate_within_json_to_nonjson_column(); + + if (Fixture.UsingJsonType) + { + AssertSql( + """ +UPDATE [j] +SET [JsonContainer].modify('$.Value', JSON_VALUE(JSON_OBJECT('v': [j].[OtherValue]), '$.v')) +FROM [JsonTypeEntity] AS [j] +"""); + } + else + { + AssertSql( + """ +UPDATE [j] +SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', JSON_VALUE(JSON_OBJECT('v': [j].[OtherValue]), '$.v')) +FROM [JsonTypeEntity] AS [j] +"""); + } + } - public class GuidTypeFixture : RelationalTypeTestFixture + public class GuidTypeFixture : SqlServerTypeFixture { 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; + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => TestEnvironment.SetCompatibilityLevelFromEnvironment(base.AddOptions(builder)); } } public class ByteArrayTypeTest(ByteArrayTypeTest.ByteArrayTypeFixture fixture) : RelationalTypeTestBase(fixture) { + [SqlServerCondition(SqlServerCondition.SupportsFunctions2022)] 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); + // TODO: Currently failing on Helix only, see #36746 + if (Environment.GetEnvironmentVariable("HELIX_WORKITEM_ROOT") is not null) + { + return; + } + + await base.ExecuteUpdate_within_json_to_nonjson_column(); + + if (Fixture.UsingJsonType) + { + AssertSql( + """ +UPDATE [j] +SET [JsonContainer].modify('$.Value', JSON_VALUE(JSON_OBJECT('v': [j].[OtherValue]), '$.v')) +FROM [JsonTypeEntity] AS [j] +"""); + } + else + { + AssertSql( + """ +UPDATE [j] +SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', JSON_VALUE(JSON_OBJECT('v': [j].[OtherValue]), '$.v')) +FROM [JsonTypeEntity] AS [j] +"""); + } } - public class ByteArrayTypeFixture() : RelationalTypeTestFixture + public class ByteArrayTypeFixture() : SqlServerTypeFixture { public override byte[] Value { get; } = [1, 2, 3]; public override byte[] OtherValue { get; } = [4, 5, 6, 7]; @@ -64,5 +116,8 @@ public class ByteArrayTypeFixture() : RelationalTypeTestFixture public override Func Comparer { get; } = (a, b) => a.SequenceEqual(b); protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; + + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => TestEnvironment.SetCompatibilityLevelFromEnvironment(base.AddOptions(builder)); } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerNumericTypeTest.cs b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerNumericTypeTest.cs index cace1fd6d13..3ca93bcdf2d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerNumericTypeTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerNumericTypeTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Types.Numeric; public class ByteTypeTest(ByteTypeTest.ByteTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class ByteTypeFixture : RelationalTypeTestFixture + public class ByteTypeFixture : SqlServerTypeFixture { public override byte Value { get; } = byte.MinValue; public override byte OtherValue { get; } = byte.MaxValue; @@ -16,7 +16,7 @@ public class ByteTypeFixture : RelationalTypeTestFixture public class ShortTypeTest(ShortTypeTest.ShortTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class ShortTypeFixture : RelationalTypeTestFixture + public class ShortTypeFixture : SqlServerTypeFixture { public override short Value { get; } = short.MinValue; public override short OtherValue { get; } = short.MaxValue; @@ -27,7 +27,7 @@ public class ShortTypeFixture : RelationalTypeTestFixture public class IntTypeTest(IntTypeTest.IntTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class IntTypeFixture : RelationalTypeTestFixture + public class IntTypeFixture : SqlServerTypeFixture { public override int Value { get; } = int.MinValue; public override int OtherValue { get; } = int.MaxValue; @@ -38,7 +38,7 @@ public class IntTypeFixture : RelationalTypeTestFixture public class LongTypeTest(LongTypeTest.LongTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class LongTypeFixture : RelationalTypeTestFixture + public class LongTypeFixture : SqlServerTypeFixture { public override long Value { get; } = long.MinValue; public override long OtherValue { get; } = long.MaxValue; @@ -49,7 +49,7 @@ public class LongTypeFixture : RelationalTypeTestFixture public class DecimalTypeTest(DecimalTypeTest.DecimalTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class DecimalTypeFixture : RelationalTypeTestFixture + public class DecimalTypeFixture : SqlServerTypeFixture { public override decimal Value { get; } = 30.5m; public override decimal OtherValue { get; } = 30m; @@ -64,7 +64,7 @@ public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder build public class DoubleTypeTest(DoubleTypeTest.DoubleTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class DoubleTypeFixture : RelationalTypeTestFixture + public class DoubleTypeFixture : SqlServerTypeFixture { public override double Value { get; } = 30.5d; public override double OtherValue { get; } = 30d; @@ -75,7 +75,7 @@ public class DoubleTypeFixture : RelationalTypeTestFixture public class FloatTypeTest(FloatTypeTest.FloatTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class FloatTypeFixture : RelationalTypeTestFixture + public class FloatTypeFixture : SqlServerTypeFixture { public override float Value { get; } = 30.5f; public override float OtherValue { get; } = 30f; diff --git a/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerTemporalTypeTest.cs b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerTemporalTypeTest.cs index e9fde6eb679..4ca3f0f049e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerTemporalTypeTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerTemporalTypeTest.cs @@ -6,7 +6,7 @@ namespace Microsoft.EntityFrameworkCore.Types.Temporal; public class DateTimeTypeTest(DateTimeTypeTest.DateTimeTypeFixture fixture, ITestOutputHelper testOutputHelper) : RelationalTypeTestBase(fixture, testOutputHelper) { - public class DateTimeTypeFixture : RelationalTypeTestFixture + public class DateTimeTypeFixture : SqlServerTypeFixture { 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); @@ -35,19 +35,55 @@ public override async Task ExecuteUpdate_within_json_to_constant() { await base.ExecuteUpdate_within_json_to_constant(); - AssertSql( - """ + if (Fixture.UsingJsonType) + { + AssertSql( + """ +UPDATE [j] +SET [JsonContainer].modify('$.Value', N'2022-05-03T00:00:00') +FROM [JsonTypeEntity] AS [j] +"""); + } + else + { + AssertSql( + """ UPDATE [j] SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', N'2022-05-03T00:00:00') FROM [JsonTypeEntity] AS [j] """); + } } + [SqlServerCondition(SqlServerCondition.SupportsFunctions2022)] 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); + // TODO: Currently failing on Helix only, see #36746 + if (Environment.GetEnvironmentVariable("HELIX_WORKITEM_ROOT") is not null) + { + return; + } + + await base.ExecuteUpdate_within_json_to_nonjson_column(); + + if (Fixture.UsingJsonType) + { + AssertSql( + """ +UPDATE [j] +SET [JsonContainer].modify('$.Value', JSON_VALUE(JSON_OBJECT('v': [j].[OtherValue]), '$.v')) +FROM [JsonTypeEntity] AS [j] +"""); + } + else + { + AssertSql( + """ + UPDATE [j] + SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', JSON_VALUE(JSON_OBJECT('v': [j].[OtherValue]), '$.v')) + FROM [JsonTypeEntity] AS [j] + """); + } } } @@ -75,22 +111,58 @@ public override async Task ExecuteUpdate_within_json_to_constant() { await base.ExecuteUpdate_within_json_to_constant(); - AssertSql( - """ + if (Fixture.UsingJsonType) + { + AssertSql( + """ +UPDATE [j] +SET [JsonContainer].modify('$.Value', N'2020-01-05T12:30:45+03:00') +FROM [JsonTypeEntity] AS [j] +"""); + } + else + { + AssertSql( + """ UPDATE [j] SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', N'2020-01-05T12:30:45+03:00') FROM [JsonTypeEntity] AS [j] """); + } } + [SqlServerCondition(SqlServerCondition.SupportsFunctions2022)] 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); + // TODO: Currently failing on Helix only, see #36746 + if (Environment.GetEnvironmentVariable("HELIX_WORKITEM_ROOT") is not null) + { + return; + } + + await base.ExecuteUpdate_within_json_to_nonjson_column(); + + if (Fixture.UsingJsonType) + { + AssertSql( + """ +UPDATE [j] +SET [JsonContainer].modify('$.Value', JSON_VALUE(JSON_OBJECT('v': [j].[OtherValue]), '$.v')) +FROM [JsonTypeEntity] AS [j] +"""); + } + else + { + AssertSql( + """ +UPDATE [j] +SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', JSON_VALUE(JSON_OBJECT('v': [j].[OtherValue]), '$.v')) +FROM [JsonTypeEntity] AS [j] +"""); + } } - public class DateTimeOffsetTypeFixture : RelationalTypeTestFixture + public class DateTimeOffsetTypeFixture : SqlServerTypeFixture { 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)); @@ -123,22 +195,58 @@ public override async Task ExecuteUpdate_within_json_to_constant() { await base.ExecuteUpdate_within_json_to_constant(); - AssertSql( - """ + if (Fixture.UsingJsonType) + { + AssertSql( + """ +UPDATE [j] +SET [JsonContainer].modify('$.Value', N'2022-05-03') +FROM [JsonTypeEntity] AS [j] +"""); + } + else + { + AssertSql( + """ UPDATE [j] SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', N'2022-05-03') FROM [JsonTypeEntity] AS [j] """); + } } + [SqlServerCondition(SqlServerCondition.SupportsFunctions2022)] 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); + // TODO: Currently failing on Helix only, see #36746 + if (Environment.GetEnvironmentVariable("HELIX_WORKITEM_ROOT") is not null) + { + return; + } + + await base.ExecuteUpdate_within_json_to_nonjson_column(); + + if (Fixture.UsingJsonType) + { + AssertSql( + """ +UPDATE [j] +SET [JsonContainer].modify('$.Value', JSON_VALUE(JSON_OBJECT('v': [j].[OtherValue]), '$.v')) +FROM [JsonTypeEntity] AS [j] +"""); + } + else + { + AssertSql( + """ + UPDATE [j] + SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', JSON_VALUE(JSON_OBJECT('v': [j].[OtherValue]), '$.v')) + FROM [JsonTypeEntity] AS [j] + """); + } } - public class DateTypeFixture : RelationalTypeTestFixture + public class DateTypeFixture : SqlServerTypeFixture { public override DateOnly Value { get; } = new DateOnly(2020, 1, 5); public override DateOnly OtherValue { get; } = new DateOnly(2022, 5, 3); @@ -147,8 +255,8 @@ public class DateTypeFixture : RelationalTypeTestFixture } } -public class TimeOnlyTypeTest(TimeOnlyTypeTest.TimeTypeFixture fixture, ITestOutputHelper testOutputHelper) - : RelationalTypeTestBase(fixture, testOutputHelper) +public class TimeOnlyTypeTest(TimeOnlyTypeTest.TimeOnlyTypeFixture fixture, ITestOutputHelper testOutputHelper) + : RelationalTypeTestBase(fixture, testOutputHelper) { public override async Task SaveChanges_within_json() { @@ -171,22 +279,58 @@ public override async Task ExecuteUpdate_within_json_to_constant() { await base.ExecuteUpdate_within_json_to_constant(); - AssertSql( - """ + if (Fixture.UsingJsonType) + { + AssertSql( + """ +UPDATE [j] +SET [JsonContainer].modify('$.Value', N'14:00:00.0000000') +FROM [JsonTypeEntity] AS [j] +"""); + } + else + { + AssertSql( + """ UPDATE [j] SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', N'14:00:00.0000000') FROM [JsonTypeEntity] AS [j] """); + } } + [SqlServerCondition(SqlServerCondition.SupportsFunctions2022)] 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); + // TODO: Currently failing on Helix only, see #36746 + if (Environment.GetEnvironmentVariable("HELIX_WORKITEM_ROOT") is not null) + { + return; + } + + await base.ExecuteUpdate_within_json_to_nonjson_column(); + + if (Fixture.UsingJsonType) + { + AssertSql( + """ +UPDATE [j] +SET [JsonContainer].modify('$.Value', JSON_VALUE(JSON_OBJECT('v': [j].[OtherValue]), '$.v')) +FROM [JsonTypeEntity] AS [j] +"""); + } + else + { + AssertSql( + """ +UPDATE [j] +SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', JSON_VALUE(JSON_OBJECT('v': [j].[OtherValue]), '$.v')) +FROM [JsonTypeEntity] AS [j] +"""); + } } - public class TimeTypeFixture : RelationalTypeTestFixture + public class TimeOnlyTypeFixture : SqlServerTypeFixture { public override TimeOnly Value { get; } = new TimeOnly(12, 30, 45); public override TimeOnly OtherValue { get; } = new TimeOnly(14, 0, 0); @@ -219,22 +363,58 @@ public override async Task ExecuteUpdate_within_json_to_constant() { await base.ExecuteUpdate_within_json_to_constant(); - AssertSql( - """ + if (Fixture.UsingJsonType) + { + AssertSql( + """ +UPDATE [j] +SET [JsonContainer].modify('$.Value', N'14:00:00') +FROM [JsonTypeEntity] AS [j] +"""); + } + else + { + AssertSql( + """ UPDATE [j] SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', N'14:00:00') FROM [JsonTypeEntity] AS [j] """); + } } + [SqlServerCondition(SqlServerCondition.SupportsFunctions2022)] 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); + // TODO: Currently failing on Helix only, see #36746 + if (Environment.GetEnvironmentVariable("HELIX_WORKITEM_ROOT") is not null) + { + return; + } + + await base.ExecuteUpdate_within_json_to_nonjson_column(); + + if (Fixture.UsingJsonType) + { + AssertSql( + """ +UPDATE [j] +SET [JsonContainer].modify('$.Value', JSON_VALUE(JSON_OBJECT('v': [j].[OtherValue]), '$.v')) +FROM [JsonTypeEntity] AS [j] +"""); + } + else + { + AssertSql( + """ +UPDATE [j] +SET [j].[JsonContainer] = JSON_MODIFY([j].[JsonContainer], '$.Value', JSON_VALUE(JSON_OBJECT('v': [j].[OtherValue]), '$.v')) +FROM [JsonTypeEntity] AS [j] +"""); + } } - public class TimeSpanTypeFixture : RelationalTypeTestFixture + public class TimeSpanTypeFixture : SqlServerTypeFixture { public override TimeSpan Value { get; } = new TimeSpan(12, 30, 45); public override TimeSpan OtherValue { get; } = new TimeSpan(14, 0, 0); diff --git a/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerTypeFixture.cs b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerTypeFixture.cs new file mode 100644 index 00000000000..30df874e1e0 --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/Types/SqlServerTypeFixture.cs @@ -0,0 +1,13 @@ +// 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; + +public abstract class SqlServerTypeFixture : RelationalTypeFixtureBase +{ + public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) + => TestEnvironment.SetCompatibilityLevelFromEnvironment(base.AddOptions(builder)); + + public virtual bool UsingJsonType + => TestEnvironment.SqlServerMajorVersion >= 17; +} diff --git a/test/EFCore.Sqlite.FunctionalTests/Types/SqliteMiscellaneousTypeTest.cs b/test/EFCore.Sqlite.FunctionalTests/Types/SqliteMiscellaneousTypeTest.cs index c33a42acd12..efca0f9f78c 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Types/SqliteMiscellaneousTypeTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Types/SqliteMiscellaneousTypeTest.cs @@ -6,7 +6,7 @@ namespace Microsoft.EntityFrameworkCore.Types.Miscellaneous; public class BoolTypeTest(BoolTypeTest.BoolTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class BoolTypeFixture : RelationalTypeTestFixture + public class BoolTypeFixture : RelationalTypeFixtureBase { public override bool Value { get; } = true; public override bool OtherValue { get; } = false; @@ -18,7 +18,7 @@ public class BoolTypeFixture : RelationalTypeTestFixture public class StringTypeTest(StringTypeTest.StringTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class StringTypeFixture : RelationalTypeTestFixture + public class StringTypeFixture : RelationalTypeFixtureBase { public override string Value { get; } = "foo"; public override string OtherValue { get; } = "bar"; @@ -32,12 +32,12 @@ public class GuidTypeTest(GuidTypeTest.GuidTypeFixture fixture) { public override async Task ExecuteUpdate_within_json_to_nonjson_column() { - // See #36688 for supporting this for SQL Server types other than string/numeric/bool + // See #36688 for supporting this for Sqlite 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 class GuidTypeFixture : RelationalTypeTestFixture + public class GuidTypeFixture : RelationalTypeFixtureBase { public override Guid Value { get; } = new("8f7331d6-cde9-44fb-8611-81fff686f280"); public override Guid OtherValue { get; } = new("ae192c36-9004-49b2-b785-8be10d169627"); @@ -51,12 +51,12 @@ public class ByteArrayTypeTest(ByteArrayTypeTest.ByteArrayTypeFixture fixture) { public override async Task ExecuteUpdate_within_json_to_nonjson_column() { - // See #36688 for supporting this for SQL Server types other than string/numeric/bool + // See #36688 for supporting this for Sqlite 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 class ByteArrayTypeFixture : RelationalTypeTestFixture + public class ByteArrayTypeFixture : RelationalTypeFixtureBase { public override byte[] Value { get; } = [1, 2, 3]; public override byte[] OtherValue { get; } = [4, 5, 6, 7]; diff --git a/test/EFCore.Sqlite.FunctionalTests/Types/SqliteNumericTypeTest.cs b/test/EFCore.Sqlite.FunctionalTests/Types/SqliteNumericTypeTest.cs index 36a83927998..ac44eee3c3e 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Types/SqliteNumericTypeTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Types/SqliteNumericTypeTest.cs @@ -5,7 +5,7 @@ namespace Microsoft.EntityFrameworkCore.Types.Numeric; public class ByteTypeTest(ByteTypeTest.ByteTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class ByteTypeFixture : RelationalTypeTestFixture + public class ByteTypeFixture : RelationalTypeFixtureBase { public override byte Value { get; } = byte.MinValue; public override byte OtherValue { get; } = byte.MaxValue; @@ -16,7 +16,7 @@ public class ByteTypeFixture : RelationalTypeTestFixture public class ShortTypeTest(ShortTypeTest.ShortTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class ShortTypeFixture : RelationalTypeTestFixture + public class ShortTypeFixture : RelationalTypeFixtureBase { public override short Value { get; } = short.MinValue; public override short OtherValue { get; } = short.MaxValue; @@ -27,7 +27,7 @@ public class ShortTypeFixture : RelationalTypeTestFixture public class IntTypeTest(IntTypeTest.IntTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class IntTypeFixture : RelationalTypeTestFixture + public class IntTypeFixture : RelationalTypeFixtureBase { public override int Value { get; } = int.MinValue; public override int OtherValue { get; } = int.MaxValue; @@ -38,7 +38,7 @@ public class IntTypeFixture : RelationalTypeTestFixture public class LongTypeTest(LongTypeTest.LongTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class LongTypeFixture : RelationalTypeTestFixture + public class LongTypeFixture : RelationalTypeFixtureBase { public override long Value { get; } = long.MinValue; public override long OtherValue { get; } = long.MaxValue; @@ -49,7 +49,7 @@ public class LongTypeFixture : RelationalTypeTestFixture public class DecimalTypeTest(DecimalTypeTest.DecimalTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class DecimalTypeFixture : RelationalTypeTestFixture + public class DecimalTypeFixture : RelationalTypeFixtureBase { public override decimal Value { get; } = 30.5m; public override decimal OtherValue { get; } = 30m; @@ -60,7 +60,7 @@ public class DecimalTypeFixture : RelationalTypeTestFixture public class DoubleTypeTest(DoubleTypeTest.DoubleTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class DoubleTypeFixture : RelationalTypeTestFixture + public class DoubleTypeFixture : RelationalTypeFixtureBase { public override double Value { get; } = 30.5d; public override double OtherValue { get; } = 30d; @@ -71,7 +71,7 @@ public class DoubleTypeFixture : RelationalTypeTestFixture public class FloatTypeTest(FloatTypeTest.FloatTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class FloatTypeFixture : RelationalTypeTestFixture + public class FloatTypeFixture : RelationalTypeFixtureBase { public override float Value { get; } = 30.5f; public override float OtherValue { get; } = 30f; diff --git a/test/EFCore.Sqlite.FunctionalTests/Types/SqliteTemporalTypeTest.cs b/test/EFCore.Sqlite.FunctionalTests/Types/SqliteTemporalTypeTest.cs index 80e0b361aed..405de087189 100644 --- a/test/EFCore.Sqlite.FunctionalTests/Types/SqliteTemporalTypeTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/Types/SqliteTemporalTypeTest.cs @@ -6,20 +6,20 @@ namespace Microsoft.EntityFrameworkCore.Types.Temporal; public class DateTimeTypeTest(DateTimeTypeTest.DateTimeTypeFixture fixture) : RelationalTypeTestBase(fixture) { - public class DateTimeTypeFixture : RelationalTypeTestFixture + public override async Task ExecuteUpdate_within_json_to_nonjson_column() + { + // See #36688 for supporting this for Sqlite 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 class DateTimeTypeFixture : RelationalTypeFixtureBase { 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; } - - 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 class DateTimeOffsetTypeTest(DateTimeOffsetTypeTest.DateTimeOffsetTypeFixture fixture) @@ -27,12 +27,12 @@ public class DateTimeOffsetTypeTest(DateTimeOffsetTypeTest.DateTimeOffsetTypeFix { public override async Task ExecuteUpdate_within_json_to_nonjson_column() { - // See #36688 for supporting this for SQL Server types other than string/numeric/bool + // See #36688 for supporting this for Sqlite 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 class DateTimeOffsetTypeFixture : RelationalTypeTestFixture + public class DateTimeOffsetTypeFixture : RelationalTypeFixtureBase { 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)); @@ -45,12 +45,12 @@ public class DateOnlyTypeTest(DateOnlyTypeTest.DateTypeFixture fixture) : Relati { public override async Task ExecuteUpdate_within_json_to_nonjson_column() { - // See #36688 for supporting this for SQL Server types other than string/numeric/bool + // See #36688 for supporting this for Sqlite 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 class DateTypeFixture : RelationalTypeTestFixture + public class DateTypeFixture : RelationalTypeFixtureBase { public override DateOnly Value { get; } = new DateOnly(2020, 1, 5); public override DateOnly OtherValue { get; } = new DateOnly(2022, 5, 3); @@ -64,12 +64,12 @@ public class TimeOnlyTypeTest(TimeOnlyTypeTest.TimeTypeFixture fixture) { public override async Task ExecuteUpdate_within_json_to_nonjson_column() { - // See #36688 for supporting this for SQL Server types other than string/numeric/bool + // See #36688 for supporting this for Sqlite 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 class TimeTypeFixture : RelationalTypeTestFixture + public class TimeTypeFixture : RelationalTypeFixtureBase { public override TimeOnly Value { get; } = new TimeOnly(12, 30, 45); public override TimeOnly OtherValue { get; } = new TimeOnly(14, 0, 0); @@ -82,12 +82,12 @@ public class TimeSpanTypeTest(TimeSpanTypeTest.TimeSpanTypeFixture fixture) : Re { public override async Task ExecuteUpdate_within_json_to_nonjson_column() { - // See #36688 for supporting this for SQL Server types other than string/numeric/bool + // See #36688 for supporting this for Sqlite 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 class TimeSpanTypeFixture : RelationalTypeTestFixture + public class TimeSpanTypeFixture : RelationalTypeFixtureBase { public override TimeSpan Value { get; } = new TimeSpan(12, 30, 45); public override TimeSpan OtherValue { get; } = new TimeSpan(14, 0, 0);