diff --git a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs index 7e864a03e0..d8d38390d8 100644 --- a/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs +++ b/src/JsonApiDotNetCore/Builders/DocumentBuilder.cs @@ -95,12 +95,10 @@ private Dictionary GetMeta(IIdentifiable entity) private List AppendIncludedObject(List includedObject, ContextEntity contextEntity, IIdentifiable entity) { - var includedEntities = GetIncludedEntities(contextEntity, entity); - if (includedEntities.Count > 0) + var includedEntities = GetIncludedEntities(includedObject, contextEntity, entity); + if (includedEntities?.Count > 0) { - if (includedObject == null) - includedObject = new List(); - includedObject.AddRange(includedEntities); + includedObject = includedEntities; } return includedObject; @@ -174,10 +172,8 @@ private void AddRelationships(DocumentData data, ContextEntity contextEntity, II }); } - private List GetIncludedEntities(ContextEntity contextEntity, IIdentifiable entity) + private List GetIncludedEntities(List included, ContextEntity contextEntity, IIdentifiable entity) { - var included = new List(); - contextEntity.Relationships.ForEach(r => { if (!RelationshipIsIncluded(r.PublicRelationshipName)) return; @@ -186,20 +182,25 @@ private List GetIncludedEntities(ContextEntity contextEntity, IIde if (navigationEntity is IEnumerable hasManyNavigationEntity) foreach (IIdentifiable includedEntity in hasManyNavigationEntity) - AddIncludedEntity(included, includedEntity); + included = AddIncludedEntity(included, includedEntity); else - AddIncludedEntity(included, (IIdentifiable)navigationEntity); + included = AddIncludedEntity(included, (IIdentifiable)navigationEntity); }); return included; } - private void AddIncludedEntity(List entities, IIdentifiable entity) + private List AddIncludedEntity(List entities, IIdentifiable entity) { var includedEntity = GetIncludedEntity(entity); + if(entities == null) + entities = new List(); + if(includedEntity != null && !entities.Any(doc => doc.Id == includedEntity.Id && doc.Type == includedEntity.Type)) entities.Add(includedEntity); + + return entities; } private DocumentData GetIncludedEntity(IIdentifiable entity) diff --git a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj index f512d3505c..b974570a90 100755 --- a/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj +++ b/src/JsonApiDotNetCore/JsonApiDotNetCore.csproj @@ -1,6 +1,6 @@  - 2.1.1 + 2.1.2 netstandard1.6 JsonApiDotNetCore JsonApiDotNetCore diff --git a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Included.cs b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Included.cs index d17546e1e7..e782f18aa9 100644 --- a/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Included.cs +++ b/test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Included.cs @@ -169,6 +169,41 @@ public async Task GET_Included_DoesNot_Duplicate_Records_ForMultipleRelationship Assert.Equal(1, documents.Included.Count); } + [Fact] + public async Task GET_Included_DoesNot_Duplicate_Records_If_HasOne_Exists_Twice() + { + // arrange + _context.People.RemoveRange(_context.People); // ensure all people have todo-items + _context.TodoItems.RemoveRange(_context.TodoItems); + var person = _personFaker.Generate(); + var todoItem1 = _todoItemFaker.Generate(); + var todoItem2 = _todoItemFaker.Generate(); + todoItem1.Owner = person; + todoItem2.Owner = person; + _context.TodoItems.AddRange(new[] { todoItem1, todoItem2 }); + _context.SaveChanges(); + + var builder = new WebHostBuilder() + .UseStartup(); + + var httpMethod = new HttpMethod("GET"); + var route = $"/api/v1/todo-items?include=owner"; + + var server = new TestServer(builder); + var client = server.CreateClient(); + var request = new HttpRequestMessage(httpMethod, route); + + // act + var response = await client.SendAsync(request); + var documents = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + var data = documents.Data; + + // assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.NotEmpty(documents.Included); + Assert.Equal(1, documents.Included.Count); + } + [Fact] public async Task GET_ById_Included_Contains_SideloadedData_ForOneToMany() {