diff --git a/src/EFCore.Relational/Storage/JsonTypeMapping.cs b/src/EFCore.Relational/Storage/JsonTypeMapping.cs
index 9a14f340bae..62ced3a64ac 100644
--- a/src/EFCore.Relational/Storage/JsonTypeMapping.cs
+++ b/src/EFCore.Relational/Storage/JsonTypeMapping.cs
@@ -18,24 +18,24 @@ namespace Microsoft.EntityFrameworkCore.Storage;
/// See Implementation of database providers and extensions
/// for more information and examples.
///
-public abstract class JsonTypeMapping : RelationalTypeMapping
+public abstract class StructuralJsonTypeMapping : RelationalTypeMapping
{
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The name of the database type.
/// The .NET type.
/// The to be used.
- protected JsonTypeMapping(string storeType, Type clrType, DbType? dbType)
+ protected StructuralJsonTypeMapping(string storeType, Type clrType, DbType? dbType)
: base(storeType, clrType, dbType)
{
}
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// Parameter object for .
- protected JsonTypeMapping(RelationalTypeMappingParameters parameters)
+ protected StructuralJsonTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters)
{
}
@@ -45,3 +45,9 @@ protected override string GenerateNonNullSqlLiteral(object value)
=> throw new InvalidOperationException(
RelationalStrings.MethodNeedsToBeImplementedInTheProvider);
}
+
+///
+/// Use StructuralJsonTypeMapping instead for type mappings representing JSON structural types as opposed to JSON strings.
+///
+[Obsolete("Use StructuralJsonTypeMapping instead for type mappings representing JSON structural types as opposed to JSON strings.")]
+public class JsonTypeMapping;
diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerJsonPostprocessor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerJsonPostprocessor.cs
index 881c64c0d37..06b281186f6 100644
--- a/src/EFCore.SqlServer/Query/Internal/SqlServerJsonPostprocessor.cs
+++ b/src/EFCore.SqlServer/Query/Internal/SqlServerJsonPostprocessor.cs
@@ -1,7 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.SqlServer.Internal;
using Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
@@ -55,8 +54,7 @@ public Expression Process(Expression expression)
/// 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.
///
- [return: NotNullIfNotNull(nameof(expression))]
- public override Expression? Visit(Expression? expression)
+ protected override Expression VisitExtension(Expression expression)
{
switch (expression)
{
@@ -128,7 +126,7 @@ public Expression Process(Expression expression)
{
Check.DebugAssert(newTables is null, "newTables must be null if columnsToRewrite is null");
- result = (SelectExpression)base.Visit(result);
+ result = (SelectExpression)base.VisitExtension(result);
}
else
{
@@ -154,7 +152,7 @@ public Expression Process(Expression expression)
// Record the OPENJSON expression and its projected column(s), along with the store type we just removed from the WITH
// clause. Then visit the select expression, adding a cast around the matching ColumnExpressions.
- result = (SelectExpression)base.Visit(result);
+ result = (SelectExpression)base.VisitExtension(result);
foreach (var columnsToRewriteKey in columnsToRewrite.Keys)
{
@@ -270,19 +268,30 @@ when _columnsToRewrite.TryGetValue((columnExpression.TableAlias, columnExpressio
&& left is not SqlConstantExpression { Value: null }
&& right is not SqlConstantExpression { Value: null }:
{
- return comparison.Update(
- sqlExpressionFactory.Convert(
- left,
- typeof(string),
- typeMappingSource.FindMapping(typeof(string))),
- sqlExpressionFactory.Convert(
- right,
- typeof(string),
- typeMappingSource.FindMapping(typeof(string))));
+ var stringTypeMapping = typeMappingSource.FindMapping(typeof(string))!;
+
+ return comparison.Update(ConvertToString(left), ConvertToString(right));
+
+ SqlExpression ConvertToString(SqlExpression expression)
+ {
+ if (expression.TypeMapping?.StoreType.Equals("json", StringComparison.OrdinalIgnoreCase) == true)
+ {
+ // If the expression happens to be a json literal (CAST('...' AS json)), we can just extract the string inside,
+ // instead of applying an additional CAST around it
+ return expression is SqlConstantExpression constant
+ ? new SqlConstantExpression(
+ constant.Value,
+ typeof(string),
+ (RelationalTypeMapping)stringTypeMapping.WithComposedConverter(constant.TypeMapping!.Converter))
+ : sqlExpressionFactory.Convert(expression, typeof(string), stringTypeMapping);
+ }
+
+ return expression;
+ }
}
default:
- return base.Visit(expression);
+ return base.VisitExtension(expression);
}
static bool IsKeyColumn(SqlExpression sqlExpression, string openJsonTableAlias)
diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryTranslationPostprocessor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryTranslationPostprocessor.cs
index 16010bc6676..a575aa0307c 100644
--- a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryTranslationPostprocessor.cs
+++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryTranslationPostprocessor.cs
@@ -9,28 +9,19 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;
/// 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 class SqlServerQueryTranslationPostprocessor : RelationalQueryTranslationPostprocessor
+public class SqlServerQueryTranslationPostprocessor(
+ QueryTranslationPostprocessorDependencies dependencies,
+ RelationalQueryTranslationPostprocessorDependencies relationalDependencies,
+ SqlServerQueryCompilationContext queryCompilationContext)
+ : RelationalQueryTranslationPostprocessor(dependencies, relationalDependencies, queryCompilationContext)
{
- private readonly SqlServerJsonPostprocessor _jsonPostprocessor;
- private readonly SqlServerAggregateOverSubqueryPostprocessor _aggregatePostprocessor;
- private readonly SqlServerSqlTreePruner _pruner = new();
+ private readonly SqlServerJsonPostprocessor _jsonPostprocessor = new(
+ relationalDependencies.TypeMappingSource,
+ relationalDependencies.SqlExpressionFactory,
+ queryCompilationContext.SqlAliasManager);
- ///
- /// 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 SqlServerQueryTranslationPostprocessor(
- QueryTranslationPostprocessorDependencies dependencies,
- RelationalQueryTranslationPostprocessorDependencies relationalDependencies,
- SqlServerQueryCompilationContext queryCompilationContext)
- : base(dependencies, relationalDependencies, queryCompilationContext)
- {
- _jsonPostprocessor = new SqlServerJsonPostprocessor(
- relationalDependencies.TypeMappingSource, relationalDependencies.SqlExpressionFactory, queryCompilationContext.SqlAliasManager);
- _aggregatePostprocessor = new SqlServerAggregateOverSubqueryPostprocessor(queryCompilationContext.SqlAliasManager);
- }
+ private readonly SqlServerAggregateOverSubqueryPostprocessor _aggregatePostprocessor = new(queryCompilationContext.SqlAliasManager);
+ private readonly SqlServerSqlTreePruner _pruner = new();
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs
index 55177d3d266..d87a3490c31 100644
--- a/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs
+++ b/src/EFCore.SqlServer/Query/Internal/SqlServerQueryableMethodTranslatingExpressionVisitor.cs
@@ -608,6 +608,71 @@ IComplexType complexType
false));
}
+ ///
+ /// 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 ShapedQueryExpression? TranslateContains(ShapedQueryExpression source, Expression item)
+ {
+ // Attempt to translate to JSON_CONTAINS for SQL Server 2025+ (compatibility level 170+).
+ // JSON_CONTAINS is more efficient than IN (SELECT ... FROM OPENJSON(...)) for primitive collections.
+ if (_sqlServerSingletonOptions.SupportsJsonType
+ && source.QueryExpression is SelectExpression
+ {
+ // Primitive collection over OPENJSON (e.g. [p].[Ints])
+ Tables:
+ [
+ SqlServerOpenJsonExpression
+ {
+ // JSON_CONTAINS() is only supported over json, not nvarchar
+ Json: { TypeMapping: SqlServerJsonTypeMapping } json,
+ Path: null,
+ ColumnInfos: [{ Name: "value" }]
+ }
+ ],
+ Predicate: null,
+ GroupBy: [],
+ Having: null,
+ IsDistinct: false,
+ Limit: null,
+ Offset: null
+ }
+ && TranslateExpression(item, applyDefaultTypeMapping: false) is { } translatedItem
+ // Literal untyped NULL not supported as item by JSON_CONTAINS().
+ // For any other nullable item, SqlServerNullabilityProcessor will add a null check around the JSON_CONTAINS call.
+ && translatedItem is not SqlConstantExpression { Value: null }
+ // Note: JSON_CONTAINS doesn't allow searching for null items within a JSON collection (returns 0)
+ // As a result, we only translate to JSON_CONTAINS when we know that either the item is non-nullable or the collection's elements are.
+ && (
+ translatedItem is ColumnExpression { IsNullable: false } or SqlConstantExpression { Value: not null }
+ || !translatedItem.Type.IsNullableType()
+ || json.Type.GetSequenceType() is var elementClrType && !elementClrType.IsNullableType()))
+ {
+ // JSON_CONTAINS returns 1 if found, 0 if not found. It's a search condition expression.
+ var jsonContains = _sqlExpressionFactory.Equal(
+ _sqlExpressionFactory.Function(
+ "JSON_CONTAINS",
+ [json, translatedItem],
+ nullable: true,
+ argumentsPropagateNullability: [false, true],
+ typeof(int)),
+ _sqlExpressionFactory.Constant(1));
+
+#pragma warning disable EF1001 // Internal EF Core API usage.
+ var selectExpression = new SelectExpression(jsonContains, _queryCompilationContext.SqlAliasManager);
+ return source.Update(
+ selectExpression,
+ Expression.Convert(
+ new ProjectionBindingExpression(selectExpression, new ProjectionMember(), typeof(bool?)),
+ typeof(bool)));
+#pragma warning restore EF1001 // Internal EF Core API usage.
+ }
+
+ return base.TranslateContains(source, item);
+ }
+
///
/// 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
@@ -623,8 +688,8 @@ IComplexType complexType
{
switch (source.QueryExpression)
{
- // index on parameter using a column
- // translate via JSON because it is a better translation
+ // Index on parameter using a column
+ // Translate via JSON_VALUE() instead of via a VALUES subquery
case SelectExpression
{
Tables: [ValuesExpression { ValuesParameter: { } valuesParameter }],
@@ -938,15 +1003,7 @@ protected override bool TrySerializeScalarToJson(
// IReadOnlyList (just like Json{Scalar,Query}Expression), but instead we do the slight hack of packaging it
// as a constant argument; it will be unpacked and handled in SQL generation.
_sqlExpressionFactory.Constant(path, RelationalTypeMapping.NullMapping),
-
- // If an inline JSON object (complex type) is being assigned, it would be rendered here as a simple string:
- // [column].modify('$.foo', '{ "x": 8 }')
- // Since it's untyped, modify would treat is as a string rather than a JSON object, and insert it as such into
- // the enclosing object, escaping all the special JSON characters - that's not what we want.
- // We add a cast to JSON to have it interpreted as a JSON object.
- value is SqlConstantExpression { TypeMapping.StoreType: "json" }
- ? _sqlExpressionFactory.Convert(value, value.Type, _typeMappingSource.FindMapping("json")!)
- : value
+ value
],
nullable: true,
instancePropagatesNullability: true,
diff --git a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlNullabilityProcessor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlNullabilityProcessor.cs
index ce62d6755b1..6d75fd93a32 100644
--- a/src/EFCore.SqlServer/Query/Internal/SqlServerSqlNullabilityProcessor.cs
+++ b/src/EFCore.SqlServer/Query/Internal/SqlServerSqlNullabilityProcessor.cs
@@ -138,6 +138,48 @@ protected virtual SqlExpression VisitSqlServerAggregateFunction(
: aggregateFunctionExpression;
}
+ ///
+ /// 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 SqlExpression VisitSqlFunction(
+ SqlFunctionExpression sqlFunctionExpression,
+ bool allowOptimizedExpansion,
+ out bool nullable)
+ {
+ if (sqlFunctionExpression is { Name: "JSON_CONTAINS", Arguments: [var collection, var item] } jsonContains)
+ {
+ // JSON_CONTAINS() does not allow searching for NULL within a JSON collection (always returns zero when the item is NULL).
+ // As a result, we do not translate to JSON_CONTAINS() in SqlServerQueryableMethodTranslatingExpressionVisitor unless we know that
+ // either the item or the collection's elements are non-nullable.
+ // When the item argument is nullable, we add a null check around JSON_CONTAINS():
+ // CASE WHEN @item IS NULL THEN NULL ELSE JSON_CONTAINS(collection, @item) END
+ item = Visit(item, out var itemNullable);
+ collection = Visit(collection, out var collectionNullable);
+
+ sqlFunctionExpression = jsonContains.Update(instance: null, arguments: [collection, item]);
+
+ if (itemNullable && !UseRelationalNulls)
+ {
+ nullable = true;
+ return Dependencies.SqlExpressionFactory.Case(
+ [
+ new CaseWhenClause(
+ Dependencies.SqlExpressionFactory.IsNull(item),
+ Dependencies.SqlExpressionFactory.Constant(null, typeof(bool?), jsonContains.TypeMapping))
+ ],
+ jsonContains);
+ }
+
+ nullable = itemNullable || collectionNullable;
+ return sqlFunctionExpression;
+ }
+
+ return base.VisitSqlFunction(sqlFunctionExpression, allowOptimizedExpansion, out nullable);
+ }
+
///
/// 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/Query/Internal/SqlServerTypeMappingPostprocessor.cs b/src/EFCore.SqlServer/Query/Internal/SqlServerTypeMappingPostprocessor.cs
index 1184e8a7b1e..96101c557b0 100644
--- a/src/EFCore.SqlServer/Query/Internal/SqlServerTypeMappingPostprocessor.cs
+++ b/src/EFCore.SqlServer/Query/Internal/SqlServerTypeMappingPostprocessor.cs
@@ -101,14 +101,15 @@ protected virtual SqlServerOpenJsonExpression ApplyTypeMappingsOnOpenJsonExpress
elementTypeMapping = e;
}
- if (parameterTypeMapping is not SqlServerStringTypeMapping { ElementTypeMapping: not null })
+ if (parameterTypeMapping is not SqlServerStringTypeMapping { ElementTypeMapping: not null }
+ and not SqlServerJsonTypeMapping { ElementTypeMapping: not null })
{
- throw new UnreachableException("A SqlServerStringTypeMapping collection type mapping could not be found");
+ throw new UnreachableException("A string/JSON collection type mapping was not found");
}
return openJsonExpression.Update(
parameterExpression.ApplyTypeMapping(parameterTypeMapping),
path: null,
- [new SqlServerOpenJsonExpression.ColumnInfo("value", elementTypeMapping, [])]);
+ [new SqlServerOpenJsonExpression.ColumnInfo("value", elementTypeMapping, Path: [])]);
}
}
diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerJsonTypeMapping.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerJsonTypeMapping.cs
new file mode 100644
index 00000000000..369a240eeeb
--- /dev/null
+++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerJsonTypeMapping.cs
@@ -0,0 +1,72 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using Microsoft.Data.SqlClient;
+
+namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
+
+///
+/// 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 class SqlServerJsonTypeMapping : StringTypeMapping
+{
+ ///
+ /// 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 static new SqlServerJsonTypeMapping Default { get; } = new();
+
+ ///
+ /// 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 SqlServerJsonTypeMapping()
+ : base("json", dbType: null)
+ {
+ }
+
+ ///
+ /// 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 SqlServerJsonTypeMapping(RelationalTypeMappingParameters parameters)
+ : base(parameters)
+ {
+ }
+
+ ///
+ /// 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 RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
+ => new SqlServerJsonTypeMapping(parameters);
+
+ ///
+ /// 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 string GenerateNonNullSqlLiteral(object value)
+ => $"CAST({base.GenerateNonNullSqlLiteral(value)} AS json)";
+
+ ///
+ /// 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 void ConfigureParameter(DbParameter parameter)
+ => ((SqlParameter)parameter).SqlDbType = System.Data.SqlDbType.Json;
+}
diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerStringTypeMapping.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerStringTypeMapping.cs
index d265674f6ca..f28f1d15523 100644
--- a/src/EFCore.SqlServer/Storage/Internal/SqlServerStringTypeMapping.cs
+++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerStringTypeMapping.cs
@@ -34,14 +34,6 @@ public class SqlServerStringTypeMapping : StringTypeMapping
///
public static new SqlServerStringTypeMapping Default { get; } = new();
- ///
- /// 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 static SqlServerStringTypeMapping JsonTypeDefault { get; } = new("json", sqlDbType: SqlDbType.Json);
-
///
/// 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
@@ -157,13 +149,10 @@ protected override void ConfigureParameter(DbParameter parameter)
var value = parameter.Value;
var length = (value as string)?.Length;
- var sqlDbType = _sqlDbType
- ?? (StoreType == "json" ? SqlDbType.Json : null);
-
- if (sqlDbType.HasValue
+ if (_sqlDbType.HasValue
&& parameter is SqlParameter sqlParameter) // To avoid crashing wrapping providers
{
- sqlParameter.SqlDbType = sqlDbType.Value;
+ sqlParameter.SqlDbType = _sqlDbType.Value;
}
if ((value == null
diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerStructuralJsonTypeMapping.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerStructuralJsonTypeMapping.cs
index 42fae29b1cc..3de33d52e90 100644
--- a/src/EFCore.SqlServer/Storage/Internal/SqlServerStructuralJsonTypeMapping.cs
+++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerStructuralJsonTypeMapping.cs
@@ -13,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.SqlServer.Storage.Internal;
/// 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 class SqlServerStructuralJsonTypeMapping : JsonTypeMapping
+public class SqlServerStructuralJsonTypeMapping : StructuralJsonTypeMapping
{
private static readonly MethodInfo CreateUtf8StreamMethod
= typeof(SqlServerStructuralJsonTypeMapping).GetMethod(nameof(CreateUtf8Stream), [typeof(string)])!;
@@ -113,7 +113,13 @@ protected virtual string EscapeSqlLiteral(string literal)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
protected override string GenerateNonNullSqlLiteral(object value)
- => $"'{EscapeSqlLiteral((string)value)}'";
+ => StoreTypeNameBase switch
+ {
+ "json" => $"CAST('{EscapeSqlLiteral((string)value)}' AS json)",
+ "nvarchar" => $"'{EscapeSqlLiteral((string)value)}'",
+
+ _ => throw new UnreachableException()
+ };
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
diff --git a/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs b/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs
index 8b6613f61c2..2523a347c8f 100644
--- a/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs
+++ b/src/EFCore.SqlServer/Storage/Internal/SqlServerTypeMappingSource.cs
@@ -184,7 +184,7 @@ static SqlServerTypeMappingSource()
{ "float", [SqlServerDoubleTypeMapping.Default] },
{ "image", [ImageBinary] },
{ "int", [IntTypeMapping.Default] },
- { "json", [SqlServerStringTypeMapping.JsonTypeDefault] },
+ { "json", [SqlServerJsonTypeMapping.Default] },
{ "money", [Money] },
{ "national char varying", [VariableLengthUnicodeString] },
{ "national char varying(max)", [VariableLengthMaxUnicodeString] },
@@ -322,7 +322,7 @@ static SqlServerTypeMappingSource()
return Rowversion;
case { } t when t == typeof(string) && storeTypeName == "json":
- return SqlServerStringTypeMapping.JsonTypeDefault;
+ return SqlServerJsonTypeMapping.Default;
case { } t when t == typeof(string):
{
diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteJsonTypeMapping.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteJsonTypeMapping.cs
index c90b5317a3d..8fcea93409b 100644
--- a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteJsonTypeMapping.cs
+++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteJsonTypeMapping.cs
@@ -11,7 +11,7 @@ namespace Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal;
/// 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 class SqliteJsonTypeMapping : JsonTypeMapping
+public class SqliteStructuralJsonTypeMapping : StructuralJsonTypeMapping
{
private static readonly MethodInfo GetStringMethod
= typeof(DbDataReader).GetRuntimeMethod(nameof(DbDataReader.GetString), [typeof(int)])!;
@@ -31,13 +31,13 @@ private static readonly ConstructorInfo MemoryStreamConstructor
/// 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 static SqliteJsonTypeMapping Default { get; } = new(SqliteTypeMappingSource.TextTypeName);
+ public static SqliteStructuralJsonTypeMapping Default { get; } = new(SqliteTypeMappingSource.TextTypeName);
///
- /// Initializes a new instance of the class.
+ /// Initializes a new instance of the class.
///
/// The name of the database type.
- public SqliteJsonTypeMapping(string storeType)
+ public SqliteStructuralJsonTypeMapping(string storeType)
: base(storeType, typeof(JsonTypePlaceholder), System.Data.DbType.String)
{
}
@@ -48,7 +48,7 @@ public SqliteJsonTypeMapping(string storeType)
/// 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 SqliteJsonTypeMapping(RelationalTypeMappingParameters parameters)
+ protected SqliteStructuralJsonTypeMapping(RelationalTypeMappingParameters parameters)
: base(parameters)
{
}
@@ -101,5 +101,5 @@ protected virtual string EscapeSqlLiteral(string literal)
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
protected override RelationalTypeMapping Clone(RelationalTypeMappingParameters parameters)
- => new SqliteJsonTypeMapping(parameters);
+ => new SqliteStructuralJsonTypeMapping(parameters);
}
diff --git a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs
index d28b9373970..c944add36f2 100644
--- a/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs
+++ b/src/EFCore.Sqlite.Core/Storage/Internal/SqliteTypeMappingSource.cs
@@ -81,7 +81,7 @@ private static readonly HashSet SpatialiteTypes
{ typeof(double), Real },
{ typeof(float), new FloatTypeMapping(RealTypeName) },
{ typeof(Guid), SqliteGuidTypeMapping.Default },
- { typeof(JsonTypePlaceholder), SqliteJsonTypeMapping.Default }
+ { typeof(JsonTypePlaceholder), SqliteStructuralJsonTypeMapping.Default }
};
private readonly Dictionary _storeTypeMappings = new(StringComparer.OrdinalIgnoreCase)
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Associations/ComplexJson/ComplexJsonBulkUpdateSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Associations/ComplexJson/ComplexJsonBulkUpdateSqlServerTest.cs
index 7ca69d042b0..894e1d97cde 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/Associations/ComplexJson/ComplexJsonBulkUpdateSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/Associations/ComplexJson/ComplexJsonBulkUpdateSqlServerTest.cs
@@ -266,12 +266,24 @@ public override async Task Update_associate_to_inline_with_lambda()
{
await base.Update_associate_to_inline_with_lambda();
- AssertExecuteUpdateSql(
- """
+ if (Fixture.UsingJsonType)
+ {
+ AssertExecuteUpdateSql(
+ """
+UPDATE [r]
+SET [r].[RequiredAssociate] = CAST('{"Id":1000,"Int":70,"Ints":[1,2,4],"Name":"Updated associate name","String":"Updated associate string","NestedCollection":[],"OptionalNestedAssociate":null,"RequiredNestedAssociate":{"Id":1000,"Int":80,"Ints":[1,2,4],"Name":"Updated nested name","String":"Updated nested string"}}' AS json)
+FROM [RootEntity] AS [r]
+""");
+ }
+ else
+ {
+ AssertExecuteUpdateSql(
+ """
UPDATE [r]
SET [r].[RequiredAssociate] = '{"Id":1000,"Int":70,"Ints":[1,2,4],"Name":"Updated associate name","String":"Updated associate string","NestedCollection":[],"OptionalNestedAssociate":null,"RequiredNestedAssociate":{"Id":1000,"Int":80,"Ints":[1,2,4],"Name":"Updated nested name","String":"Updated nested string"}}'
FROM [RootEntity] AS [r]
""");
+ }
}
public override async Task Update_nested_associate_to_inline_with_lambda()
@@ -487,7 +499,7 @@ public override async Task Update_primitive_collection_to_parameter()
{
AssertExecuteUpdateSql(
"""
-@ints='[1,2,4]' (Size = 8000)
+@ints='[1,2,4]' (Size = 7)
UPDATE [r]
SET [RequiredAssociate].modify('$.Ints', @ints)
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Associations/ComplexJson/ComplexJsonPrimitiveCollectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Associations/ComplexJson/ComplexJsonPrimitiveCollectionSqlServerTest.cs
index 4b124a5daad..c32a57c936c 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/Associations/ComplexJson/ComplexJsonPrimitiveCollectionSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/Associations/ComplexJson/ComplexJsonPrimitiveCollectionSqlServerTest.cs
@@ -48,8 +48,19 @@ public override async Task Contains()
{
await base.Contains();
- AssertSql(
- """
+ if (Fixture.UsingJsonType)
+ {
+ AssertSql(
+ """
+SELECT [r].[Id], [r].[Name], [r].[AssociateCollection], [r].[OptionalAssociate], [r].[RequiredAssociate]
+FROM [RootEntity] AS [r]
+WHERE JSON_CONTAINS(JSON_QUERY([r].[RequiredAssociate], '$.Ints'), 3) = 1
+""");
+ }
+ else
+ {
+ AssertSql(
+ """
SELECT [r].[Id], [r].[Name], [r].[AssociateCollection], [r].[OptionalAssociate], [r].[RequiredAssociate]
FROM [RootEntity] AS [r]
WHERE 3 IN (
@@ -57,14 +68,26 @@ SELECT [i].[value]
FROM OPENJSON(JSON_QUERY([r].[RequiredAssociate], '$.Ints')) WITH ([value] int '$') AS [i]
)
""");
+ }
}
public override async Task Any_predicate()
{
await base.Any_predicate();
- AssertSql(
- """
+ if (Fixture.UsingJsonType)
+ {
+ AssertSql(
+ """
+SELECT [r].[Id], [r].[Name], [r].[AssociateCollection], [r].[OptionalAssociate], [r].[RequiredAssociate]
+FROM [RootEntity] AS [r]
+WHERE JSON_CONTAINS(JSON_QUERY([r].[RequiredAssociate], '$.Ints'), 2) = 1
+""");
+ }
+ else
+ {
+ AssertSql(
+ """
SELECT [r].[Id], [r].[Name], [r].[AssociateCollection], [r].[OptionalAssociate], [r].[RequiredAssociate]
FROM [RootEntity] AS [r]
WHERE 2 IN (
@@ -72,6 +95,7 @@ SELECT [i].[value]
FROM OPENJSON(JSON_QUERY([r].[RequiredAssociate], '$.Ints')) WITH ([value] int '$') AS [i]
)
""");
+ }
}
public override async Task Nested_Count()
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Associations/ComplexJson/ComplexJsonStructuralEqualitySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Associations/ComplexJson/ComplexJsonStructuralEqualitySqlServerTest.cs
index af756237f77..598087799ec 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/Associations/ComplexJson/ComplexJsonStructuralEqualitySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/Associations/ComplexJson/ComplexJsonStructuralEqualitySqlServerTest.cs
@@ -140,7 +140,7 @@ public override async Task Nested_associate_with_inline()
"""
SELECT [r].[Id], [r].[Name], [r].[AssociateCollection], [r].[OptionalAssociate], [r].[RequiredAssociate]
FROM [RootEntity] AS [r]
-WHERE CAST(JSON_QUERY([r].[RequiredAssociate], '$.RequiredNestedAssociate') AS nvarchar(max)) = CAST('{"Id":1000,"Int":8,"Ints":[1,2,3],"Name":"Root1_RequiredAssociate_RequiredNestedAssociate","String":"foo"}' AS nvarchar(max))
+WHERE CAST(JSON_QUERY([r].[RequiredAssociate], '$.RequiredNestedAssociate') AS nvarchar(max)) = N'{"Id":1000,"Int":8,"Ints":[1,2,3],"Name":"Root1_RequiredAssociate_RequiredNestedAssociate","String":"foo"}'
""");
}
else
@@ -216,7 +216,7 @@ public override async Task Nested_collection_with_inline()
"""
SELECT [r].[Id], [r].[Name], [r].[AssociateCollection], [r].[OptionalAssociate], [r].[RequiredAssociate]
FROM [RootEntity] AS [r]
-WHERE CAST(JSON_QUERY([r].[RequiredAssociate], '$.NestedCollection') AS nvarchar(max)) = CAST('[{"Id":1002,"Int":8,"Ints":[1,2,3],"Name":"Root1_RequiredAssociate_NestedCollection_1","String":"foo"},{"Id":1003,"Int":8,"Ints":[1,2,3],"Name":"Root1_RequiredAssociate_NestedCollection_2","String":"foo"}]' AS nvarchar(max))
+WHERE CAST(JSON_QUERY([r].[RequiredAssociate], '$.NestedCollection') AS nvarchar(max)) = N'[{"Id":1002,"Int":8,"Ints":[1,2,3],"Name":"Root1_RequiredAssociate_NestedCollection_1","String":"foo"},{"Id":1003,"Int":8,"Ints":[1,2,3],"Name":"Root1_RequiredAssociate_NestedCollection_2","String":"foo"}]'
""");
}
else
@@ -282,7 +282,7 @@ FROM OPENJSON([r].[RequiredAssociate], '$.NestedCollection') WITH (
[Name] nvarchar(max) '$.Name',
[String] nvarchar(max) '$.String'
) AS [n]
- WHERE [n].[Id] = 1002 AND [n].[Int] = 8 AND CAST([n].[Ints] AS nvarchar(max)) = CAST('[1,2,3]' AS nvarchar(max)) AND [n].[Name] = N'Root1_RequiredAssociate_NestedCollection_1' AND [n].[String] = N'foo')
+ WHERE [n].[Id] = 1002 AND [n].[Int] = 8 AND CAST([n].[Ints] AS nvarchar(max)) = N'[1,2,3]' AND [n].[Name] = N'Root1_RequiredAssociate_NestedCollection_1' AND [n].[String] = N'foo')
""");
}
else
@@ -318,7 +318,7 @@ public override async Task Contains_with_parameter()
"""
@entity_equality_nested_Id='1002' (Nullable = true)
@entity_equality_nested_Int='8' (Nullable = true)
-@entity_equality_nested_Ints='[1,2,3]' (Size = 8000)
+@entity_equality_nested_Ints='[1,2,3]' (Size = 7)
@entity_equality_nested_Name='Root1_RequiredAssociate_NestedCollection_1' (Size = 4000)
@entity_equality_nested_String='foo' (Size = 4000)
@@ -373,7 +373,7 @@ public override async Task Contains_with_operators_composed_on_the_collection()
@get_Item_Int='106'
@entity_equality_get_Item_Id='3003' (Nullable = true)
@entity_equality_get_Item_Int='108' (Nullable = true)
-@entity_equality_get_Item_Ints='[8,9,109]' (Size = 8000)
+@entity_equality_get_Item_Ints='[8,9,109]' (Size = 9)
@entity_equality_get_Item_Name='Root3_RequiredAssociate_NestedCollection_2' (Size = 4000)
@entity_equality_get_Item_String='foo104' (Size = 4000)
@@ -429,7 +429,7 @@ public override async Task Contains_with_nested_and_composed_operators()
@get_Item_Id='302'
@entity_equality_get_Item_Id='303' (Nullable = true)
@entity_equality_get_Item_Int='130' (Nullable = true)
-@entity_equality_get_Item_Ints='[8,9,131]' (Size = 8000)
+@entity_equality_get_Item_Ints='[8,9,131]' (Size = 9)
@entity_equality_get_Item_Name='Root3_AssociateCollection_2' (Size = 4000)
@entity_equality_get_Item_String='foo115' (Size = 4000)
@entity_equality_get_Item_NestedCollection='[{"Id":3014,"Int":136,"Ints":[8,9,137],"Name":"Root3_AssociateCollection_2_NestedCollection_1","String":"foo118"},{"Id":3015,"Int":138,"Ints":[8,9,139],"Name":"Root3_Root1_AssociateCollection_2_NestedCollection_2","String":"foo119"}]' (Size = 233)
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Associations/OwnedJson/OwnedJsonPrimitiveCollectionSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Associations/OwnedJson/OwnedJsonPrimitiveCollectionSqlServerTest.cs
index cdd9ba0b0b4..ae6f59caf65 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/Associations/OwnedJson/OwnedJsonPrimitiveCollectionSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/Associations/OwnedJson/OwnedJsonPrimitiveCollectionSqlServerTest.cs
@@ -48,8 +48,19 @@ public override async Task Contains()
{
await base.Contains();
- AssertSql(
- """
+ if (Fixture.UsingJsonType)
+ {
+ AssertSql(
+ """
+SELECT [r].[Id], [r].[Name], [r].[AssociateCollection], [r].[OptionalAssociate], [r].[RequiredAssociate]
+FROM [RootEntity] AS [r]
+WHERE JSON_CONTAINS(JSON_QUERY([r].[RequiredAssociate], '$.Ints'), 3) = 1
+""");
+ }
+ else
+ {
+ AssertSql(
+ """
SELECT [r].[Id], [r].[Name], [r].[AssociateCollection], [r].[OptionalAssociate], [r].[RequiredAssociate]
FROM [RootEntity] AS [r]
WHERE 3 IN (
@@ -57,14 +68,26 @@ SELECT [i].[value]
FROM OPENJSON(JSON_QUERY([r].[RequiredAssociate], '$.Ints')) WITH ([value] int '$') AS [i]
)
""");
+ }
}
public override async Task Any_predicate()
{
await base.Any_predicate();
- AssertSql(
- """
+ if (Fixture.UsingJsonType)
+ {
+ AssertSql(
+ """
+SELECT [r].[Id], [r].[Name], [r].[AssociateCollection], [r].[OptionalAssociate], [r].[RequiredAssociate]
+FROM [RootEntity] AS [r]
+WHERE JSON_CONTAINS(JSON_QUERY([r].[RequiredAssociate], '$.Ints'), 2) = 1
+""");
+ }
+ else
+ {
+ AssertSql(
+ """
SELECT [r].[Id], [r].[Name], [r].[AssociateCollection], [r].[OptionalAssociate], [r].[RequiredAssociate]
FROM [RootEntity] AS [r]
WHERE 2 IN (
@@ -72,6 +95,7 @@ SELECT [i].[value]
FROM OPENJSON(JSON_QUERY([r].[RequiredAssociate], '$.Ints')) WITH ([value] int '$') AS [i]
)
""");
+ }
}
public override async Task Nested_Count()
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs
index 37589e77a17..96ab5cb01f7 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerJsonTypeTest.cs
@@ -3,7 +3,7 @@
namespace Microsoft.EntityFrameworkCore.Query;
-[SqlServerCondition(SqlServerCondition.SupportsFunctions2022 | SqlServerCondition.SupportsJsonType)]
+[SqlServerCondition(SqlServerCondition.SupportsJsonType)]
public class PrimitiveCollectionsQuerySqlServerJsonTypeTest : PrimitiveCollectionsQueryRelationalTestBase<
PrimitiveCollectionsQuerySqlServerJsonTypeTest.PrimitiveCollectionsQuerySqlServerFixture>
{
@@ -570,7 +570,7 @@ public override async Task Inline_collection_Contains_with_EF_Parameter()
AssertSql(
"""
-@p='[2,999,1000]' (Size = 4000)
+@p='[2,999,1000]' (Size = 12)
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
@@ -587,7 +587,7 @@ public override async Task Inline_collection_Contains_with_IEnumerable_EF_Parame
AssertSql(
"""
-@Select='["10","a","aa"]' (Size = 4000)
+@Select='["10","a","aa"]' (Size = 15)
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
@@ -604,7 +604,7 @@ public override async Task Inline_collection_Count_with_column_predicate_with_EF
AssertSql(
"""
-@p='[2,999,1000]' (Size = 4000)
+@p='[2,999,1000]' (Size = 12)
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
@@ -1225,10 +1225,7 @@ public override async Task Column_collection_of_ints_Contains()
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
-WHERE 10 IN (
- SELECT [i].[value]
- FROM OPENJSON([p].[Ints]) WITH ([value] int '$') AS [i]
-)
+WHERE JSON_CONTAINS([p].[Ints], 10) = 1
""");
}
@@ -1240,10 +1237,7 @@ public override async Task Column_collection_of_nullable_ints_Contains()
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
-WHERE 10 IN (
- SELECT [n].[value]
- FROM OPENJSON([p].[NullableInts]) WITH ([value] int '$') AS [n]
-)
+WHERE JSON_CONTAINS([p].[NullableInts], 10) = 1
""");
}
@@ -1297,10 +1291,7 @@ public override async Task Column_collection_of_bools_Contains()
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
-WHERE CAST(1 AS bit) IN (
- SELECT [b].[value]
- FROM OPENJSON([p].[Bools]) WITH ([value] bit '$') AS [b]
-)
+WHERE JSON_CONTAINS([p].[Bools], CAST(1 AS bit)) = 1
""");
}
@@ -1509,7 +1500,7 @@ public override async Task Inline_collection_index_Column_with_EF_Constant()
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
-WHERE CAST(JSON_VALUE(N'[1,2,3]', '$[' + CAST([p].[Int] AS nvarchar(max)) + ']') AS int) = 1
+WHERE JSON_VALUE(CAST('[1,2,3]' AS json), '$[' + CAST([p].[Int] AS nvarchar(max)) + ']' RETURNING int) = 1
""");
}
@@ -1552,11 +1543,11 @@ public override async Task Parameter_collection_index_Column_equal_Column()
AssertSql(
"""
-@ints='[0,2,3]' (Size = 4000)
+@ints='[0,2,3]' (Size = 7)
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
-WHERE CAST(JSON_VALUE(@ints, '$[' + CAST([p].[Int] AS nvarchar(max)) + ']') AS int) = [p].[Int]
+WHERE JSON_VALUE(@ints, '$[' + CAST([p].[Int] AS nvarchar(max)) + ']' RETURNING int) = [p].[Int]
""");
}
@@ -1567,11 +1558,11 @@ public override async Task Parameter_collection_index_Column_equal_constant()
AssertSql(
"""
-@ints='[1,2,3]' (Size = 4000)
+@ints='[1,2,3]' (Size = 7)
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
-WHERE CAST(JSON_VALUE(@ints, '$[' + CAST([p].[Int] AS nvarchar(max)) + ']') AS int) = 1
+WHERE JSON_VALUE(@ints, '$[' + CAST([p].[Int] AS nvarchar(max)) + ']' RETURNING int) = 1
""");
}
@@ -1952,10 +1943,10 @@ public override async Task Parameter_collection_with_type_inference_for_JsonScal
AssertSql(
"""
-@values='["one","two"]' (Size = 4000)
+@values='["one","two"]' (Size = 13)
SELECT CASE
- WHEN [p].[Id] <> 0 THEN JSON_VALUE(@values, '$[' + CAST([p].[Int] % 2 AS nvarchar(max)) + ']')
+ WHEN [p].[Id] <> 0 THEN JSON_VALUE(@values, '$[' + CAST([p].[Int] % 2 AS nvarchar(max)) + ']' RETURNING nvarchar(max))
ELSE N'foo'
END
FROM [PrimitiveCollectionsEntity] AS [p]
@@ -2053,7 +2044,7 @@ public override async Task Column_collection_equality_parameter_collection()
AssertSql(
"""
-@ints='[1,10]' (Size = 8000)
+@ints='[1,10]' (Size = 6)
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
@@ -2076,7 +2067,7 @@ public override async Task Column_collection_equality_inline_collection()
"""
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[NullableWrappedId], [p].[NullableWrappedIdWithNullableComparer], [p].[String], [p].[Strings], [p].[WrappedId]
FROM [PrimitiveCollectionsEntity] AS [p]
-WHERE CAST([p].[Ints] AS nvarchar(max)) = CAST('[1,10]' AS nvarchar(max))
+WHERE CAST([p].[Ints] AS nvarchar(max)) = N'[1,10]'
""");
}
@@ -2571,7 +2562,7 @@ protected override ITestStoreFactory TestStoreFactory
public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder)
{
var options = base.AddOptions(builder);
- return options.UseSqlServerCompatibilityLevel(160);
+ return options.UseSqlServerCompatibilityLevel(170);
}
protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context)
@@ -2582,14 +2573,6 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con
{
// Map DateTime to non-default datetime instead of the default datetime2 to exercise type mapping inference
b.Property(p => p.DateTime).HasColumnType("datetime");
- b.PrimitiveCollection(e => e.Strings).HasColumnType("json");
- b.PrimitiveCollection(e => e.Ints).HasColumnType("json");
- b.PrimitiveCollection(e => e.DateTimes).HasColumnType("json");
- b.PrimitiveCollection(e => e.Bools).HasColumnType("json");
- b.PrimitiveCollection(e => e.Ints).HasColumnType("json");
- b.PrimitiveCollection(e => e.Enums).HasColumnType("json");
- b.PrimitiveCollection(e => e.NullableStrings).HasColumnType("json");
- b.PrimitiveCollection(e => e.NullableInts).HasColumnType("json");
});
}
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerCondition.cs b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerCondition.cs
index 33eb74beb23..90e5e937201 100644
--- a/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerCondition.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/TestUtilities/SqlServerCondition.cs
@@ -22,5 +22,5 @@ public enum SqlServerCondition
SupportsFunctions2019 = 1 << 13,
SupportsFunctions2022 = 1 << 14,
SupportsJsonType = 1 << 15,
- SupportsVectorType = 1 << 16,
+ SupportsVectorType = 1 << 16
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Update/JsonUpdateJsonTypeSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Update/JsonUpdateJsonTypeSqlServerTest.cs
index 6d25944439a..879095bb839 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Update/JsonUpdateJsonTypeSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Update/JsonUpdateJsonTypeSqlServerTest.cs
@@ -2535,31 +2535,31 @@ public override async Task Add_and_update_nested_optional_primitive_collection(b
+ parameterSize
+ @")
@p1='7624'
-@p2='[]' (Size = 8000)
-@p3=NULL (Size = 8000)
-@p4='[]' (Size = 8000)
-@p5='[]' (Size = 8000)
-@p6='[]' (Size = 8000)
-@p7='[]' (Size = 8000)
-@p8='[]' (Size = 8000)
-@p9='[]' (Size = 8000)
-@p10='[]' (Size = 8000)
-@p11='[]' (Size = 8000)
-@p12='[]' (Nullable = false) (Size = 8000)
-@p13='[]' (Size = 8000)
-@p14='[]' (Size = 8000)
-@p15='[]' (Size = 8000)
-@p16='[]' (Size = 8000)
-@p17='[]' (Size = 8000)
-@p18=NULL (Size = 8000)
-@p19='[]' (Size = 8000)
-@p20='[]' (Size = 8000)
-@p21='[]' (Size = 8000)
-@p22='[]' (Size = 8000)
-@p23='[]' (Size = 8000)
-@p24='[]' (Size = 8000)
-@p25='[]' (Size = 8000)
-@p26='[]' (Size = 8000)
+@p2='[]' (Size = 2)
+@p3=NULL
+@p4='[]' (Size = 2)
+@p5='[]' (Size = 2)
+@p6='[]' (Size = 2)
+@p7='[]' (Size = 2)
+@p8='[]' (Size = 2)
+@p9='[]' (Size = 2)
+@p10='[]' (Size = 2)
+@p11='[]' (Size = 2)
+@p12='[]' (Nullable = false) (Size = 2)
+@p13='[]' (Size = 2)
+@p14='[]' (Size = 2)
+@p15='[]' (Size = 2)
+@p16='[]' (Size = 2)
+@p17='[]' (Size = 2)
+@p18=NULL
+@p19='[]' (Size = 2)
+@p20='[]' (Size = 2)
+@p21='[]' (Size = 2)
+@p22='[]' (Size = 2)
+@p23='[]' (Size = 2)
+@p24='[]' (Size = 2)
+@p25='[]' (Size = 2)
+@p26='[]' (Size = 2)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -2579,8 +2579,8 @@ FROM [JsonEntitiesAllTypes] AS [j]
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
UPDATE [JsonEntitiesAllTypes] SET [Collection] = JSON_MODIFY([Collection], 'strict $[0].TestCharacterCollection', "
-+ updatedP0Reference
-+ @")
+ + updatedP0Reference
+ + @")
OUTPUT 1
WHERE [Id] = @p1;",
//
@@ -2663,7 +2663,7 @@ public override async Task Edit_single_property_relational_collection_of_bool()
AssertSql(
"""
@p1='1'
-@p0='[true,true,false]' (Size = 8000)
+@p0='[true,true,false]' (Size = 17)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -2686,7 +2686,7 @@ public override async Task Edit_single_property_relational_collection_of_byte()
AssertSql(
"""
@p1='1'
-@p0='[25,26]' (Size = 8000)
+@p0='[25,26]' (Size = 7)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -2717,7 +2717,7 @@ public override async Task Edit_single_property_relational_collection_of_datetim
AssertSql(
"""
@p1='1'
-@p0='["2000-01-01T12:34:56","3000-01-01T12:34:56","3000-01-01T12:34:56"]' (Size = 8000)
+@p0='["2000-01-01T12:34:56","3000-01-01T12:34:56","3000-01-01T12:34:56"]' (Size = 67)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -2740,7 +2740,7 @@ public override async Task Edit_single_property_relational_collection_of_datetim
AssertSql(
"""
@p1='1'
-@p0='["3000-01-01T12:34:56-04:00"]' (Size = 8000)
+@p0='["3000-01-01T12:34:56-04:00"]' (Size = 29)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -2763,7 +2763,7 @@ public override async Task Edit_single_property_relational_collection_of_decimal
AssertSql(
"""
@p1='1'
-@p0='[-13579.01]' (Size = 8000)
+@p0='[-13579.01]' (Size = 11)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -2786,7 +2786,7 @@ public override async Task Edit_single_property_relational_collection_of_double(
AssertSql(
"""
@p1='1'
-@p0='[-1.23456789,1.23456789,0,-1.23579]' (Size = 8000)
+@p0='[-1.23456789,1.23456789,0,-1.23579]' (Size = 35)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -2809,7 +2809,7 @@ public override async Task Edit_single_property_relational_collection_of_guid()
AssertSql(
"""
@p1='1'
-@p0='["12345678-1234-4321-5555-987654321000"]' (Nullable = false) (Size = 8000)
+@p0='["12345678-1234-4321-5555-987654321000"]' (Nullable = false) (Size = 40)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -2832,7 +2832,7 @@ public override async Task Edit_single_property_relational_collection_of_int16()
AssertSql(
"""
@p1='1'
-@p0='[-3234]' (Size = 8000)
+@p0='[-3234]' (Size = 7)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -2855,7 +2855,7 @@ public override async Task Edit_single_property_relational_collection_of_int32()
AssertSql(
"""
@p1='1'
-@p0='[-3234]' (Size = 8000)
+@p0='[-3234]' (Size = 7)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -2878,7 +2878,7 @@ public override async Task Edit_single_property_relational_collection_of_int64()
AssertSql(
"""
@p1='1'
-@p0='[]' (Size = 8000)
+@p0='[]' (Size = 2)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -2901,7 +2901,7 @@ public override async Task Edit_single_property_relational_collection_of_signed_
AssertSql(
"""
@p1='1'
-@p0='[-108]' (Size = 8000)
+@p0='[-108]' (Size = 6)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -2924,7 +2924,7 @@ public override async Task Edit_single_property_relational_collection_of_single(
AssertSql(
"""
@p1='1'
-@p0='[0,-1.234]' (Size = 8000)
+@p0='[0,-1.234]' (Size = 10)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -2947,7 +2947,7 @@ public override async Task Edit_single_property_relational_collection_of_timespa
AssertSql(
"""
@p1='1'
-@p0='["10:01:01.007","7:09:08.007"]' (Size = 8000)
+@p0='["10:01:01.007","7:09:08.007"]' (Size = 30)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -2970,7 +2970,7 @@ public override async Task Edit_single_property_relational_collection_of_uint16(
AssertSql(
"""
@p1='1'
-@p0='[1534]' (Size = 8000)
+@p0='[1534]' (Size = 6)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -2993,7 +2993,7 @@ public override async Task Edit_single_property_relational_collection_of_uint32(
AssertSql(
"""
@p1='1'
-@p0='[1237775789]' (Size = 8000)
+@p0='[1237775789]' (Size = 12)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -3016,7 +3016,7 @@ public override async Task Edit_single_property_relational_collection_of_uint64(
AssertSql(
"""
@p1='1'
-@p0='[1234555555123456789]' (Size = 8000)
+@p0='[1234555555123456789]' (Size = 21)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -3039,7 +3039,7 @@ public override async Task Edit_single_property_relational_collection_of_nullabl
AssertSql(
"""
@p1='1'
-@p0='[null,-2147483648,0,null,2147483647,null,77,null]' (Size = 8000)
+@p0='[null,-2147483648,0,null,2147483647,null,77,null]' (Size = 49)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -3062,7 +3062,7 @@ public override async Task Edit_single_property_relational_collection_of_nullabl
AssertSql(
"""
@p1='1'
-@p0=NULL (Size = 8000)
+@p0=NULL
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -3085,7 +3085,7 @@ public override async Task Edit_single_property_relational_collection_of_enum()
AssertSql(
"""
@p1='1'
-@p0='[-3]' (Size = 8000)
+@p0='[-3]' (Size = 4)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -3108,7 +3108,7 @@ public override async Task Edit_single_property_relational_collection_of_enum_wi
AssertSql(
"""
@p1='1'
-@p0='[-3]' (Size = 8000)
+@p0='[-3]' (Size = 4)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -3131,7 +3131,7 @@ public override async Task Edit_single_property_relational_collection_of_nullabl
AssertSql(
"""
@p1='1'
-@p0='[-3]' (Size = 8000)
+@p0='[-3]' (Size = 4)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -3154,7 +3154,7 @@ public override async Task Edit_single_property_relational_collection_of_nullabl
AssertSql(
"""
@p1='1'
-@p0=NULL (Size = 8000)
+@p0=NULL
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -3177,7 +3177,7 @@ public override async Task Edit_single_property_relational_collection_of_nullabl
AssertSql(
"""
@p1='1'
-@p0='[-1,-3,-7,2]' (Size = 8000)
+@p0='[-1,-3,-7,2]' (Size = 12)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -3200,7 +3200,7 @@ public override async Task Edit_single_property_relational_collection_of_nullabl
AssertSql(
"""
@p1='1'
-@p0=NULL (Size = 8000)
+@p0=NULL
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -3223,7 +3223,7 @@ public override async Task Edit_single_property_relational_collection_of_nullabl
AssertSql(
"""
@p1='1'
-@p0='[-1]' (Size = 8000)
+@p0='[-1]' (Size = 4)
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;
@@ -3246,7 +3246,7 @@ public override async Task Edit_single_property_relational_collection_of_nullabl
AssertSql(
"""
@p1='1'
-@p0=NULL (Size = 8000)
+@p0=NULL
SET IMPLICIT_TRANSACTIONS OFF;
SET NOCOUNT ON;