7
7
using JsonApiDotNetCore . Extensions ;
8
8
using JsonApiDotNetCore . Models ;
9
9
using Microsoft . EntityFrameworkCore ;
10
+ using Microsoft . EntityFrameworkCore . Infrastructure ;
11
+ using Microsoft . EntityFrameworkCore . Storage ;
10
12
11
13
namespace JsonApiDotNetCore . Internal . Generics
12
14
{
15
+ // TODO: consider renaming to PatchRelationshipService (or something)
13
16
public interface IGenericProcessor
14
17
{
15
18
Task UpdateRelationshipsAsync ( object parent , RelationshipAttribute relationship , IEnumerable < string > relationshipIds ) ;
16
- void SetRelationships ( object parent , RelationshipAttribute relationship , IEnumerable < string > relationshipIds ) ;
17
19
}
18
20
21
+ /// <summary>
22
+ /// A special processor that gets instantiated for a generic type (<T>)
23
+ /// when the actual type is not known until runtime. Specifically, this is used for updating
24
+ /// relationships.
25
+ /// </summary>
19
26
public class GenericProcessor < T > : IGenericProcessor where T : class
20
27
{
21
28
private readonly DbContext _context ;
@@ -26,14 +33,21 @@ public GenericProcessor(IDbContextResolver contextResolver)
26
33
27
34
public virtual async Task UpdateRelationshipsAsync ( object parent , RelationshipAttribute relationship , IEnumerable < string > relationshipIds )
28
35
{
29
- SetRelationships ( parent , relationship , relationshipIds ) ;
30
-
31
- await _context . SaveChangesAsync ( ) ;
36
+ if ( relationship is HasManyThroughAttribute hasManyThrough && parent is IIdentifiable identifiableParent )
37
+ {
38
+ await SetHasManyThroughRelationshipAsync ( identifiableParent , hasManyThrough , relationshipIds ) ;
39
+ }
40
+ else
41
+ {
42
+ await SetRelationshipsAsync ( parent , relationship , relationshipIds ) ;
43
+ }
32
44
}
33
45
34
- public virtual void SetRelationships ( object parent , RelationshipAttribute relationship , IEnumerable < string > relationshipIds )
46
+ private async Task SetHasManyThroughRelationshipAsync ( IIdentifiable identifiableParent , HasManyThroughAttribute hasManyThrough , IEnumerable < string > relationshipIds )
35
47
{
36
- if ( relationship is HasManyThroughAttribute hasManyThrough && parent is IIdentifiable identifiableParent )
48
+ // we need to create a transaction for the HasManyThrough case so we can get and remove any existing
49
+ // join entities and only commit if all operations are successful
50
+ using ( var transaction = await _context . GetCurrentOrCreateTransactionAsync ( ) )
37
51
{
38
52
// ArticleTag
39
53
ParameterExpression parameter = Expression . Parameter ( hasManyThrough . ThroughType ) ;
@@ -50,13 +64,14 @@ public virtual void SetRelationships(object parent, RelationshipAttribute relati
50
64
51
65
var lambda = Expression . Lambda < Func < T , bool > > ( equals , parameter ) ;
52
66
67
+ // TODO: we shouldn't need to do this instead we should try updating the existing?
68
+ // the challenge here is if a composite key is used, then we will fail to
69
+ // create due to a unique key violation
53
70
var oldLinks = _context
54
71
. Set < T > ( )
55
72
. Where ( lambda . Compile ( ) )
56
73
. ToList ( ) ;
57
74
58
- // TODO: we shouldn't need to do this and it especially shouldn't happen outside a transaction
59
- // instead we should try updating the existing?
60
75
_context . RemoveRange ( oldLinks ) ;
61
76
62
77
var newLinks = relationshipIds . Select ( x => {
@@ -67,8 +82,15 @@ public virtual void SetRelationships(object parent, RelationshipAttribute relati
67
82
} ) ;
68
83
69
84
_context . AddRange ( newLinks ) ;
85
+ await _context . SaveChangesAsync ( ) ;
86
+
87
+ transaction . Commit ( ) ;
70
88
}
71
- else if ( relationship . IsHasMany )
89
+ }
90
+
91
+ private async Task SetRelationshipsAsync ( object parent , RelationshipAttribute relationship , IEnumerable < string > relationshipIds )
92
+ {
93
+ if ( relationship . IsHasMany )
72
94
{
73
95
// TODO: need to handle the failure mode when the relationship does not implement IIdentifiable
74
96
var entities = _context . Set < T > ( ) . Where ( x => relationshipIds . Contains ( ( ( IIdentifiable ) x ) . StringId ) ) . ToList ( ) ;
@@ -80,6 +102,8 @@ public virtual void SetRelationships(object parent, RelationshipAttribute relati
80
102
var entity = _context . Set < T > ( ) . SingleOrDefault ( x => relationshipIds . First ( ) == ( ( IIdentifiable ) x ) . StringId ) ;
81
103
relationship . SetValue ( parent , entity ) ;
82
104
}
105
+
106
+ await _context . SaveChangesAsync ( ) ;
83
107
}
84
108
}
85
109
}
0 commit comments