Skip to content

Commit 74d31e0

Browse files
committed
Merge branch 'fix/#237' into develop
2 parents 015938f + 5ea2252 commit 74d31e0

15 files changed

+180
-36
lines changed

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

+10
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,20 @@
11
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
24
using System.Reflection;
35

46
namespace JsonApiDotNetCore.Internal
57
{
68
public static class TypeHelper
79
{
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+
818
public static object ConvertType(object value, Type type)
919
{
1020
if (value == null)

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
}
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

+19-10
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)
@@ -225,11 +232,6 @@ private object SetHasManyRelationship(object entity,
225232
ContextEntity contextEntity,
226233
Dictionary<string, RelationshipData> relationships)
227234
{
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-
233235
var relationshipName = attr.PublicRelationshipName;
234236

235237
if (relationships.TryGetValue(relationshipName, out RelationshipData relationshipData))
@@ -238,11 +240,18 @@ private object SetHasManyRelationship(object entity,
238240

239241
if (data == null) return entity;
240242

241-
var genericProcessor = _genericProcessorFactory.GetProcessor<IGenericProcessor>(typeof(GenericProcessor<>), attr.Type);
243+
var relationshipShells = relationshipData.ManyData.Select(r =>
244+
{
245+
var instance = attr.Type.New<IIdentifiable>();
246+
instance.StringId = r.Id;
247+
return instance;
248+
});
249+
250+
var convertedCollection = TypeHelper.ConvertCollection(relationshipShells, attr.Type);
242251

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

245-
genericProcessor.SetRelationships(entity, attr, ids);
254+
_jsonApiContext.HasManyRelationshipPointers.Add(attr.Type, convertedCollection);
246255
}
247256

248257
return entity;

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

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using JsonApiDotNetCore.Internal.Generics;
77
using JsonApiDotNetCore.Internal.Query;
88
using JsonApiDotNetCore.Models;
9+
using JsonApiDotNetCore.Request;
910

1011
namespace JsonApiDotNetCore.Services
1112
{
@@ -28,6 +29,7 @@ public interface IJsonApiContext
2829
Type ControllerType { get; set; }
2930
Dictionary<string, object> DocumentMeta { get; set; }
3031
bool IsBulkOperationRequest { get; set; }
32+
HasManyRelationshipPointers HasManyRelationshipPointers { get; }
3133

3234
TAttribute GetControllerAttribute<TAttribute>() where TAttribute : Attribute;
3335
}

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Linq;
43
using JsonApiDotNetCore.Builders;
54
using JsonApiDotNetCore.Configuration;
65
using JsonApiDotNetCore.Internal;
76
using JsonApiDotNetCore.Internal.Generics;
87
using JsonApiDotNetCore.Internal.Query;
98
using JsonApiDotNetCore.Models;
9+
using JsonApiDotNetCore.Request;
1010
using Microsoft.AspNetCore.Http;
1111

1212
namespace JsonApiDotNetCore.Services
@@ -52,6 +52,7 @@ public JsonApiContext(
5252
public Type ControllerType { get; set; }
5353
public Dictionary<string, object> DocumentMeta { get; set; }
5454
public bool IsBulkOperationRequest { get; set; }
55+
public HasManyRelationshipPointers HasManyRelationshipPointers { get; } = new HasManyRelationshipPointers();
5556

5657
public IJsonApiContext ApplyContext<T>(object controller)
5758
{

Diff for: test/JsonApiDotNetCoreExampleTests/Unit/Builders/MetaBuilderTests.cs renamed to test/UnitTests/Builders/MetaBuilderTests.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
1-
using Xunit;
2-
using JsonApiDotNetCore.Builders;
31
using System.Collections.Generic;
2+
using JsonApiDotNetCore.Builders;
3+
using Xunit;
44

5-
namespace JsonApiDotNetCoreExampleTests.Unit.Builders
5+
namespace UnitTests.Builders
66
{
77
public class MetaBuilderTests
88
{

Diff for: test/JsonApiDotNetCoreExampleTests/Unit/Extensions/IServiceCollectionExtensionsTests.cs renamed to test/UnitTests/Extensions/IServiceCollectionExtensionsTests.cs

+3-4
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,11 @@
1010
using JsonApiDotNetCoreExample.Data;
1111
using JsonApiDotNetCoreExample.Models;
1212
using Microsoft.AspNetCore.Http;
13-
using Microsoft.Extensions.Caching.Memory;
1413
using Microsoft.Extensions.DependencyInjection;
15-
using UnitTests;
1614
using Xunit;
15+
using Microsoft.EntityFrameworkCore;
1716

18-
namespace JsonApiDotNetCoreExampleTests.Unit.Extensions
17+
namespace UnitTests.Extensions
1918
{
2019
public class IServiceCollectionExtensionsTests
2120
{
@@ -28,7 +27,7 @@ public void AddJsonApiInternals_Adds_All_Required_Services()
2827

2928
services.AddDbContext<AppDbContext>(options =>
3029
{
31-
options.UseMemoryCache(new MemoryCache(new MemoryCacheOptions()));
30+
options.UseInMemoryDatabase();
3231
}, ServiceLifetime.Transient);
3332

3433
// act

Diff for: test/UnitTests/Extensions/TypeExtensions_Tests.cs

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using System.Collections.Generic;
2+
using JsonApiDotNetCore.Extensions;
3+
using JsonApiDotNetCore.Models;
4+
using Xunit;
5+
6+
namespace UnitTests.Extensions
7+
{
8+
public class TypeExtensions_Tests
9+
{
10+
[Fact]
11+
public void GetCollection_Creates_List_If_T_Implements_Interface()
12+
{
13+
// arrange
14+
var type = typeof(Model);
15+
16+
// act
17+
var collection = type.GetEmptyCollection();
18+
19+
// assert
20+
Assert.NotNull(collection);
21+
Assert.Empty(collection);
22+
Assert.IsType<List<Model>>(collection);
23+
}
24+
25+
[Fact]
26+
public void New_Creates_An_Instance_If_T_Implements_Interface()
27+
{
28+
// arrange
29+
var type = typeof(Model);
30+
31+
// act
32+
var instance = type.New<IIdentifiable>();
33+
34+
// assert
35+
Assert.NotNull(instance);
36+
Assert.IsType<Model>(instance);
37+
}
38+
39+
private class Model : IIdentifiable
40+
{
41+
public string StringId { get; set; }
42+
}
43+
}
44+
}

Diff for: test/JsonApiDotNetCoreExampleTests/Unit/Models/AttributesEqualsTests.cs renamed to test/UnitTests/Models/AttributesEqualsTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using JsonApiDotNetCore.Models;
22
using Xunit;
33

4-
namespace JsonApiDotNetCoreExampleTests.Unit.Models
4+
namespace UnitTests.Models
55
{
66
public class AttributesEqualsTests
77
{

Diff for: test/UnitTests/UnitTests.csproj

+1
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@
1212
<PackageReference Include="xunit.runner.visualstudio" Version="2.2.0" />
1313
<PackageReference Include="xunit" Version="$(XUnitVersion)" />
1414
<PackageReference Include="Moq" Version="$(MoqVersion)" />
15+
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="2.0.2" />
1516
</ItemGroup>
1617
</Project>

0 commit comments

Comments
 (0)