Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,4 @@ public class SqlServerLoggingDefinitions : RelationalLoggingDefinitions
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public EventDefinitionBase? LogMissingViewDefinitionRights;

/// <summary>
/// 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.
/// </summary>
public EventDefinitionBase? LogJsonTypeExperimental;
}
16 changes: 1 addition & 15 deletions src/EFCore.SqlServer/Diagnostics/SqlServerEventId.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ private enum Id
ConflictingValueGenerationStrategiesWarning,
DecimalTypeKeyWarning,
SavepointsDisabledBecauseOfMARS,
JsonTypeExperimental,
JsonTypeExperimental, // No longer used

// Scaffolding events
ColumnFound = CoreEventId.ProviderDesignBaseId,
Expand Down Expand Up @@ -115,20 +115,6 @@ private static EventId MakeValidationId(Id id)
/// </remarks>
public static readonly EventId ByteIdentityColumnWarning = MakeValidationId(Id.ByteIdentityColumnWarning);

/// <summary>
/// An entity type makes use of the SQL Server native 'json' type. Please note that support for this type in EF Core 9 is
/// experimental and may change in future releases.
/// </summary>
/// <remarks>
/// <para>
/// This event is in the <see cref="DbLoggerCategory.Model.Validation" /> category.
/// </para>
/// <para>
/// This event uses the <see cref="EntityTypeEventData" /> payload when used with a <see cref="DiagnosticSource" />.
/// </para>
/// </remarks>
public static readonly EventId JsonTypeExperimental = MakeValidationId(Id.JsonTypeExperimental);

/// <summary>
/// There are conflicting value generation methods for a property.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,33 +124,6 @@ private static string ByteIdentityColumnWarning(EventDefinitionBase definition,
p.Property.DeclaringType.DisplayName());
}

/// <summary>
/// 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.
/// </summary>
public static void JsonTypeExperimental(
this IDiagnosticsLogger<DbLoggerCategory.Model.Validation> diagnostics,
IEntityType entityType)
{
var definition = SqlServerResources.LogJsonTypeExperimental(diagnostics);

if (diagnostics.ShouldLog(definition))
{
definition.Log(diagnostics, entityType.DisplayName());
}

if (diagnostics.NeedsEventData(definition, out var diagnosticSourceEnabled, out var simpleLogEnabled))
{
var eventData = new EntityTypeEventData(
definition, (d, p)
=> ((EventDefinition<string>)d).GenerateMessage(((EntityTypeEventData)p).EntityType.DisplayName()), entityType);

diagnostics.DispatchEventData(definition, eventData, diagnosticSourceEnabled, simpleLogEnabled);
}
}

/// <summary>
/// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,27 +48,6 @@ public override void Validate(IModel model, IDiagnosticsLogger<DbLoggerCategory.
ValidateVectorColumns(model, logger);
ValidateByteIdentityMapping(model, logger);
ValidateTemporalTables(model, logger);
ValidateUseOfJsonType(model, logger);
}

/// <summary>
/// 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.
/// </summary>
protected virtual void ValidateUseOfJsonType(
IModel model,
IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
{
foreach (var entityType in model.GetEntityTypes())
{
if (string.Equals(entityType.GetContainerColumnType(), "json", StringComparison.OrdinalIgnoreCase)
|| entityType.GetProperties().Any(p => string.Equals(p.GetColumnType(), "json", StringComparison.OrdinalIgnoreCase)))
{
logger.JsonTypeExperimental(entityType);
}
}
}

/// <summary>
Expand Down
25 changes: 0 additions & 25 deletions src/EFCore.SqlServer/Properties/SqlServerStrings.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 0 additions & 4 deletions src/EFCore.SqlServer/Properties/SqlServerStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -264,10 +264,6 @@
<value>Found unique constraint on table '{tableName}' with name '{uniqueConstraintName}'.</value>
<comment>Debug SqlServerEventId.UniqueConstraintFound string string</comment>
</data>
<data name="LogJsonTypeExperimental" xml:space="preserve">
<value>The entity type '{entityType}' makes use of the SQL Server native 'json' type. Please note that support for this type in EF Core 9 is experimental and may change in future releases.</value>
<comment>Warning SqlServerEventId.JsonTypeExperimental string</comment>
</data>
<data name="LogMissingSchema" xml:space="preserve">
<value>Unable to find a schema in the database matching the selected schema '{schema}'.</value>
<comment>Warning SqlServerEventId.MissingSchemaWarning string?</comment>
Expand Down
37 changes: 22 additions & 15 deletions src/EFCore.SqlServer/Query/Internal/SqlServerJsonPostprocessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -254,21 +254,28 @@ when _columnsToRewrite.TryGetValue((columnExpression.TableAlias, columnExpressio
jsonScalar.IsNullable);
}

case SqlServerOpenJsonExpression openJsonExpression:
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed that this cast is no longer needed - OPENJSON can just directly accept the json data type.

// Currently, OPENJSON does not accept a "json" type, so we must cast the value to a string.
// We do this for both the case where is a string type mapping for a top-level property with the store type
// of "json", and when there is an "element" type mapping to something in the document but is now being used
// with OPENJSON.
return openJsonExpression.JsonExpression.TypeMapping
is SqlServerStringTypeMapping { StoreType: "json" }
or SqlServerStructuralJsonTypeMapping { StoreType: "json" }
? openJsonExpression.Update(
new SqlUnaryExpression(
ExpressionType.Convert,
(SqlExpression)Visit(openJsonExpression.JsonExpression),
typeof(string),
typeMappingSource.FindMapping(typeof(string))!))
: base.Visit(expression);
// The SQL Server json type cannot be compared ("The JSON data type cannot be compared or sorted, except when using the
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to confirm this in design.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Design decision: we'll go ahead with allowing comparison of JSON-mapped complex types by adding this cast to nvarchar(max):

  • This isn't any worse than the already-supported comparison of nvarchar(max)-mapped types.
  • As long as EF is used to read/write the columns, everything works.
  • We don't want the new json support to be inferior to the worst nvarchar(max) mapping option.

// IS NULL operator"). So we find comparisons that involve the json type, and apply a conversion to string (nvarchar(max))
// to both sides. We exempt this when one of the sides is a constant null (not required).
case SqlBinaryExpression
{
OperatorType: ExpressionType.Equal or ExpressionType.NotEqual,
Left: var left,
Right: var right
} comparison
when (left.TypeMapping?.StoreType is "json" || right.TypeMapping?.StoreType is "json")
&& 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))));
}

default:
return base.Visit(expression);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,37 +2,56 @@
// The .NET Foundation licenses this file to you under the MIT license.

#nullable disable

using Microsoft.Data.SqlClient;
using Xunit.Sdk;

namespace Microsoft.EntityFrameworkCore.Query;

[SqlServerCondition(SqlServerCondition.SupportsJsonType)]
public class AdHocJsonQuerySqlServerJsonTypeTest(NonSharedFixture fixture) : AdHocJsonQuerySqlServerTestBase(fixture)
{
public override async Task Missing_navigation_works_with_deduplication(bool async)
{
// TODO:SQLJSON Returns empty (invalid) JSON (See BadJson.cs)
if (async)
{
Assert.Equal(
"Unable to cast object of type 'System.DBNull' to type 'System.String'.",
(await Assert.ThrowsAsync<InvalidCastException>(() => base.Missing_navigation_works_with_deduplication(true))).Message);
}
else
{
Assert.Equal(
RelationalStrings.JsonEmptyString,
(await Assert.ThrowsAsync<InvalidOperationException>(() => base.Missing_navigation_works_with_deduplication(false)))
.Message);
}
}

public override async Task Contains_on_nested_collection_with_init_only_navigation()
// TODO:SQLJSON (See JsonTypeToFunction.cs)
=> Assert.Equal(
"OpenJson support not yet supported for JSON native data type.",
(await Assert.ThrowsAsync<SqlException>(
() => base.Contains_on_nested_collection_with_init_only_navigation())).Message);
#region BadJsonProperties

// When using the SQL Server JSON data type, insertion of the bad data fails thanks to SQL Server validation,
// unlike with tests mapping to nvarchar(max) where the bad JSON data is inserted correctly and then read.

public override Task Bad_json_properties_duplicated_navigations(bool noTracking)
=> Task.CompletedTask;

public override Task Bad_json_properties_duplicated_scalars(bool noTracking)
=> Task.CompletedTask;

public override Task Bad_json_properties_empty_navigations(bool noTracking)
=> Task.CompletedTask;

public override Task Bad_json_properties_empty_scalars(bool noTracking)
=> Task.CompletedTask;

public override Task Bad_json_properties_null_navigations(bool noTracking)
=> Task.CompletedTask;

public override Task Bad_json_properties_null_scalars(bool noTracking)
=> Task.CompletedTask;

#endregion BadJsonProperties

// SQL Server 2025 (CTP 2.1) does not support casting JSON scalar strings to json
// (CAST('8' AS json) and CAST('null' AS json) fail with "JSON text is not properly formatted").
public override Task Project_entity_with_json_null_values()
=> Assert.ThrowsAsync<SqlException>(base.Project_entity_with_json_null_values);

// SQL Server 2025 (CTP 2.1) does not support casting JSON scalar strings to json
// (CAST('8' AS json) and CAST('null' AS json) fail with "JSON text is not properly formatted").
// The base implementation expects a different exception.
public override Task Try_project_collection_but_JSON_is_entity()
=> Assert.ThrowsAsync<ThrowsException>(base.Try_project_collection_but_JSON_is_entity);

// SQL Server 2025 (CTP 2.1) does not support casting JSON scalar strings to json
// (CAST('8' AS json) and CAST('null' AS json) fail with "JSON text is not properly formatted").
// The base implementation expects a different exception.
public override Task Try_project_reference_but_JSON_is_collection()
=> Assert.ThrowsAsync<ThrowsException>(base.Try_project_reference_but_JSON_is_collection);

protected override string StoreName
=> "AdHocJsonQueryJsonTypeTest";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,4 @@

namespace Microsoft.EntityFrameworkCore.Query;

public class AdHocJsonQuerySqlServerTest(NonSharedFixture fixture) : AdHocJsonQuerySqlServerTestBase(fixture)
{
}
public class AdHocJsonQuerySqlServerTest(NonSharedFixture fixture) : AdHocJsonQuerySqlServerTestBase(fixture);
Loading