Skip to content

Commit aff997c

Browse files
authored
Merge pull request #254 from json-api-dotnet/fix/#237
De-Couple deserializer from GenericProcessorFactory
2 parents 4c5acb9 + d76c600 commit aff997c

23 files changed

+848
-427
lines changed

Diff for: src/JsonApiDotNetCore/Builders/DocumentBuilder.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public DocumentData GetData(ContextEntity contextEntity, IIdentifiable entity)
107107
Id = entity.StringId
108108
};
109109

110-
if (_jsonApiContext.IsRelationshipData)
110+
if (_jsonApiContext.IsRelationshipPath)
111111
return data;
112112

113113
data.Attributes = new Dictionary<string, object>();

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

+18
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,29 @@ public virtual async Task<TEntity> GetAndIncludeAsync(TId id, string relationshi
8484

8585
public virtual async Task<TEntity> CreateAsync(TEntity entity)
8686
{
87+
AttachHasManyPointers();
8788
_dbSet.Add(entity);
89+
8890
await _context.SaveChangesAsync();
8991
return entity;
9092
}
9193

94+
/// <summary>
95+
/// This is used to allow creation of HasMany relationships when the
96+
/// dependent side of the relationship already exists.
97+
/// </summary>
98+
private void AttachHasManyPointers()
99+
{
100+
var relationships = _jsonApiContext.HasManyRelationshipPointers.Get();
101+
foreach(var relationship in relationships)
102+
{
103+
foreach(var pointer in relationship.Value)
104+
{
105+
_context.Entry(pointer).State = EntityState.Unchanged;
106+
}
107+
}
108+
}
109+
92110
public virtual async Task<TEntity> UpdateAsync(TId id, TEntity entity)
93111
{
94112
var oldEntity = await GetAsync(id);

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

-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
using System.Collections.Generic;
2-
using System.Linq;
3-
using System.Threading.Tasks;
4-
using JsonApiDotNetCore.Internal.Query;
51
using JsonApiDotNetCore.Models;
62

73
namespace JsonApiDotNetCore.Data
+4-12
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,12 @@
1-
using Microsoft.EntityFrameworkCore;
21
using System;
2+
using Microsoft.EntityFrameworkCore;
33

44
namespace JsonApiDotNetCore.Extensions
55
{
66
public static class DbContextExtensions
77
{
8-
public static DbSet<T> GetDbSet<T>(this DbContext context) where T : class
9-
{
10-
var contextProperties = context.GetType().GetProperties();
11-
foreach(var property in contextProperties)
12-
{
13-
if (property.PropertyType == typeof(DbSet<T>))
14-
return (DbSet<T>)property.GetValue(context);
15-
}
16-
17-
throw new ArgumentException($"DbSet of type {typeof(T).FullName} not found on the DbContext", nameof(T));
18-
}
8+
[Obsolete("This is no longer required since the introduction of context.Set<T>", error: false)]
9+
public static DbSet<T> GetDbSet<T>(this DbContext context) where T : class
10+
=> context.Set<T>();
1911
}
2012
}

Diff for: src/JsonApiDotNetCore/Extensions/TypeExtensions.cs

+23
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,28 @@ public static Type GetElementType(this IEnumerable enumerable)
3131

3232
return elementType;
3333
}
34+
35+
/// <summary>
36+
/// Creates a List{TInterface} where TInterface is the generic for type specified by t
37+
/// </summary>
38+
public static IEnumerable GetEmptyCollection(this Type t)
39+
{
40+
if (t == null) throw new ArgumentNullException(nameof(t));
41+
42+
var listType = typeof(List<>).MakeGenericType(t);
43+
var list = (IEnumerable)Activator.CreateInstance(listType);
44+
return list;
45+
}
46+
47+
/// <summary>
48+
/// Creates a new instance of type t, casting it to the specified TInterface
49+
/// </summary>
50+
public static TInterface New<TInterface>(this Type t)
51+
{
52+
if (t == null) throw new ArgumentNullException(nameof(t));
53+
54+
var instance = (TInterface)Activator.CreateInstance(t);
55+
return instance;
56+
}
3457
}
3558
}

Diff for: src/JsonApiDotNetCore/Internal/TypeHelper.cs

+8
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@ namespace JsonApiDotNetCore.Internal
77
{
88
public static class TypeHelper
99
{
10+
public static IList ConvertCollection(IEnumerable<object> collection, Type targetType)
11+
{
12+
var list = Activator.CreateInstance(typeof(List<>).MakeGenericType(targetType)) as IList;
13+
foreach(var item in collection)
14+
list.Add(ConvertType(item, targetType));
15+
return list;
16+
}
17+
1018
public static object ConvertType(object value, Type type)
1119
{
1220
if (value == null)

Diff for: src/JsonApiDotNetCore/JsonApiDotNetCore.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22
<PropertyGroup>
3-
<VersionPrefix>2.2.4</VersionPrefix>
3+
<VersionPrefix>2.2.5</VersionPrefix>
44
<TargetFrameworks>$(NetStandardVersion)</TargetFrameworks>
55
<AssemblyName>JsonApiDotNetCore</AssemblyName>
66
<PackageId>JsonApiDotNetCore</PackageId>

Diff for: src/JsonApiDotNetCore/Models/HasManyAttribute.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public override void SetValue(object entity, object newValue)
1212
.GetType()
1313
.GetProperty(InternalRelationshipName);
1414

15-
propertyInfo.SetValue(entity, newValue);
15+
propertyInfo.SetValue(entity, newValue);
1616
}
1717
}
1818
}

Diff for: src/JsonApiDotNetCore/Models/HasOneAttribute.cs

+6-1
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,14 @@ public HasOneAttribute(string publicName, Link documentLinks = Link.All, bool ca
3939
? $"{InternalRelationshipName}Id"
4040
: _explicitIdentifiablePropertyName;
4141

42+
/// <summary>
43+
/// Sets the value of the property identified by this attribute
44+
/// </summary>
45+
/// <param name="entity">The target object</param>
46+
/// <param name="newValue">The new property value</param>
4247
public override void SetValue(object entity, object newValue)
4348
{
44-
var propertyName = (newValue.GetType() == Type)
49+
var propertyName = (newValue?.GetType() == Type)
4550
? InternalRelationshipName
4651
: IdentifiablePropertyName;
4752

Diff for: src/JsonApiDotNetCore/Models/RelationshipAttribute.cs

+22
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,28 @@ protected RelationshipAttribute(string publicName, Link documentLinks, bool canI
1919
public Link DocumentLinks { get; } = Link.All;
2020
public bool CanInclude { get; }
2121

22+
public bool TryGetHasOne(out HasOneAttribute result)
23+
{
24+
if (IsHasOne)
25+
{
26+
result = (HasOneAttribute)this;
27+
return true;
28+
}
29+
result = null;
30+
return false;
31+
}
32+
33+
public bool TryGetHasMany(out HasManyAttribute result)
34+
{
35+
if (IsHasMany)
36+
{
37+
result = (HasManyAttribute)this;
38+
return true;
39+
}
40+
result = null;
41+
return false;
42+
}
43+
2244
public abstract void SetValue(object entity, object newValue);
2345

2446
public override string ToString()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
5+
namespace JsonApiDotNetCore.Request
6+
{
7+
/// <summary>
8+
/// Stores information to set relationships for the request resource.
9+
/// These relationships must already exist and should not be re-created.
10+
///
11+
/// The expected use case is POST-ing or PATCH-ing
12+
/// an entity with HasMany relaitonships:
13+
/// <code>
14+
/// {
15+
/// "data": {
16+
/// "type": "photos",
17+
/// "attributes": {
18+
/// "title": "Ember Hamster",
19+
/// "src": "http://example.com/images/productivity.png"
20+
/// },
21+
/// "relationships": {
22+
/// "tags": {
23+
/// "data": [
24+
/// { "type": "tags", "id": "2" },
25+
/// { "type": "tags", "id": "3" }
26+
/// ]
27+
/// }
28+
/// }
29+
/// }
30+
/// }
31+
/// </code>
32+
/// </summary>
33+
public class HasManyRelationshipPointers
34+
{
35+
private Dictionary<Type, IList> _hasManyRelationships = new Dictionary<Type, IList>();
36+
37+
/// <summary>
38+
/// Add the relationship to the list of relationships that should be
39+
/// set in the repository layer.
40+
/// </summary>
41+
public void Add(Type dependentType, IList entities)
42+
=> _hasManyRelationships[dependentType] = entities;
43+
44+
/// <summary>
45+
/// Get all the models that should be associated
46+
/// </summary>
47+
public Dictionary<Type, IList> Get() => _hasManyRelationships;
48+
}
49+
}

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

+32-18
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Reflection;
5+
using JsonApiDotNetCore.Extensions;
56
using JsonApiDotNetCore.Internal;
67
using JsonApiDotNetCore.Internal.Generics;
78
using JsonApiDotNetCore.Models;
@@ -15,14 +16,20 @@ namespace JsonApiDotNetCore.Serialization
1516
public class JsonApiDeSerializer : IJsonApiDeSerializer
1617
{
1718
private readonly IJsonApiContext _jsonApiContext;
18-
private readonly IGenericProcessorFactory _genericProcessorFactory;
1919

20+
[Obsolete(
21+
"The deserializer no longer depends on the IGenericProcessorFactory",
22+
error: false)]
2023
public JsonApiDeSerializer(
2124
IJsonApiContext jsonApiContext,
2225
IGenericProcessorFactory genericProcessorFactory)
2326
{
2427
_jsonApiContext = jsonApiContext;
25-
_genericProcessorFactory = genericProcessorFactory;
28+
}
29+
30+
public JsonApiDeSerializer(IJsonApiContext jsonApiContext)
31+
{
32+
_jsonApiContext = jsonApiContext;
2633
}
2734

2835
public object Deserialize(string requestBody)
@@ -200,20 +207,25 @@ private object SetHasOneRelationship(object entity,
200207

201208
var rio = (ResourceIdentifierObject)relationshipData.ExposedData;
202209

203-
if (rio == null) return entity;
204-
205-
var newValue = rio.Id;
206-
207210
var foreignKey = attr.IdentifiablePropertyName;
208211
var entityProperty = entityProperties.FirstOrDefault(p => p.Name == foreignKey);
209-
if (entityProperty == null)
212+
if (entityProperty == null && rio != null)
210213
throw new JsonApiException(400, $"{contextEntity.EntityType.Name} does not contain a foreign key property '{foreignKey}' for has one relationship '{attr.InternalRelationshipName}'");
211214

212-
var convertedValue = TypeHelper.ConvertType(newValue, entityProperty.PropertyType);
215+
if (entityProperty != null)
216+
{
217+
// e.g. PATCH /articles
218+
// {... { "relationships":{ "Owner": { "data" :null } } } }
219+
if (rio == null && Nullable.GetUnderlyingType(entityProperty.PropertyType) == null)
220+
throw new JsonApiException(400, $"Cannot set required relationship identifier '{attr.IdentifiablePropertyName}' to null.");
213221

214-
_jsonApiContext.RelationshipsToUpdate[relationshipAttr] = convertedValue;
222+
var newValue = rio?.Id ?? null;
223+
var convertedValue = TypeHelper.ConvertType(newValue, entityProperty.PropertyType);
215224

216-
entityProperty.SetValue(entity, convertedValue);
225+
_jsonApiContext.RelationshipsToUpdate[relationshipAttr] = convertedValue;
226+
227+
entityProperty.SetValue(entity, convertedValue);
228+
}
217229
}
218230

219231
return entity;
@@ -225,11 +237,6 @@ private object SetHasManyRelationship(object entity,
225237
ContextEntity contextEntity,
226238
Dictionary<string, RelationshipData> relationships)
227239
{
228-
var entityProperty = entityProperties.FirstOrDefault(p => p.Name == attr.InternalRelationshipName);
229-
230-
if (entityProperty == null)
231-
throw new JsonApiException(400, $"{contextEntity.EntityType.Name} does not contain an relationsip named {attr.InternalRelationshipName}");
232-
233240
var relationshipName = attr.PublicRelationshipName;
234241

235242
if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData))
@@ -238,11 +245,18 @@ private object SetHasManyRelationship(object entity,
238245

239246
if (data == null) return entity;
240247

241-
var genericProcessor = _genericProcessorFactory.GetProcessor<IGenericProcessor>(typeof(GenericProcessor<>), attr.Type);
248+
var relationshipShells = relationshipData.ManyData.Select(r =>
249+
{
250+
var instance = attr.Type.New<IIdentifiable>();
251+
instance.StringId = r.Id;
252+
return instance;
253+
});
254+
255+
var convertedCollection = TypeHelper.ConvertCollection(relationshipShells, attr.Type);
242256

243-
var ids = relationshipData.ManyData.Select(r => r.Id);
257+
attr.SetValue(entity, convertedCollection);
244258

245-
genericProcessor.SetRelationships(entity, attr, ids);
259+
_jsonApiContext.HasManyRelationshipPointers.Add(attr.Type, convertedCollection);
246260
}
247261

248262
return entity;

Diff for: src/JsonApiDotNetCore/Services/EntityResourceService.cs

+2-5
Original file line numberDiff line numberDiff line change
@@ -80,10 +80,7 @@ private async Task<T> GetWithRelationshipsAsync(TId id)
8080
}
8181

8282
public virtual async Task<object> GetRelationshipsAsync(TId id, string relationshipName)
83-
{
84-
_jsonApiContext.IsRelationshipData = true;
85-
return await GetRelationshipAsync(id, relationshipName);
86-
}
83+
=> await GetRelationshipAsync(id, relationshipName);
8784

8885
public virtual async Task<object> GetRelationshipAsync(TId id, string relationshipName)
8986
{
@@ -136,7 +133,7 @@ public virtual async Task UpdateRelationshipsAsync(TId id, string relationshipNa
136133
.Relationships
137134
.FirstOrDefault(r => r.InternalRelationshipName == relationshipName);
138135

139-
var relationshipIds = relationships.Select(r => r.Id?.ToString());
136+
var relationshipIds = relationships.Select(r => r?.Id?.ToString());
140137

141138
await _entities.UpdateRelationshipsAsync(entity, relationship, relationshipIds);
142139
}

0 commit comments

Comments
 (0)