Skip to content

Commit b073a0b

Browse files
authored
Merge pull request json-api-dotnet#378 from json-api-dotnet/feat/json-api-dotnet#39
[WIP] Feat/json-api-dotnet#39: Deeply Nested Inclusions
2 parents c1bf919 + b0e4fa1 commit b073a0b

File tree

9 files changed

+835
-43
lines changed

9 files changed

+835
-43
lines changed

src/JsonApiDotNetCore/Builders/DocumentBuilder.cs

+48-16
Original file line numberDiff line numberDiff line change
@@ -192,24 +192,62 @@ 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;
199+
foreach(var relationshipName in _jsonApiContext.IncludedRelationships)
200+
{
201+
var relationshipChain = relationshipName.Split('.');
200202

201-
var navigationEntity = _jsonApiContext.ContextGraph.GetRelationship(entity, r.InternalRelationshipName);
203+
var contextEntity = rootContextEntity;
204+
var entity = rootResource;
205+
included = IncludeRelationshipChain(included, rootContextEntity, rootResource, relationshipChain, 0);
206+
}
207+
}
202208

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-
});
209+
return included;
210+
}
209211

212+
private List<DocumentData> IncludeRelationshipChain(
213+
List<DocumentData> included, ContextEntity parentEntity, IIdentifiable parentResource, string[] relationshipChain, int relationshipChainIndex)
214+
{
215+
var requestedRelationship = relationshipChain[relationshipChainIndex];
216+
var relationship = parentEntity.Relationships.FirstOrDefault(r => r.PublicRelationshipName == requestedRelationship);
217+
var navigationEntity = _jsonApiContext.ContextGraph.GetRelationship(parentResource, relationship.InternalRelationshipName);
218+
if (navigationEntity is IEnumerable hasManyNavigationEntity)
219+
{
220+
foreach (IIdentifiable includedEntity in hasManyNavigationEntity)
221+
{
222+
included = AddIncludedEntity(included, includedEntity);
223+
included = IncludeSingleResourceRelationships(included, includedEntity, relationship, relationshipChain, relationshipChainIndex);
224+
}
225+
}
226+
else
227+
{
228+
included = AddIncludedEntity(included, (IIdentifiable)navigationEntity);
229+
included = IncludeSingleResourceRelationships(included, (IIdentifiable)navigationEntity, relationship, relationshipChain, relationshipChainIndex);
230+
}
231+
232+
return included;
233+
}
234+
235+
private List<DocumentData> IncludeSingleResourceRelationships(
236+
List<DocumentData> included, IIdentifiable navigationEntity, RelationshipAttribute relationship, string[] relationshipChain, int relationshipChainIndex)
237+
{
238+
if(relationshipChainIndex < relationshipChain.Length)
239+
{
240+
var nextContextEntity = _jsonApiContext.ContextGraph.GetContextEntity(relationship.Type);
241+
var resource = (IIdentifiable)navigationEntity;
242+
// recursive call
243+
if(relationshipChainIndex < relationshipChain.Length - 1)
244+
included = IncludeRelationshipChain(included, nextContextEntity, resource, relationshipChain, relationshipChainIndex + 1);
245+
}
246+
210247
return included;
211248
}
212249

250+
213251
private List<DocumentData> AddIncludedEntity(List<DocumentData> entities, IIdentifiable entity)
214252
{
215253
var includedEntity = GetIncludedEntity(entity);
@@ -245,12 +283,6 @@ private DocumentData GetIncludedEntity(IIdentifiable entity)
245283
return data;
246284
}
247285

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

src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs

+27-9
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

+5
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

+13
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,19 @@ private object SetHasOneRelationship(object entity,
214214
SetHasOneForeignKeyValue(entity, attr, foreignKeyProperty, rio);
215215
SetHasOneNavigationPropertyValue(entity, attr, rio, included);
216216

217+
// recursive call ...
218+
if(included != null)
219+
{
220+
var navigationPropertyValue = attr.GetValue(entity);
221+
var contextGraphEntity = _jsonApiContext.ContextGraph.GetContextEntity(attr.Type);
222+
if(navigationPropertyValue != null && contextGraphEntity != null)
223+
{
224+
var includedResource = included.SingleOrDefault(r => r.Type == rio.Type && r.Id == rio.Id);
225+
if(includedResource != null)
226+
SetRelationships(navigationPropertyValue, contextGraphEntity, includedResource.Relationships, included);
227+
}
228+
}
229+
217230
return entity;
218231
}
219232

src/JsonApiDotNetCore/Services/QueryParser.cs

-4
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)