Skip to content

Commit cbc8c95

Browse files
committed
Add change tracking support for complex collections
Part of #31237
1 parent 202d207 commit cbc8c95

File tree

153 files changed

+14490
-9432
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

153 files changed

+14490
-9432
lines changed

src/EFCore.Design/Scaffolding/Internal/CSharpRuntimeModelCodeGenerator.cs

Lines changed: 54 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1497,40 +1497,45 @@ private void
14971497
var variableName = parameters.TargetName;
14981498
var mainBuilder = parameters.MainBuilder;
14991499
var unsafeAccessors = new HashSet<string>();
1500-
var isOnComplexCollection = property.DeclaringType is IReadOnlyComplexType complexType && complexType.ComplexProperty.IsCollection;
15011500

15021501
if (!property.IsShadowProperty()
1503-
&& !isOnComplexCollection
15041502
&& property is not IServiceProperty) // Service properties don't use property accessors
15051503
{
15061504
ClrPropertyGetterFactory.Instance.Create(
15071505
property,
1508-
out var getterExpression,
1509-
out var hasSentinelExpression,
1510-
out var structuralGetterExpression,
1511-
out var hasStructuralSentinelExpression);
1506+
out var getClrValueUsingContainingEntityExpression,
1507+
out var hasSentinelValueUsingContainingEntityExpression,
1508+
out var getClrValueExpression,
1509+
out var hasSentinelValueExpression);
15121510

15131511
mainBuilder
15141512
.Append(variableName).AppendLine(".SetGetter(")
1515-
.IncrementIndent()
1516-
.AppendLines(
1517-
_code.Expression(
1518-
getterExpression, parameters.Namespaces, unsafeAccessors,
1519-
(IReadOnlyDictionary<object, string>)parameters.ScopeVariables, memberAccessReplacements), skipFinalNewline: true)
1520-
.AppendLine(",")
1521-
.AppendLines(
1522-
_code.Expression(
1523-
hasSentinelExpression, parameters.Namespaces, unsafeAccessors,
1524-
(IReadOnlyDictionary<object, string>)parameters.ScopeVariables, memberAccessReplacements), skipFinalNewline: true)
1525-
.AppendLine(",")
1513+
.IncrementIndent();
1514+
if (property.DeclaringType is not IEntityType)
1515+
{
1516+
mainBuilder
1517+
.AppendLines(
1518+
_code.Expression(
1519+
getClrValueUsingContainingEntityExpression, parameters.Namespaces, unsafeAccessors,
1520+
(IReadOnlyDictionary<object, string>)parameters.ScopeVariables, memberAccessReplacements), skipFinalNewline: true)
1521+
.AppendLine(",")
1522+
.AppendLines(
1523+
_code.Expression(
1524+
hasSentinelValueUsingContainingEntityExpression, parameters.Namespaces, unsafeAccessors,
1525+
(IReadOnlyDictionary<object, string>)parameters.ScopeVariables, memberAccessReplacements), skipFinalNewline: true)
1526+
.AppendLine(",");
1527+
}
1528+
1529+
// For properties declared on entity types, use only two parameters
1530+
mainBuilder
15261531
.AppendLines(
15271532
_code.Expression(
1528-
structuralGetterExpression, parameters.Namespaces, unsafeAccessors,
1533+
getClrValueExpression, parameters.Namespaces, unsafeAccessors,
15291534
(IReadOnlyDictionary<object, string>)parameters.ScopeVariables, memberAccessReplacements), skipFinalNewline: true)
15301535
.AppendLine(",")
15311536
.AppendLines(
15321537
_code.Expression(
1533-
hasStructuralSentinelExpression, parameters.Namespaces, unsafeAccessors,
1538+
hasSentinelValueExpression, parameters.Namespaces, unsafeAccessors,
15341539
(IReadOnlyDictionary<object, string>)parameters.ScopeVariables, memberAccessReplacements), skipFinalNewline: true)
15351540
.AppendLine(");")
15361541
.DecrementIndent();
@@ -1558,10 +1563,38 @@ private void
15581563
(IReadOnlyDictionary<object, string>)parameters.ScopeVariables, memberAccessReplacements), skipFinalNewline: true)
15591564
.AppendLine(");")
15601565
.DecrementIndent();
1566+
1567+
if (property.IsCollection
1568+
&& property is IComplexProperty)
1569+
{
1570+
ClrIndexedCollectionAccessorFactory.Instance.Create(
1571+
property,
1572+
out _, out _, out _,
1573+
out var get, out var set, out var setForMaterialization);
1574+
1575+
mainBuilder
1576+
.Append(variableName).AppendLine(".SetIndexedCollectionAccessor(")
1577+
.IncrementIndent()
1578+
.AppendLines(
1579+
_code.Expression(
1580+
get!, parameters.Namespaces, unsafeAccessors,
1581+
(IReadOnlyDictionary<object, string>)parameters.ScopeVariables, memberAccessReplacements), skipFinalNewline: true)
1582+
.AppendLine(",")
1583+
.AppendLines(
1584+
_code.Expression(
1585+
set!, parameters.Namespaces, unsafeAccessors,
1586+
(IReadOnlyDictionary<object, string>)parameters.ScopeVariables, memberAccessReplacements), skipFinalNewline: true)
1587+
.AppendLine(",")
1588+
.AppendLines(
1589+
_code.Expression(
1590+
setForMaterialization!, parameters.Namespaces, unsafeAccessors,
1591+
(IReadOnlyDictionary<object, string>)parameters.ScopeVariables, memberAccessReplacements), skipFinalNewline: true)
1592+
.AppendLine(");")
1593+
.DecrementIndent();
1594+
}
15611595
}
15621596

1563-
if (property is not IServiceProperty
1564-
&& !isOnComplexCollection)
1597+
if (property is not IServiceProperty)
15651598
{
15661599
PropertyAccessorsFactory.Instance.Create(
15671600
property,
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections;
5+
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
6+
using Microsoft.EntityFrameworkCore.Internal;
7+
using Microsoft.EntityFrameworkCore.Metadata.Internal;
8+
9+
namespace Microsoft.EntityFrameworkCore.ChangeTracking;
10+
11+
/// <summary>
12+
/// Provides access to change tracking and loading information for a collection
13+
/// navigation complexProperty that associates this entity to a collection of another entities.
14+
/// </summary>
15+
/// <remarks>
16+
/// <para>
17+
/// Instances of this class are returned from methods when using the <see cref="ChangeTracker" /> API and it is
18+
/// not designed to be directly constructed in your application code.
19+
/// </para>
20+
/// <para>
21+
/// See <see href="https://aka.ms/efcore-docs-entity-entries">Accessing tracked entities in EF Core</see>,
22+
/// <see href="https://aka.ms/efcore-docs-changing-relationships">Changing foreign keys and navigations</see>,
23+
/// and <see href="https://aka.ms/efcore-docs-load-related-data">Loading related entities</see> for more information and examples.
24+
/// </para>
25+
/// </remarks>
26+
public class ComplexCollectionEntry : MemberEntry
27+
{
28+
/// <summary>
29+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
30+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
31+
/// any release. You should only use it directly in your code with extreme caution and knowing that
32+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
33+
/// </summary>
34+
[EntityFrameworkInternal]
35+
public ComplexCollectionEntry(IInternalEntry internalEntry, IComplexProperty complexProperty)
36+
: base(internalEntry, complexProperty)
37+
{
38+
if (!complexProperty.IsCollection)
39+
{
40+
throw new InvalidOperationException(
41+
CoreStrings.ComplexCollectionIsReference(
42+
internalEntry.StructuralType.DisplayName(), complexProperty.Name,
43+
nameof(ChangeTracking.EntityEntry.ComplexCollection), nameof(ChangeTracking.EntityEntry.ComplexProperty)));
44+
}
45+
46+
DetectChanges();
47+
}
48+
49+
private void DetectChanges()
50+
{
51+
var context = InternalEntry.Context;
52+
if (!context.ChangeTracker.AutoDetectChangesEnabled
53+
|| ((IRuntimeModel)context.Model).SkipDetectChanges)
54+
{
55+
return;
56+
}
57+
58+
var changeDetector = context.GetDependencies().ChangeDetector;
59+
foreach (var complexEntry in InternalEntry.GetFlattenedComplexEntries())
60+
{
61+
changeDetector.DetectChanges(complexEntry);
62+
}
63+
}
64+
65+
/// <summary>
66+
/// Gets or sets the value currently assigned to this complexProperty. If the current value is set using this complexProperty,
67+
/// the change tracker is aware of the change and <see cref="ChangeTracker.DetectChanges" /> is not required
68+
/// for the context to detect the change.
69+
/// </summary>
70+
/// <remarks>
71+
/// See <see href="https://aka.ms/efcore-docs-entity-entries">Accessing tracked entities in EF Core</see>
72+
/// and <see href="https://aka.ms/efcore-docs-changing-relationships">Changing foreign keys and navigations</see>
73+
/// for more information and examples.
74+
/// </remarks>
75+
public new virtual IEnumerable? CurrentValue
76+
{
77+
get => this.GetInfrastructure().GetCurrentValue<IEnumerable?>(Metadata);
78+
set => base.CurrentValue = value;
79+
}
80+
81+
/// <summary>
82+
/// Gets a <see cref="ComplexEntry"/> for the complex item at the specified ordinal.
83+
/// </summary>
84+
/// <param name="ordinal">The ordinal of the complex item to access.</param>
85+
/// <returns>A <see cref="ComplexEntry"/> for the complex item at the specified ordinal.</returns>
86+
public virtual ComplexEntry this[int ordinal]
87+
=> new(InternalEntry.GetComplexCollectionEntry(Metadata, ordinal));
88+
89+
/// <summary>
90+
/// Gets a <see cref="ComplexEntry"/> for the original complex item at the specified ordinal.
91+
/// </summary>
92+
/// <param name="ordinal">The original ordinal of the complex item to access.</param>
93+
/// <returns>A <see cref="ComplexEntry"/> for the complex item at the specified original ordinal.</returns>
94+
public virtual ComplexEntry GetOriginalEntry(int ordinal)
95+
=> new(InternalEntry.GetComplexCollectionOriginalEntry(Metadata, ordinal));
96+
97+
/// <summary>
98+
/// Gets the metadata that describes the facets of this property and how it maps to the database.
99+
/// </summary>
100+
public new virtual IComplexProperty Metadata
101+
=> (IComplexProperty)base.Metadata;
102+
103+
/// <summary>
104+
/// Gets or sets a value indicating whether any of foreign key complexProperty values associated
105+
/// with this navigation complexProperty have been modified and should be updated in the database
106+
/// when <see cref="DbContext.SaveChanges()" /> is called.
107+
/// </summary>
108+
/// <remarks>
109+
/// See <see href="https://aka.ms/efcore-docs-entity-entries">Accessing tracked entities in EF Core</see>
110+
/// and <see href="https://aka.ms/efcore-docs-changing-relationships">Changing foreign keys and navigations</see>
111+
/// for more information and examples.
112+
/// </remarks>
113+
public override bool IsModified
114+
{
115+
get => InternalEntry.IsModified(Metadata);
116+
set => InternalEntry.SetPropertyModified(Metadata, isModified: value, recurse: true);
117+
}
118+
119+
/// <summary>
120+
/// Gets an enumerator over all complex entries in this collection.
121+
/// </summary>
122+
/// <returns>An enumerator over all complex entries in this collection.</returns>
123+
public virtual IEnumerator<ComplexEntry> GetEnumerator()
124+
{
125+
var currentValue = CurrentValue;
126+
if (currentValue == null)
127+
{
128+
yield break;
129+
}
130+
131+
foreach (var complexEntry in InternalEntry.GetFlattenedComplexEntries())
132+
{
133+
yield return new ComplexEntry(complexEntry);
134+
}
135+
}
136+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Collections.Generic;
5+
using Microsoft.EntityFrameworkCore.ChangeTracking.Internal;
6+
7+
namespace Microsoft.EntityFrameworkCore.ChangeTracking;
8+
9+
/// <summary>
10+
/// Provides access to change tracking and loading information for a collection
11+
/// navigation property that associates this entity to a collection of another entities.
12+
/// </summary>
13+
/// <remarks>
14+
/// <para>
15+
/// Instances of this class are returned from methods when using the <see cref="ChangeTracker" /> API and it is
16+
/// not designed to be directly constructed in your application code.
17+
/// </para>
18+
/// <para>
19+
/// See <see href="https://aka.ms/efcore-docs-entity-entries">Accessing tracked entities in EF Core</see>,
20+
/// <see href="https://aka.ms/efcore-docs-changing-relationships">Changing foreign keys and navigations</see>,
21+
/// and <see href="https://aka.ms/efcore-docs-load-related-data">Loading related entities</see> for more information and examples.
22+
/// </para>
23+
/// </remarks>
24+
/// <typeparam name="TEntity">The type of the entity the property belongs to.</typeparam>
25+
/// <typeparam name="TElement">The element type.</typeparam>
26+
public class ComplexCollectionEntry<TEntity, TElement> : ComplexCollectionEntry
27+
where TEntity : class
28+
where TElement : notnull
29+
{
30+
/// <summary>
31+
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
32+
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
33+
/// any release. You should only use it directly in your code with extreme caution and knowing that
34+
/// doing so can result in application failures when updating to a new Entity Framework Core release.
35+
/// </summary>
36+
[EntityFrameworkInternal]
37+
public ComplexCollectionEntry(IInternalEntry internalEntry, IComplexProperty property)
38+
: base(internalEntry, property)
39+
{
40+
}
41+
42+
/// <summary>
43+
/// The <see cref="EntityEntry{TEntity}" /> to which this member belongs.
44+
/// </summary>
45+
/// <remarks>
46+
/// See <see href="https://aka.ms/efcore-docs-entity-entries">Accessing tracked entities in EF Core</see> for more information and
47+
/// examples.
48+
/// </remarks>
49+
/// <value> An entry for the entity that owns this member. </value>
50+
public override EntityEntry<TEntity> EntityEntry
51+
=> new(InternalEntry.EntityEntry);
52+
53+
/// <summary>
54+
/// Gets or sets the value currently assigned to this property. If the current value is set using this property,
55+
/// the change tracker is aware of the change and <see cref="ChangeTracker.DetectChanges" /> is not required
56+
/// for the context to detect the change.
57+
/// </summary>
58+
/// <remarks>
59+
/// See <see href="https://aka.ms/efcore-docs-entity-entries">Accessing tracked entities in EF Core</see>
60+
/// and <see href="https://aka.ms/efcore-docs-changing-relationships">Changing foreign keys and navigations</see>
61+
/// for more information and examples.
62+
/// </remarks>
63+
public new virtual IReadOnlyList<TElement>? CurrentValue
64+
{
65+
get => this.GetInfrastructure().GetCurrentValue<IReadOnlyList<TElement>?>(Metadata);
66+
set => base.CurrentValue = value;
67+
}
68+
69+
/// <summary>
70+
/// Gets a <see cref="ComplexEntry{TEntity, TElement}"/> for the complex item at the specified ordinal.
71+
/// </summary>
72+
/// <param name="ordinal">The ordinal of the complex item to access.</param>
73+
/// <returns>A <see cref="ComplexEntry{TEntity, TElement}"/> for the complex item at the specified ordinal.</returns>
74+
public override ComplexEntry<TEntity, TElement> this[int ordinal]
75+
=> new(InternalEntry.GetComplexCollectionEntry(Metadata, ordinal));
76+
77+
/// <summary>
78+
/// Gets a <see cref="ComplexEntry{TEntity, TElement}"/> for the complex item at the specified original ordinal.
79+
/// </summary>
80+
/// <param name="ordinal">The original ordinal of the complex item to access.</param>
81+
/// <returns>A <see cref="ComplexEntry{TEntity, TElement}"/> for the original complex item at the specified ordinal.</returns>
82+
public override ComplexEntry<TEntity, TElement> GetOriginalEntry(int ordinal)
83+
=> new(InternalEntry.GetComplexCollectionOriginalEntry(Metadata, ordinal));
84+
}

0 commit comments

Comments
 (0)