Skip to content
Open
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 @@ -247,7 +247,13 @@ protected virtual void ValidateIndexIncludeProperties(IIndex index)
{
#pragma warning disable EF1001 // Internal EF Core API usage.
var notFound = includeProperties
.FirstOrDefault(i => RelationalModel.FindPropertyByPath(index.DeclaringEntityType, i) == null);
.FirstOrDefault(i =>
{
var propertyBase = RelationalModel.FindPropertyBaseByPath(index.DeclaringEntityType, i);
return propertyBase == null
|| (propertyBase is IComplexProperty complexProperty
&& !complexProperty.ComplexType.IsMappedToJson());
});
#pragma warning restore EF1001 // Internal EF Core API usage.
Comment thread
AndriySvyryd marked this conversation as resolved.

if (notFound != null)
Expand All @@ -259,6 +265,21 @@ protected virtual void ValidateIndexIncludeProperties(IIndex index)
index.DeclaringEntityType.DisplayName()));
}

foreach (var includeProperty in includeProperties)
{
#pragma warning disable EF1001 // Internal EF Core API usage.
var propertyBase = RelationalModel.FindPropertyBaseByPath(index.DeclaringEntityType, includeProperty)!;
#pragma warning restore EF1001 // Internal EF Core API usage.
if (propertyBase.DeclaringType.IsMappedToJson())
{
Comment thread
AndriySvyryd marked this conversation as resolved.
throw new InvalidOperationException(
SqlServerStrings.IncludePropertyInJsonMappedType(
includeProperty,
index.DisplayName(),
index.DeclaringEntityType.DisplayName()));
}
}

var duplicateProperty = includeProperties
.GroupBy(i => i)
.Where(g => g.Count() > 1)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -269,9 +269,15 @@ public override IEnumerable<IAnnotation> For(ITableIndex index, bool designTime)
if (modelIndex.GetIncludeProperties(table) is { } includeProperties)
{
#pragma warning disable EF1001 // Internal EF Core API usage.
var storeObjectIdentifier = StoreObjectIdentifier.Table(table.Name, table.Schema);
var includeColumns = includeProperties
.Select(p => RelationalModel.FindPropertyByPath(modelIndex.DeclaringEntityType, p)!
.GetColumnName(StoreObjectIdentifier.Table(table.Name, table.Schema)))
.Select(p =>
{
var propertyBase = RelationalModel.FindPropertyBaseByPath(modelIndex.DeclaringEntityType, p)!;
return propertyBase is IReadOnlyProperty property
? property.GetColumnName(storeObjectIdentifier)
: ((IReadOnlyComplexProperty)propertyBase).ComplexType.GetContainerColumnName();
})
.ToArray();
#pragma warning restore EF1001 // Internal EF Core API usage.

Expand Down

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

3 changes: 3 additions & 0 deletions src/EFCore.SqlServer/Properties/SqlServerStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,9 @@
<data name="IncludePropertyNotFound" xml:space="preserve">
<value>The include property '{property}' specified on the index {index} was not found on entity type '{entityType}'.</value>
</data>
<data name="IncludePropertyInJsonMappedType" xml:space="preserve">
<value>The include property '{property}' specified on the index {index} on entity type '{entityType}' is contained within a JSON-mapped type. Properties contained within JSON-mapped types cannot be included in an index.</value>
</data>
<data name="IncompatibleSqlOutputClauseMismatch" xml:space="preserve">
<value>Cannot use table '{table}' for entity type '{entityType}' since it is being used for entity type '{otherEntityType}' and entity type '{entityTypeWithSqlOutputClause}' is configured to use the SQL OUTPUT clause, but entity type '{entityTypeWithoutSqlOutputClause}' is not.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2357,6 +2357,49 @@ FROM [sys].[columns] [c]
""");
}

[Fact]
public virtual async Task Create_index_with_include_on_complex_property()
{
await Test(
builder => builder.Entity(
"People", e =>
{
e.Property<int>("Id");
e.HasKey("Id");
e.Property<string>("Name");
e.ComplexProperty<JsonIndexItem>(
"Details", cb =>
{
cb.Property(i => i.Value);
cb.Property(i => i.Other);
});
}),
builder => { },
builder => builder.Entity("People").HasIndex("Name")
.IncludeProperties("Details.Value"),
model =>
{
var table = Assert.Single(model.Tables);
var index = Assert.Single(table.Indexes);
Assert.Equal(1, index.Columns.Count);
Assert.Contains(table.Columns.Single(c => c.Name == "Name"), index.Columns);
});

AssertSql(
"""
DECLARE @var nvarchar(max);
SELECT @var = QUOTENAME(OBJECT_NAME([c].[default_object_id]))
FROM [sys].[columns] [c]
WHERE [c].[object_id] = OBJECT_ID(N'[People]') AND [c].[name] = N'Name';
IF @var IS NOT NULL EXEC(N'ALTER TABLE [People] DROP CONSTRAINT ' + @var + ';');
ALTER TABLE [People] ALTER COLUMN [Name] nvarchar(450) NULL;
""",
//
"""
CREATE INDEX [IX_People_Name] ON [People] ([Name]) INCLUDE ([Details_Value]);
""");
}

[Fact]
public virtual async Task Create_index_with_include_and_filter()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -602,12 +602,130 @@ public void IncludeProperties_dotted_path_not_found_throws()
modelBuilder);
}

[Fact]
public void IncludeProperties_on_non_json_complex_property_throws()
{
var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity<EntityWithIncludedComplex>(b =>
{
b.HasKey(e => e.Id);
b.ComplexProperty(e => e.Address, cb =>
{
cb.Property(a => a.City).IsRequired();
cb.Property(a => a.Street).IsRequired();
});
b.HasIndex(e => e.Id).IncludeProperties("Address");
});

VerifyError(
SqlServerStrings.IncludePropertyNotFound("Address", "{'Id'}", nameof(EntityWithIncludedComplex)),
modelBuilder);
}

[Fact]
public void IncludeProperties_inside_json_complex_collection_throws()
{
var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity<EntityWithIncludedComplexCollection>(b =>
{
b.HasKey(e => e.Id);
b.ComplexCollection(e => e.Addresses, cb =>
{
cb.ToJson();
cb.Property(a => a.City).IsRequired();
cb.Property(a => a.Street).IsRequired();
});
b.HasIndex(e => e.Id).IncludeProperties("Addresses.City");
});

VerifyError(
SqlServerStrings.IncludePropertyInJsonMappedType(
"Addresses.City", "{'Id'}", nameof(EntityWithIncludedComplexCollection)),
modelBuilder);
}

[Fact]
public void IncludeProperties_on_json_complex_collection_is_valid()
{
var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity<EntityWithIncludedComplexCollection>(b =>
{
b.HasKey(e => e.Id);
b.ComplexCollection(e => e.Addresses, cb =>
{
cb.ToJson();
cb.Property(a => a.City).IsRequired();
cb.Property(a => a.Street).IsRequired();
});
b.HasIndex(e => e.Id).IncludeProperties("Addresses");
});

var model = Validate(modelBuilder);
var index = model.FindEntityType(typeof(EntityWithIncludedComplexCollection))!.GetIndexes().Single();
Assert.Equal(["Addresses"], index.GetIncludeProperties());
}

[Fact]
public void IncludeProperties_inside_json_complex_property_throws()
{
var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity<EntityWithIncludedComplexJson>(b =>
{
b.HasKey(e => e.Id);
b.ComplexProperty(e => e.Address, cb =>
{
cb.ToJson();
cb.Property(a => a.City).IsRequired();
cb.Property(a => a.Street).IsRequired();
});
b.HasIndex(e => e.Id).IncludeProperties("Address.City");
});

VerifyError(
SqlServerStrings.IncludePropertyInJsonMappedType(
"Address.City", "{'Id'}", nameof(EntityWithIncludedComplexJson)),
modelBuilder);
}

[Fact]
public void IncludeProperties_on_json_complex_property_is_valid()
{
var modelBuilder = CreateConventionModelBuilder();
modelBuilder.Entity<EntityWithIncludedComplexJson>(b =>
{
b.HasKey(e => e.Id);
b.ComplexProperty(e => e.Address, cb =>
{
cb.ToJson();
cb.Property(a => a.City).IsRequired();
cb.Property(a => a.Street).IsRequired();
});
b.HasIndex(e => e.Id).IncludeProperties("Address");
});

var model = Validate(modelBuilder);
var index = model.FindEntityType(typeof(EntityWithIncludedComplexJson))!.GetIndexes().Single();
Assert.Equal(["Address"], index.GetIncludeProperties());
}

Comment thread
AndriySvyryd marked this conversation as resolved.
protected class EntityWithIncludedComplexJson
{
public int Id { get; set; }
public required EntityWithIncludedComplexAddress Address { get; set; }
}

protected class EntityWithIncludedComplex
{
public int Id { get; set; }
public required EntityWithIncludedComplexAddress Address { get; set; }
}

protected class EntityWithIncludedComplexCollection
{
public int Id { get; set; }
public required List<EntityWithIncludedComplexAddress> Addresses { get; set; }
}

protected class EntityWithIncludedComplexAddress
{
public required string City { get; set; }
Expand Down
Loading