Skip to content

Commit 9059560

Browse files
authored
Merge pull request #493 from wisepotato/fix/#492
Fix/#492
2 parents 8f685a6 + e7ee2c2 commit 9059560

File tree

6 files changed

+99
-16
lines changed

6 files changed

+99
-16
lines changed

Diff for: JsonApiDotnetCore.sln

+2-1
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ Global
190190
{6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x64.ActiveCfg = Release|Any CPU
191191
{6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x64.Build.0 = Release|Any CPU
192192
{6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x86.ActiveCfg = Release|Any CPU
193-
{6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x86.Build.0 = Release|Any CPU\
193+
{6DFA30D7-1679-4333-9779-6FB678E48EF5}.Release|x86.Build.0 = Release|Any CPU
194194
{09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
195195
{09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
196196
{09C0C8D8-B721-4955-8889-55CB149C3B5C}.Debug|x64.ActiveCfg = Debug|Any CPU
@@ -203,6 +203,7 @@ Global
203203
{09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x64.Build.0 = Release|Any CPU
204204
{09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x86.ActiveCfg = Release|Any CPU
205205
{09C0C8D8-B721-4955-8889-55CB149C3B5C}.Release|x86.Build.0 = Release|Any CPU
206+
{DF9BFD82-D937-4907-B0B4-64670417115F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
206207
EndGlobalSection
207208
GlobalSection(SolutionProperties) = preSolution
208209
HideSolutionNode = FALSE

Diff for: src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs

+19-12
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ public void DetachRelationshipPointers(TEntity entity)
163163
{
164164
foreach (var hasOneRelationship in _jsonApiContext.HasOneRelationshipPointers.Get())
165165
{
166-
var hasOne = (HasOneAttribute) hasOneRelationship.Key;
166+
var hasOne = (HasOneAttribute)hasOneRelationship.Key;
167167
if (hasOne.EntityPropertyName != null)
168168
{
169169
var relatedEntity = entity.GetType().GetProperty(hasOne.EntityPropertyName)?.GetValue(entity);
@@ -178,7 +178,7 @@ public void DetachRelationshipPointers(TEntity entity)
178178

179179
foreach (var hasManyRelationship in _jsonApiContext.HasManyRelationshipPointers.Get())
180180
{
181-
var hasMany = (HasManyAttribute) hasManyRelationship.Key;
181+
var hasMany = (HasManyAttribute)hasManyRelationship.Key;
182182
if (hasMany.EntityPropertyName != null)
183183
{
184184
var relatedList = (IList)entity.GetType().GetProperty(hasMany.EntityPropertyName)?.GetValue(entity);
@@ -194,7 +194,7 @@ public void DetachRelationshipPointers(TEntity entity)
194194
_context.Entry(pointer).State = EntityState.Detached;
195195
}
196196
}
197-
197+
198198
// HACK: detaching has many relationships doesn't appear to be sufficient
199199
// the navigation property actually needs to be nulled out, otherwise
200200
// EF adds duplicate instances to the collection
@@ -234,7 +234,7 @@ private void AttachHasMany(TEntity entity, HasManyAttribute relationship, IList
234234
{
235235
_context.Entry(pointer).State = EntityState.Unchanged;
236236
}
237-
}
237+
}
238238
}
239239

240240
private void AttachHasManyThrough(TEntity entity, HasManyThroughAttribute hasManyThrough, IList pointers)
@@ -270,7 +270,7 @@ private void AttachHasOnePointers(TEntity entity)
270270
if (relationship.Key.GetType() != typeof(HasOneAttribute))
271271
continue;
272272

273-
var hasOne = (HasOneAttribute) relationship.Key;
273+
var hasOne = (HasOneAttribute)relationship.Key;
274274
if (hasOne.EntityPropertyName != null)
275275
{
276276
var relatedEntity = entity.GetType().GetProperty(hasOne.EntityPropertyName)?.GetValue(entity);
@@ -296,13 +296,20 @@ public virtual async Task<TEntity> UpdateAsync(TId id, TEntity entity)
296296
foreach (var attr in _jsonApiContext.AttributesToUpdate)
297297
attr.Key.SetValue(oldEntity, attr.Value);
298298

299-
foreach (var relationship in _jsonApiContext.RelationshipsToUpdate)
300-
relationship.Key.SetValue(oldEntity, relationship.Value);
301-
302-
AttachRelationships(oldEntity);
303-
299+
if (_jsonApiContext.RelationshipsToUpdate.Any())
300+
{
301+
AttachRelationships(oldEntity);
302+
foreach (var relationship in _jsonApiContext.RelationshipsToUpdate)
303+
{
304+
/// If we are updating to-many relations from PATCH, we need to include the relation first,
305+
/// else it will not peform a complete replacement, as required by the specs.
306+
/// Also, we currently do not support the same for many-to-many
307+
if (relationship.Key is HasManyAttribute && !(relationship.Key is HasManyThroughAttribute))
308+
await _context.Entry(oldEntity).Collection(relationship.Key.InternalRelationshipName).LoadAsync();
309+
relationship.Key.SetValue(oldEntity, relationship.Value); // article.tags = nieuwe lijst
310+
}
311+
}
304312
await _context.SaveChangesAsync();
305-
306313
return oldEntity;
307314
}
308315

@@ -366,7 +373,7 @@ public virtual IQueryable<TEntity> Include(IQueryable<TEntity> entities, string
366373
? relationship.RelationshipPath
367374
: $"{internalRelationshipPath}.{relationship.RelationshipPath}";
368375

369-
if(i < relationshipChain.Length)
376+
if (i < relationshipChain.Length)
370377
entity = _jsonApiContext.ResourceGraph.GetContextEntity(relationship.Type);
371378
}
372379

Diff for: src/JsonApiDotNetCore/Serialization/JsonApiDeSerializer.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ private object SetRelationships(
186186
{
187187
entity = attr.IsHasOne
188188
? SetHasOneRelationship(entity, entityProperties, (HasOneAttribute)attr, contextEntity, relationships, included)
189-
: SetHasManyRelationship(entity, entityProperties, attr, contextEntity, relationships, included);
189+
: SetHasManyRelationship(entity, entityProperties, (HasManyAttribute)attr, contextEntity, relationships, included);
190190
}
191191

192192
return entity;
@@ -274,7 +274,7 @@ private void SetHasOneNavigationPropertyValue(object entity, HasOneAttribute has
274274

275275
private object SetHasManyRelationship(object entity,
276276
PropertyInfo[] entityProperties,
277-
RelationshipAttribute attr,
277+
HasManyAttribute attr,
278278
ContextEntity contextEntity,
279279
Dictionary<string, RelationshipData> relationships,
280280
List<ResourceObject> included = null)
@@ -295,7 +295,7 @@ private object SetHasManyRelationship(object entity,
295295
var convertedCollection = TypeHelper.ConvertCollection(relatedResources, attr.Type);
296296

297297
attr.SetValue(entity, convertedCollection);
298-
298+
_jsonApiContext.RelationshipsToUpdate[attr] = convertedCollection;
299299
_jsonApiContext.HasManyRelationshipPointers.Add(attr, convertedCollection);
300300
}
301301

Diff for: test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/DocumentTests/Included.cs

+1
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,7 @@ public async Task GET_Included_DoesNot_Duplicate_Records_ForMultipleRelationship
181181
public async Task GET_Included_DoesNot_Duplicate_Records_If_HasOne_Exists_Twice()
182182
{
183183
// arrange
184+
_context.TodoItemCollections.RemoveRange(_context.TodoItemCollections);
184185
_context.People.RemoveRange(_context.People); // ensure all people have todo-items
185186
_context.TodoItems.RemoveRange(_context.TodoItems);
186187
var person = _personFaker.Generate();

Diff for: test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/UpdatingRelationshipsTests.cs

+72
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,78 @@ public UpdatingRelationshipsTests(TestFixture<TestStartup> fixture)
3737
.RuleFor(t => t.Description, f => f.Lorem.Sentence())
3838
.RuleFor(t => t.Ordinal, f => f.Random.Number())
3939
.RuleFor(t => t.CreatedDate, f => f.Date.Past());
40+
41+
42+
}
43+
44+
45+
[Fact]
46+
public async Task Can_Update_ToMany_Relationship_By_Patching_Resource()
47+
{
48+
// arrange
49+
var todoCollection = new TodoItemCollection();
50+
todoCollection.TodoItems = new List<TodoItem>();
51+
var person = _personFaker.Generate();
52+
var todoItem = _todoItemFaker.Generate();
53+
todoCollection.Owner = person;
54+
todoCollection.TodoItems.Add(todoItem);
55+
_context.TodoItemCollections.Add(todoCollection);
56+
_context.SaveChanges();
57+
58+
var newTodoItem1 = _todoItemFaker.Generate();
59+
var newTodoItem2 = _todoItemFaker.Generate();
60+
_context.AddRange(new TodoItem[] { newTodoItem1, newTodoItem2 });
61+
_context.SaveChanges();
62+
63+
var builder = new WebHostBuilder()
64+
.UseStartup<Startup>();
65+
66+
var server = new TestServer(builder);
67+
var client = server.CreateClient();
68+
69+
70+
var content = new
71+
{
72+
data = new
73+
{
74+
type = "todo-collections",
75+
id = todoCollection.Id,
76+
relationships = new Dictionary<string, object>
77+
{
78+
{ "todo-items", new
79+
{
80+
data = new object[]
81+
{
82+
new { type = "todo-items", id = $"{newTodoItem1.Id}" },
83+
new { type = "todo-items", id = $"{newTodoItem2.Id}" }
84+
}
85+
86+
}
87+
},
88+
}
89+
}
90+
};
91+
92+
var httpMethod = new HttpMethod("PATCH");
93+
var route = $"/api/v1/todo-collections/{todoCollection.Id}";
94+
var request = new HttpRequestMessage(httpMethod, route);
95+
96+
string serializedContent = JsonConvert.SerializeObject(content);
97+
request.Content = new StringContent(serializedContent);
98+
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/vnd.api+json");
99+
100+
// Act
101+
var response = await client.SendAsync(request);
102+
_context = _fixture.GetService<AppDbContext>();
103+
var updatedTodoItems = _context.TodoItemCollections.AsNoTracking()
104+
.Where(tic => tic.Id == todoCollection.Id)
105+
.Include(tdc => tdc.TodoItems).SingleOrDefault().TodoItems;
106+
107+
108+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
109+
/// we are expecting two, not three, because the request does
110+
/// a "complete replace".
111+
Assert.Equal(2, updatedTodoItems.Count);
40112
}
41113

42114
[Fact]

Diff for: test/UnitTests/Serialization/JsonApiDeSerializerTests.cs

+2
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,7 @@ public void Can_Deserialize_Object_With_HasManyRelationship()
361361
jsonApiContextMock.SetupAllProperties();
362362
jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph);
363363
jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary<AttrAttribute, object>());
364+
jsonApiContextMock.Setup(m => m.RelationshipsToUpdate).Returns(new Dictionary<RelationshipAttribute, object>());
364365
jsonApiContextMock.Setup(m => m.HasManyRelationshipPointers).Returns(new HasManyRelationshipPointers());
365366

366367
var jsonApiOptions = new JsonApiOptions();
@@ -414,6 +415,7 @@ public void Sets_Attribute_Values_On_Included_HasMany_Relationships()
414415
jsonApiContextMock.SetupAllProperties();
415416
jsonApiContextMock.Setup(m => m.ResourceGraph).Returns(resourceGraph);
416417
jsonApiContextMock.Setup(m => m.AttributesToUpdate).Returns(new Dictionary<AttrAttribute, object>());
418+
jsonApiContextMock.Setup(m => m.RelationshipsToUpdate).Returns(new Dictionary<RelationshipAttribute, object>());
417419
jsonApiContextMock.Setup(m => m.HasManyRelationshipPointers).Returns(new HasManyRelationshipPointers());
418420

419421
var jsonApiOptions = new JsonApiOptions();

0 commit comments

Comments
 (0)