Skip to content

Commit 3528e1f

Browse files
committed
first pass at deeply nested inclusion implementation
1 parent d1777cf commit 3528e1f

File tree

5 files changed

+68
-31
lines changed

5 files changed

+68
-31
lines changed

src/JsonApiDotNetCore/Builders/DocumentBuilder.cs

Lines changed: 27 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -192,20 +192,35 @@ private RelationshipData GetRelationshipData(RelationshipAttribute attr, Context
192192
return relationshipData;
193193
}
194194

195-
private List<DocumentData> GetIncludedEntities(List<DocumentData> included, ContextEntity contextEntity, IIdentifiable entity)
195+
private List<DocumentData> GetIncludedEntities(List<DocumentData> included, ContextEntity rootContextEntity, IIdentifiable rootResource)
196196
{
197-
contextEntity.Relationships.ForEach(r =>
197+
if(_jsonApiContext.IncludedRelationships != null)
198198
{
199-
if (!RelationshipIsIncluded(r.PublicRelationshipName)) return;
200-
201-
var navigationEntity = _jsonApiContext.ContextGraph.GetRelationship(entity, r.InternalRelationshipName);
202-
203-
if (navigationEntity is IEnumerable hasManyNavigationEntity)
204-
foreach (IIdentifiable includedEntity in hasManyNavigationEntity)
205-
included = AddIncludedEntity(included, includedEntity);
206-
else
207-
included = AddIncludedEntity(included, (IIdentifiable)navigationEntity);
208-
});
199+
foreach(var relationshipName in _jsonApiContext.IncludedRelationships)
200+
{
201+
var relationshipChain = relationshipName.Split('.');
202+
203+
var contextEntity = rootContextEntity;
204+
var entity = rootResource;
205+
206+
for(var i = 0; i < relationshipChain.Length; i++)
207+
{
208+
var requestedRelationship = relationshipChain[i];
209+
var relationship = contextEntity.Relationships.FirstOrDefault(r => r.PublicRelationshipName == requestedRelationship);
210+
var navigationEntity = _jsonApiContext.ContextGraph.GetRelationship(entity, relationship.InternalRelationshipName);
211+
if (navigationEntity is IEnumerable hasManyNavigationEntity)
212+
foreach (IIdentifiable includedEntity in hasManyNavigationEntity)
213+
included = AddIncludedEntity(included, includedEntity);
214+
else
215+
included = AddIncludedEntity(included, (IIdentifiable)navigationEntity);
216+
217+
if(i < relationshipChain.Length) {
218+
contextEntity = _jsonApiContext.ContextGraph.GetContextEntity(relationship.Type);
219+
entity = (IIdentifiable)navigationEntity; // HACK: only handles HasOne case ...
220+
}
221+
}
222+
}
223+
}
209224

210225
return included;
211226
}
@@ -245,12 +260,6 @@ private DocumentData GetIncludedEntity(IIdentifiable entity)
245260
return data;
246261
}
247262

248-
private bool RelationshipIsIncluded(string relationshipName)
249-
{
250-
return _jsonApiContext.IncludedRelationships != null &&
251-
_jsonApiContext.IncludedRelationships.Contains(relationshipName);
252-
}
253-
254263
private List<ResourceIdentifierObject> GetRelationships(IEnumerable<object> entities)
255264
{
256265
var objType = entities.GetElementType();

src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -203,20 +203,38 @@ public virtual async Task<bool> DeleteAsync(TId id)
203203
/// <inheritdoc />
204204
public virtual IQueryable<TEntity> Include(IQueryable<TEntity> entities, string relationshipName)
205205
{
206+
if(string.IsNullOrWhiteSpace(relationshipName)) throw new JsonApiException(400, "Include parameter must not be empty if provided");
207+
208+
var relationshipChain = relationshipName.Split('.');
209+
210+
// variables mutated in recursive loop
211+
// TODO: make recursive method
212+
string internalRelationshipPath = null;
206213
var entity = _jsonApiContext.RequestEntity;
207-
var relationship = entity.Relationships.FirstOrDefault(r => r.PublicRelationshipName == relationshipName);
208-
if (relationship == null)
214+
for(var i = 0; i < relationshipChain.Length; i++)
209215
{
210-
throw new JsonApiException(400, $"Invalid relationship {relationshipName} on {entity.EntityName}",
211-
$"{entity.EntityName} does not have a relationship named {relationshipName}");
212-
}
216+
var requestedRelationship = relationshipChain[i];
217+
var relationship = entity.Relationships.FirstOrDefault(r => r.PublicRelationshipName == requestedRelationship);
218+
if (relationship == null)
219+
{
220+
throw new JsonApiException(400, $"Invalid relationship {requestedRelationship} on {entity.EntityName}",
221+
$"{entity.EntityName} does not have a relationship named {requestedRelationship}");
222+
}
213223

214-
if (!relationship.CanInclude)
215-
{
216-
throw new JsonApiException(400, $"Including the relationship {relationshipName} on {entity.EntityName} is not allowed");
224+
if (relationship.CanInclude == false)
225+
{
226+
throw new JsonApiException(400, $"Including the relationship {requestedRelationship} on {entity.EntityName} is not allowed");
227+
}
228+
229+
internalRelationshipPath = (internalRelationshipPath == null)
230+
? relationship.InternalRelationshipName
231+
: $"{internalRelationshipPath}.{relationship.InternalRelationshipName}";
232+
233+
if(i < relationshipChain.Length)
234+
entity = _jsonApiContext.ContextGraph.GetContextEntity(relationship.Type);
217235
}
218236

219-
return entities.Include(relationship.InternalRelationshipName);
237+
return entities.Include(internalRelationshipPath);
220238
}
221239

222240
/// <inheritdoc />

src/JsonApiDotNetCore/Models/RelationshipAttribute.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ public bool TryGetHasMany(out HasManyAttribute result)
5454

5555
public abstract void SetValue(object entity, object newValue);
5656

57+
public object GetValue(object entity) => entity
58+
?.GetType()
59+
.GetProperty(InternalRelationshipName)
60+
.GetValue(entity);
61+
5762
public override string ToString()
5863
{
5964
return base.ToString() + ":" + PublicRelationshipName;

src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,15 @@ private object SetHasOneRelationship(object entity,
214214
SetHasOneForeignKeyValue(entity, attr, foreignKeyProperty, rio);
215215
SetHasOneNavigationPropertyValue(entity, attr, rio, included);
216216

217+
// recursive call ...
218+
var navigationPropertyValue = attr.GetValue(entity);
219+
var contextGraphEntity = _jsonApiContext.ContextGraph.GetContextEntity(attr.Type);
220+
if(navigationPropertyValue != null && contextGraphEntity != null)
221+
{
222+
var includedResource = included.Single(r => r.Type == rio.Type && r.Id == rio.Id);
223+
SetRelationships(navigationPropertyValue, contextGraphEntity, includedResource.Relationships, included);
224+
}
225+
217226
return entity;
218227
}
219228

src/JsonApiDotNetCore/Services/QueryParser.cs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -179,10 +179,6 @@ protected virtual List<SortQuery> ParseSortParameters(string value)
179179

180180
protected virtual List<string> ParseIncludedRelationships(string value)
181181
{
182-
const string NESTED_DELIMITER = ".";
183-
if (value.Contains(NESTED_DELIMITER))
184-
throw new JsonApiException(400, "Deeply nested relationships are not supported");
185-
186182
return value
187183
.Split(QueryConstants.COMMA)
188184
.ToList();

0 commit comments

Comments
 (0)