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 @@ -792,8 +792,22 @@ void GenerateColumnInfo(SqlServerOpenJsonExpression.ColumnInfo columnInfo)
{
Sql
.Append(Dependencies.SqlGenerationHelper.DelimitIdentifier(columnInfo.Name))
.Append(" ")
.Append(columnInfo.TypeMapping.StoreType);
.Append(" ");

// OPENJSON() WITH clause AS JSON option requires nvarchar(max) on platforms that don't support the json data
// type in OPENJSON (i.e. Azure SQL). SQL Server 2025 (17.x) added native support for json in OPENJSON WITH.
// See: https://learn.microsoft.com/sql/t-sql/data-types/json-data-type#limitations
if (columnInfo.AsJson
&& columnInfo.TypeMapping.StoreType == "json"
&& (_sqlServerSingletonOptions.EngineType != SqlServerEngineType.SqlServer
|| _sqlServerSingletonOptions.SqlServerCompatibilityLevel < 170))
{
Comment thread
AndriySvyryd marked this conversation as resolved.
Sql.Append("nvarchar(max)");
}
else
{
Sql.Append(columnInfo.TypeMapping.StoreType);
}

if (columnInfo.Path is not null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -691,6 +691,99 @@ public class JsonNestedType

#endregion HasJsonPropertyName

#region 38615

[ConditionalFact]
public virtual async Task SelectMany_over_primitive_collection_nested_in_complex_collection_inside_json_column()
Comment thread
AndriySvyryd marked this conversation as resolved.
{
var contextFactory = await InitializeAsync<Context38615>(
onConfiguring: b => b.ConfigureWarnings(ConfigureWarnings),
onModelCreating: m => m.Entity<Context38615.Car>(b =>
{
b.ToTable("Cars");
b.HasKey(e => e.CarId);
b.Property(e => e.Vin).IsUnicode(false).HasMaxLength(32);
b.Property(e => e.DealerId).IsUnicode(false).HasMaxLength(32);
b.HasIndex(e => new { e.Vin, e.DealerId }).IsUnique();

b.ComplexProperty(e => e.CarConfiguration, pp =>
{
pp.ToJson("CarConfiguration");
if (JsonColumnType != null)
{
pp.HasColumnType(JsonColumnType);
}

pp.Property(p => p.CurrentTrim).HasJsonPropertyName("currentTrim");

pp.ComplexCollection(p => p.OptionPackages, op =>
{
op.HasJsonPropertyName("optionPackages");
op.Property(o => o.PackageId).HasJsonPropertyName("packageId");
op.PrimitiveCollection(o => o.PartNumbers)
.ElementType(e => e.IsUnicode(false).HasMaxLength(32))
.HasJsonPropertyName("partNumbers");
});
});
}),
seed: context =>
{
context.Set<Context38615.Car>().Add(
new Context38615.Car
{
Vin = "1FA6P8TH8J5123456",
DealerId = "DEALER-001",
CarConfiguration = new Context38615.CarConfiguration
{
CurrentTrim = "GT-Line",
OptionPackages =
[
new Context38615.OptionPackage { PackageId = "PKG-SPORT", PartNumbers = ["SP-100", "SP-101"] },
new Context38615.OptionPackage { PackageId = "PKG-TOW", PartNumbers = ["TW-200"] }
]
}
});

return context.SaveChangesAsync();
});

await using var context = contextFactory.CreateContext();

var partNumbers = await context.Set<Context38615.Car>()
.Where(c => c.Vin == "1FA6P8TH8J5123456" && c.DealerId == "DEALER-001")
.SelectMany(c => c.CarConfiguration.OptionPackages)
.Where(op => op.PackageId == "PKG-SPORT")
.SelectMany(op => op.PartNumbers)
.ToListAsync();

Assert.Equal(["SP-100", "SP-101"], partNumbers.Order().ToList());
}

protected class Context38615(DbContextOptions options) : DbContext(options)
{
public class Car
{
public int CarId { get; set; }
public string Vin { get; set; }
public string DealerId { get; set; }
public CarConfiguration CarConfiguration { get; set; }
}

public class CarConfiguration
{
public string CurrentTrim { get; set; }
public List<OptionPackage> OptionPackages { get; set; }
}

public class OptionPackage
{
public string PackageId { get; set; }
public ICollection<string> PartNumbers { get; set; }
}
}

#endregion 38615

protected TestSqlLoggerFactory TestSqlLoggerFactory
=> (TestSqlLoggerFactory)ListLoggerFactory;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -680,4 +680,60 @@ FROM [MyEntity] AS [m]
INNER JOIN [OtherTable] AS [o] ON [m].[Id] = [o].[Id]
""");
}

public override async Task SelectMany_over_primitive_collection_nested_in_complex_collection_inside_json_column()
{
await base.SelectMany_over_primitive_collection_nested_in_complex_collection_inside_json_column();

switch (JsonColumnType)
{
case "json":
if (Microsoft.EntityFrameworkCore.TestUtilities.TestEnvironment.IsAzureSql)
{
AssertSql(
"""
SELECT [p].[value]
FROM [Cars] AS [c]
CROSS APPLY OPENJSON([c].[CarConfiguration], '$.optionPackages') WITH (
[packageId] nvarchar(max) '$.packageId',
[partNumbers] nvarchar(max) '$.partNumbers' AS JSON
) AS [o]
CROSS APPLY OPENJSON([o].[partNumbers]) WITH ([value] varchar(32) '$') AS [p]
WHERE [c].[Vin] = '1FA6P8TH8J5123456' AND [c].[DealerId] = 'DEALER-001' AND [o].[packageId] = N'PKG-SPORT'
""");
}
else
{
AssertSql(
"""
SELECT [p].[value]
FROM [Cars] AS [c]
CROSS APPLY OPENJSON([c].[CarConfiguration], '$.optionPackages') WITH (
[packageId] nvarchar(max) '$.packageId',
[partNumbers] json '$.partNumbers' AS JSON
) AS [o]
CROSS APPLY OPENJSON([o].[partNumbers]) WITH ([value] varchar(32) '$') AS [p]
WHERE [c].[Vin] = '1FA6P8TH8J5123456' AND [c].[DealerId] = 'DEALER-001' AND [o].[packageId] = N'PKG-SPORT'
""");
}

Comment thread
Copilot marked this conversation as resolved.
break;
case "nvarchar(max)":
AssertSql(
"""
SELECT [p].[value]
FROM [Cars] AS [c]
CROSS APPLY OPENJSON([c].[CarConfiguration], '$.optionPackages') WITH (
[packageId] nvarchar(max) '$.packageId',
[partNumbers] nvarchar(max) '$.partNumbers' AS JSON
) AS [o]
CROSS APPLY OPENJSON([o].[partNumbers]) WITH ([value] varchar(32) '$') AS [p]
WHERE [c].[Vin] = '1FA6P8TH8J5123456' AND [c].[DealerId] = 'DEALER-001' AND [o].[packageId] = N'PKG-SPORT'
""");
break;

default:
throw new UnreachableException();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -319,4 +319,8 @@ INSERT INTO Entities (Id, Scenario, OptionalReference, RequiredReference, Collec
]')
""");
}

public override Task SelectMany_over_primitive_collection_nested_in_complex_collection_inside_json_column()
=> Assert.ThrowsAsync<InvalidOperationException>(
base.SelectMany_over_primitive_collection_nested_in_complex_collection_inside_json_column);
}
Loading