Skip to content

Commit a23998f

Browse files
committed
fix: construction of relationship dictionaries for root layer
1 parent a11c9e9 commit a23998f

File tree

4 files changed

+217
-203
lines changed

4 files changed

+217
-203
lines changed

src/JsonApiDotNetCore/Hooks/Execution/RelationshipsDictionary.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@ public interface IRelationshipsDictionary { }
2222
public interface IByAffectedRelationships<TDependentResource> :
2323
IRelationshipGetters<TDependentResource> where TDependentResource : class, IIdentifiable
2424
{
25+
/// todo: expose getters that behave something like this:
26+
/// relationshipDictionary.GetAffected( entity => entity.NavigationProperty ).
27+
/// see https://stackoverflow.com/a/17116267/4441216
28+
2529
/// <summary>
2630
/// Gets a dictionary of affected resources grouped by affected relationships.
2731
/// </summary>
@@ -39,16 +43,16 @@ public interface IRelationshipsDictionary<TDependentResource> :
3943
/// <summary>
4044
/// A helper class that provides insights in which relationships have been updated for which entities.
4145
/// </summary>
42-
public interface IRelationshipGetters<TDependentResource> where TDependentResource : class, IIdentifiable
46+
public interface IRelationshipGetters<TResource> where TResource : class, IIdentifiable
4347
{
4448
/// <summary>
4549
/// Gets a dictionary of all entities that have an affected relationship to type <typeparamref name="TPrincipalResource"/>
4650
/// </summary>
47-
Dictionary<RelationshipAttribute, HashSet<TDependentResource>> GetByRelationship<TPrincipalResource>() where TPrincipalResource : class, IIdentifiable;
51+
Dictionary<RelationshipAttribute, HashSet<TResource>> GetByRelationship<TRelatedResource>() where TRelatedResource : class, IIdentifiable;
4852
/// <summary>
4953
/// Gets a dictionary of all entities that have an affected relationship to type <paramref name="principalType"/>
5054
/// </summary>
51-
Dictionary<RelationshipAttribute, HashSet<TDependentResource>> GetByRelationship(Type principalType);
55+
Dictionary<RelationshipAttribute, HashSet<TResource>> GetByRelationship(Type relatedResourceType);
5256
}
5357

5458
/// <summary>
@@ -82,7 +86,7 @@ public Dictionary<RelationshipAttribute, HashSet<TResource>> GetByRelationship<T
8286
/// <inheritdoc />
8387
public Dictionary<RelationshipAttribute, HashSet<TResource>> GetByRelationship(Type relatedType)
8488
{
85-
return this.Where(p => p.Key.PrincipalType == relatedType).ToDictionary(p => p.Key, p => p.Value);
89+
return this.Where(p => p.Key.DependentType == relatedType).ToDictionary(p => p.Key, p => p.Value);
8690
}
8791
}
8892
}

src/JsonApiDotNetCore/Hooks/Traversal/TraversalHelper.cs

Lines changed: 32 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public RootNode<TEntity> CreateRootNode<TEntity>(IEnumerable<TEntity> rootEntiti
5757
_processedEntities = new Dictionary<DependentType, HashSet<IIdentifiable>>();
5858
RegisterRelationshipProxies(typeof(TEntity));
5959
var uniqueEntities = ProcessEntities(rootEntities);
60-
var relationshipsToNextLayer = GetRelationships(typeof(TEntity));
60+
var relationshipsToNextLayer = GetPopulatedRelationships(typeof(TEntity), uniqueEntities.Cast<IIdentifiable>());
6161
return new RootNode<TEntity>(uniqueEntities, relationshipsToNextLayer);
6262
}
6363

@@ -80,30 +80,38 @@ public EntityChildLayer CreateNextLayer(IEnumerable<IEntityNode> nodes)
8080
{
8181
/// first extract entities by parsing populated relationships in the entities
8282
/// of previous layer
83-
(var dependents, var principals) = ExtractEntities(nodes);
83+
(var principals, var dependents) = ExtractEntities(nodes);
8484

85-
/// group them conveniently so we can make ChildNodes of them
86-
var dependentsGrouped = GroupByDependentTypeOfRelationship(dependents);
85+
/// group them conveniently so we can make ChildNodes of them:
86+
/// there might be several relationship attributes in dependents dictionary
87+
/// that point to the same dependent type.
88+
var principalsGrouped = GroupByDependentTypeOfRelationship(principals);
8789

8890
/// convert the groups into child nodes
89-
var nextNodes = dependentsGrouped.Select(entry =>
91+
var nextNodes = principalsGrouped.Select(entry =>
9092
{
9193
var nextNodeType = entry.Key;
94+
var populatedRelationships = new List<RelationshipProxy>();
9295
var relationshipsToPreviousLayer = entry.Value.Select(grouped =>
9396
{
9497
var proxy = grouped.Key;
95-
return CreateRelationsipGroupInstance(nextNodeType, proxy, grouped.Value, principals[proxy]);
98+
populatedRelationships.AddRange(GetPopulatedRelationships(nextNodeType, dependents[proxy]));
99+
return CreateRelationshipGroupInstance(nextNodeType, proxy, grouped.Value, dependents[proxy]);
96100
}).ToList();
97101

98102
RegisterRelationshipProxies(nextNodeType);
99-
return CreateNodeInstance(nextNodeType, GetRelationships(nextNodeType), relationshipsToPreviousLayer);
103+
return CreateNodeInstance(nextNodeType, populatedRelationships.ToArray(), relationshipsToPreviousLayer);
100104
}).ToList();
101105

102106
/// wrap the child nodes in a EntityChildLayer
103107
return new EntityChildLayer(nextNodes);
104108
}
105109

106110

111+
/// <summary>
112+
/// iterates throug the <paramref name="relationships"/> dictinary and groups the values
113+
/// by matching dependent type of the keys (which are relationshipattributes)
114+
/// </summary>
107115
Dictionary<DependentType, List<KeyValuePair<RelationshipProxy, List<IIdentifiable>>>> GroupByDependentTypeOfRelationship(Dictionary<RelationshipProxy, List<IIdentifiable>> relationships)
108116
{
109117
return relationships.GroupBy(kvp => kvp.Key.DependentType).ToDictionary(gdc => gdc.Key, gdc => gdc.ToList());
@@ -115,11 +123,8 @@ Dictionary<DependentType, List<KeyValuePair<RelationshipProxy, List<IIdentifiabl
115123
/// </summary>
116124
(Dictionary<RelationshipProxy, List<IIdentifiable>>, Dictionary<RelationshipProxy, List<IIdentifiable>>) ExtractEntities(IEnumerable<IEntityNode> principalNodes)
117125
{
118-
var currentLayerEntities = new List<IIdentifiable>();
119-
var principalsGrouped = new Dictionary<RelationshipProxy, List<IIdentifiable>>();
120-
var dependentsGrouped = new Dictionary<RelationshipProxy, List<IIdentifiable>>();
121-
122-
principalNodes.ForEach(n => RegisterRelationshipProxies(n.EntityType));
126+
var principalsEntitiesGrouped = new Dictionary<RelationshipProxy, List<IIdentifiable>>(); // RelationshipAttr_prevlayer->currentlayer => prevLayerEntities
127+
var dependentsEntitiesGrouped = new Dictionary<RelationshipProxy, List<IIdentifiable>>(); // RelationshipAttr_prevlayer->currentlayer => currentLayerEntities
123128

124129
foreach (var node in principalNodes)
125130
{
@@ -141,36 +146,36 @@ Dictionary<DependentType, List<KeyValuePair<RelationshipProxy, List<IIdentifiabl
141146
dependentEntities = list;
142147
}
143148

144-
var newDependentEntities = UniqueInTree(dependentEntities.Cast<IIdentifiable>(), proxy.DependentType);
145-
if (proxy.IsContextRelation || newDependentEntities.Any())
149+
var uniqueDependentEntities = UniqueInTree(dependentEntities.Cast<IIdentifiable>(), proxy.DependentType);
150+
if (proxy.IsContextRelation || uniqueDependentEntities.Any())
146151
{
147-
currentLayerEntities.AddRange(newDependentEntities);
148-
AddToRelationshipGroup(dependentsGrouped, proxy, newDependentEntities); // TODO check if this needs to be newDependentEntities or just dependentEntities
149-
AddToRelationshipGroup(principalsGrouped, proxy, new IIdentifiable[] { principalEntity });
152+
AddToRelationshipGroup(dependentsEntitiesGrouped, proxy, uniqueDependentEntities);
153+
AddToRelationshipGroup(principalsEntitiesGrouped, proxy, new IIdentifiable[] { principalEntity });
150154
}
151155
}
152156
}
153157
}
154158

155-
var processEntities = GetType().GetMethod(nameof(ProcessEntities), BindingFlags.NonPublic | BindingFlags.Instance);
156-
foreach (var kvp in dependentsGrouped)
159+
var processEntitiesMethod = GetType().GetMethod(nameof(ProcessEntities), BindingFlags.NonPublic | BindingFlags.Instance);
160+
foreach (var kvp in dependentsEntitiesGrouped)
157161
{
158162
var type = kvp.Key.DependentType;
159163
var list = kvp.Value.Cast(type);
160-
processEntities.MakeGenericMethod(type).Invoke(this, new object[] { list });
164+
processEntitiesMethod.MakeGenericMethod(type).Invoke(this, new object[] { list });
161165
}
162166

163-
return (principalsGrouped, dependentsGrouped);
167+
return (principalsEntitiesGrouped, dependentsEntitiesGrouped);
164168
}
165169

166170
/// <summary>
167-
/// Get all relationships known in the current tree traversal from a
171+
/// Get all populated relationships known in the current tree traversal from a
168172
/// principal type to any dependent type
169173
/// </summary>
170174
/// <returns>The relationships.</returns>
171-
RelationshipProxy[] GetRelationships(PrincipalType principal)
175+
RelationshipProxy[] GetPopulatedRelationships(PrincipalType principalType, IEnumerable<IIdentifiable> principals)
172176
{
173-
return RelationshipProxies.Select(entry => entry.Value).Where(proxy => proxy.PrincipalType == principal).ToArray();
177+
var relationshipsFromPrincipalToDependent = RelationshipProxies.Select(entry => entry.Value).Where(proxy => proxy.PrincipalType == principalType);
178+
return relationshipsFromPrincipalToDependent.Where(proxy => proxy.IsContextRelation || principals.Any(p => proxy.GetValue(p) != null)).ToArray();
174179
}
175180

176181
/// <summary>
@@ -196,8 +201,8 @@ void RegisterRelationshipProxies(DependentType type)
196201
if (!RelationshipProxies.TryGetValue(attr, out RelationshipProxy proxies))
197202
{
198203
DependentType dependentType = GetDependentTypeFromRelationship(attr);
199-
var isContextRelation = _context.RelationshipsToUpdate?.ContainsKey(attr);
200-
var proxy = new RelationshipProxy(attr, dependentType, isContextRelation != null && (bool)isContextRelation);
204+
var isContextRelation = (_context.RelationshipsToUpdate?.ContainsKey(attr)) != null;
205+
var proxy = new RelationshipProxy(attr, dependentType, isContextRelation);
201206
RelationshipProxies[attr] = proxy;
202207
}
203208
}
@@ -291,7 +296,7 @@ IRelationshipsFromPreviousLayer CreateRelationshipsFromInstance(DependentType no
291296
/// <summary>
292297
/// Reflective helper method to create an instance of <see cref="RelationshipGroup{TDependent}"/>;
293298
/// </summary>
294-
IRelationshipGroup CreateRelationsipGroupInstance(Type thisLayerType, RelationshipProxy proxy, List<IIdentifiable> principalEntities, List<IIdentifiable> dependentEntities)
299+
IRelationshipGroup CreateRelationshipGroupInstance(Type thisLayerType, RelationshipProxy proxy, List<IIdentifiable> principalEntities, List<IIdentifiable> dependentEntities)
295300
{
296301
var dependentEntitiesHashed = TypeHelper.CreateInstanceOfOpenType(typeof(HashSet<>), thisLayerType, dependentEntities.Cast(thisLayerType));
297302
return (IRelationshipGroup)TypeHelper.CreateInstanceOfOpenType(typeof(RelationshipGroup<>),

0 commit comments

Comments
 (0)