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 @@ -21,6 +21,9 @@ public partial class InternalEntryBase
internal static readonly bool UseOldBehavior38299 =
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue38299", out var enabled) && enabled;

internal static readonly bool UseOldBehavior39073 =
AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue39073", out var enabled) && enabled;

private struct InternalComplexCollectionEntry(InternalEntryBase entry, IComplexProperty complexCollection)
{
private static readonly bool UseOldBehavior37585 =
Expand All @@ -46,7 +49,8 @@ private struct InternalComplexCollectionEntry(InternalEntryBase entry, IComplexP
&& (defaultState != EntityState.Deleted || original)
&& (defaultState != EntityState.Added || !original))
{
for (var i = 0; i < entries.Count; i++)
var count = UseOldBehavior39073 ? entries.Count : collection.Count;
for (var i = 0; i < count; i++)
{
if (entries[i] != null)
{
Expand Down Expand Up @@ -151,7 +155,7 @@ private struct InternalComplexCollectionEntry(InternalEntryBase entry, IComplexP
return _entries;
}

private IList? GetCollection(bool original)
public IList? GetCollection(bool original)
{
if (!UseOldBehavior37585
&& _containingEntry is InternalComplexEntry complexEntry)
Expand Down Expand Up @@ -405,14 +409,20 @@ public void SetState(EntityState oldState, EntityState newState, bool acceptChan
setOriginalState = true;
}

EnsureCapacity(GetCollection(original: true)?.Count ?? 0, original: true, trim: false);
EnsureCapacity(GetCollection(original: false)?.Count ?? 0, original: false, trim: false);
var originalCollectionCount = GetCollection(original: true)?.Count ?? 0;
var currentCollectionCount = GetCollection(original: false)?.Count ?? 0;
EnsureCapacity(originalCollectionCount, original: true, trim: false);
EnsureCapacity(currentCollectionCount, original: false, trim: false);

var defaultState = newState == EntityState.Modified && !modifyProperties
? EntityState.Unchanged
: newState;
var originalEntries = GetOrCreateEntries(original: true, defaultState).ToArray();
var currentEntries = GetOrCreateEntries(original: false, defaultState).ToArray();
var originalEntries = GetOrCreateEntries(original: true, defaultState)
.Take(UseOldBehavior39073 ? int.MaxValue : originalCollectionCount)
.ToArray();
var currentEntries = GetOrCreateEntries(original: false, defaultState)
.Take(UseOldBehavior39073 ? int.MaxValue : currentCollectionCount)
.ToArray();
if (setOriginalState)
{
foreach (var originalEntry in originalEntries)
Expand Down
6 changes: 5 additions & 1 deletion src/EFCore/ChangeTracking/Internal/InternalEntryBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1165,7 +1165,11 @@ public InternalComplexEntry GetComplexCollectionOriginalEntry(IComplexProperty p
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public IEnumerable<InternalComplexEntry> GetFlattenedComplexEntries()
=> _complexCollectionEntries.SelectMany(c => c.GetOrCreateEntries(original: false)).Where(e => e != null)!;
=> _complexCollectionEntries
.SelectMany(
c => c.GetOrCreateEntries(original: false)
.Take(UseOldBehavior39073 ? int.MaxValue : c.GetCollection(original: false)?.Count ?? 0))
.Where(e => e != null)!;

/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -767,6 +767,43 @@ public void DetectChanges_detects_changes_in_nested_complex_collections()
Assert.Equal(EntityState.Added, items[2].State);
}

[ConditionalFact]
public void Entity_can_be_marked_modified_after_nested_complex_collection_shrinks()
Comment thread
AndriySvyryd marked this conversation as resolved.
{
var model = CreateModelWithNestedComplexCollection();
var complexProperty = model.FindEntityType(typeof(BlogWithNestedCollection))!
.FindComplexProperty(nameof(BlogWithNestedCollection.Items))!;
var serviceProvider = InMemoryTestHelpers.Instance.CreateContextServices(model);
var stateManager = serviceProvider.GetRequiredService<IStateManager>();
var changeDetector = serviceProvider.GetRequiredService<IChangeDetector>();

var blog = new BlogWithNestedCollection
{
Items =
[
new NestedCollectionItem { Name = "foo" },
new NestedCollectionItem
{
Name = "bar",
NestedItems = [new NestedItem { Name = "baz" }]
}
]
};

var entityEntry = stateManager.GetOrCreateEntry(blog);
entityEntry.SetEntityState(EntityState.Unchanged);

blog.Items = [new NestedCollectionItem { Name = "foo" }];

entityEntry.SetEntityState(EntityState.Modified);

Assert.Equal(EntityState.Modified, entityEntry.EntityState);

changeDetector.DetectChanges(stateManager);

Assert.Equal(EntityState.Deleted, entityEntry.GetComplexCollectionOriginalEntry(complexProperty, 1).EntityState);
}

private static IModel CreateModel()
{
var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder();
Expand All @@ -781,6 +818,18 @@ private static IModel CreateModel()
return modelBuilder.FinalizeModel();
}

private static IModel CreateModelWithNestedComplexCollection()
{
var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder();

modelBuilder.Entity<BlogWithNestedCollection>(
b => b.ComplexCollection(
e => e.Items,
b => b.ComplexCollection(e => e.NestedItems)));

return modelBuilder.FinalizeModel();
}

private static IModel CreateModelWithNestedComplexCollections()
{
var modelBuilder = InMemoryTestHelpers.Instance.CreateConventionBuilder();
Expand Down Expand Up @@ -831,6 +880,18 @@ private class BlogWithNested
public NestedJson NestedJson { get; set; } = new();
}

private class BlogWithNestedCollection
{
public int Id { get; set; }
public List<NestedCollectionItem> Items { get; set; } = [];
}

private class NestedCollectionItem
{
public string Name { get; set; } = "";
public List<NestedItem> NestedItems { get; set; } = [];
}

private record NestedJson
{
public NestedItem Item { get; init; } = new();
Expand Down
Loading