Skip to content

Commit c1d472d

Browse files
committed
fix: bugs with inverse relationship loading
1 parent 6e6f7fa commit c1d472d

File tree

2 files changed

+84
-7
lines changed

2 files changed

+84
-7
lines changed

src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,20 +174,34 @@ public virtual async Task<TEntity> CreateAsync(TEntity entity)
174174
/// </summary>
175175
private void LoadInverseRelationships(object trackedRelationshipValue, RelationshipAttribute relationshipAttr)
176176
{
177-
if (relationshipAttr.InverseNavigation == null) return;
177+
if (relationshipAttr.InverseNavigation == null || trackedRelationshipValue == null) return;
178178
if (relationshipAttr is HasOneAttribute hasOneAttr)
179179
{
180-
_context.Entry((IIdentifiable)trackedRelationshipValue).Reference(hasOneAttr.InverseNavigation).Load();
180+
var relationEntry = _context.Entry((IIdentifiable)trackedRelationshipValue);
181+
if (IsHasOneRelationship(hasOneAttr.InverseNavigation, trackedRelationshipValue.GetType()))
182+
{
183+
relationEntry.Reference(hasOneAttr.InverseNavigation).Load();
184+
} else
185+
{
186+
relationEntry.Collection(hasOneAttr.InverseNavigation).Load();
187+
}
181188
}
182-
else if (relationshipAttr is HasManyAttribute hasManyAttr)
189+
else if (relationshipAttr is HasManyAttribute hasManyAttr && !(relationshipAttr is HasManyThroughAttribute))
183190
{
184191
foreach (IIdentifiable relationshipValue in (IList)trackedRelationshipValue)
185192
{
186-
_context.Entry((IIdentifiable)trackedRelationshipValue).Reference(hasManyAttr.InverseNavigation).Load();
193+
_context.Entry(relationshipValue).Reference(hasManyAttr.InverseNavigation).Load();
187194
}
188195
}
189196
}
190197

198+
private bool IsHasOneRelationship(string inverseNavigation, Type type)
199+
{
200+
var relationshipAttr = _jsonApiContext.ResourceGraph.GetContextEntity(type).Relationships.Single(r => r.InternalRelationshipName == inverseNavigation);
201+
if (relationshipAttr is HasOneAttribute) return true;
202+
return false;
203+
}
204+
191205

192206
/// <inheritdoc />
193207
public void DetachRelationshipPointers(TEntity entity)

test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/UpdatingRelationshipsTests.cs

Lines changed: 66 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -633,7 +633,7 @@ public async Task Updating_ToOne_Relationship_With_Implicit_Remove()
633633
var person2 = _personFaker.Generate();
634634
context.People.AddRange(new List<Person>() { person1, person2 });
635635
await context.SaveChangesAsync();
636-
636+
var passportId = person1.PassportId;
637637
var content = new
638638
{
639639
data = new
@@ -644,7 +644,7 @@ public async Task Updating_ToOne_Relationship_With_Implicit_Remove()
644644
{
645645
{ "passport", new
646646
{
647-
data = new { type = "passports", id = $"{person1.PassportId}" }
647+
data = new { type = "passports", id = $"{passportId}" }
648648
}
649649
}
650650
}
@@ -661,12 +661,75 @@ public async Task Updating_ToOne_Relationship_With_Implicit_Remove()
661661

662662
// Act
663663
var response = await _fixture.Client.SendAsync(request);
664+
var body = await response.Content.ReadAsStringAsync();
664665

665666
// Assert
667+
668+
Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}");
669+
var dbPerson = context.People.AsNoTracking().Where(p => p.Id == person2.Id).Include("Passport").FirstOrDefault();
670+
Assert.Equal(passportId, dbPerson.Passport.Id);
671+
}
672+
673+
[Fact]
674+
public async Task Updating_ToMany_Relationship_With_Implicit_Remove()
675+
{
676+
// Arrange
677+
var context = _fixture.GetService<AppDbContext>();
678+
var person1 = _personFaker.Generate();
679+
person1.TodoItems = _todoItemFaker.Generate(3).ToList();
680+
var person2 = _personFaker.Generate();
681+
person2.TodoItems = _todoItemFaker.Generate(2).ToList();
682+
context.People.AddRange(new List<Person>() { person1, person2 });
683+
await context.SaveChangesAsync();
684+
var todoItem1Id = person1.TodoItems[0].Id;
685+
var todoItem2Id = person1.TodoItems[1].Id;
686+
687+
var content = new
688+
{
689+
data = new
690+
{
691+
type = "people",
692+
id = person2.Id,
693+
relationships = new Dictionary<string, object>
694+
{
695+
{ "todo-items", new
696+
{
697+
data = new List<object>
698+
{
699+
new {
700+
type = "todo-items",
701+
id = $"{todoItem1Id}"
702+
},
703+
new {
704+
type = "todo-items",
705+
id = $"{todoItem2Id}"
706+
}
707+
}
708+
}
709+
}
710+
}
711+
}
712+
};
713+
714+
var httpMethod = new HttpMethod("PATCH");
715+
var route = $"/api/v1/people/{person2.Id}";
716+
var request = new HttpRequestMessage(httpMethod, route);
717+
718+
string serializedContent = JsonConvert.SerializeObject(content);
719+
request.Content = new StringContent(serializedContent);
720+
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json");
721+
722+
// Act
723+
var response = await _fixture.Client.SendAsync(request);
666724
var body = await response.Content.ReadAsStringAsync();
667725

668-
Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}");
726+
// Assert
669727

728+
Assert.True(HttpStatusCode.OK == response.StatusCode, $"{route} returned {response.StatusCode} status code with payload: {body}");
729+
var dbPerson = context.People.AsNoTracking().Where(p => p.Id == person2.Id).Include("TodoItems").FirstOrDefault();
730+
Assert.Equal(2, dbPerson.TodoItems.Count);
731+
Assert.NotNull(dbPerson.TodoItems.SingleOrDefault(ti => ti.Id == todoItem1Id));
732+
Assert.NotNull(dbPerson.TodoItems.SingleOrDefault(ti => ti.Id == todoItem2Id));
670733
}
671734
}
672735
}

0 commit comments

Comments
 (0)