77using JsonApiDotNetCore . Extensions ;
88using JsonApiDotNetCore . Models ;
99using Microsoft . EntityFrameworkCore ;
10+ using Microsoft . EntityFrameworkCore . Infrastructure ;
11+ using Microsoft . EntityFrameworkCore . Storage ;
1012
1113namespace JsonApiDotNetCore . Internal . Generics
1214{
15+ // TODO: consider renaming to PatchRelationshipService (or something)
1316 public interface IGenericProcessor
1417 {
1518 Task UpdateRelationshipsAsync ( object parent , RelationshipAttribute relationship , IEnumerable < string > relationshipIds ) ;
16- void SetRelationships ( object parent , RelationshipAttribute relationship , IEnumerable < string > relationshipIds ) ;
1719 }
1820
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>
1926 public class GenericProcessor < T > : IGenericProcessor where T : class
2027 {
2128 private readonly DbContext _context ;
@@ -26,14 +33,21 @@ public GenericProcessor(IDbContextResolver contextResolver)
2633
2734 public virtual async Task UpdateRelationshipsAsync ( object parent , RelationshipAttribute relationship , IEnumerable < string > relationshipIds )
2835 {
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+ }
3244 }
3345
34- public virtual void SetRelationships ( object parent , RelationshipAttribute relationship , IEnumerable < string > relationshipIds )
46+ private async Task SetHasManyThroughRelationshipAsync ( IIdentifiable identifiableParent , HasManyThroughAttribute hasManyThrough , IEnumerable < string > relationshipIds )
3547 {
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 ( ) )
3751 {
3852 // ArticleTag
3953 ParameterExpression parameter = Expression . Parameter ( hasManyThrough . ThroughType ) ;
@@ -50,13 +64,14 @@ public virtual void SetRelationships(object parent, RelationshipAttribute relati
5064
5165 var lambda = Expression . Lambda < Func < T , bool > > ( equals , parameter ) ;
5266
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
5370 var oldLinks = _context
5471 . Set < T > ( )
5572 . Where ( lambda . Compile ( ) )
5673 . ToList ( ) ;
5774
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?
6075 _context . RemoveRange ( oldLinks ) ;
6176
6277 var newLinks = relationshipIds . Select ( x => {
@@ -67,8 +82,15 @@ public virtual void SetRelationships(object parent, RelationshipAttribute relati
6782 } ) ;
6883
6984 _context . AddRange ( newLinks ) ;
85+ await _context . SaveChangesAsync ( ) ;
86+
87+ transaction . Commit ( ) ;
7088 }
71- else if ( relationship . IsHasMany )
89+ }
90+
91+ private async Task SetRelationshipsAsync ( object parent , RelationshipAttribute relationship , IEnumerable < string > relationshipIds )
92+ {
93+ if ( relationship . IsHasMany )
7294 {
7395 // TODO: need to handle the failure mode when the relationship does not implement IIdentifiable
7496 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
80102 var entity = _context . Set < T > ( ) . SingleOrDefault ( x => relationshipIds . First ( ) == ( ( IIdentifiable ) x ) . StringId ) ;
81103 relationship . SetValue ( parent , entity ) ;
82104 }
105+
106+ await _context . SaveChangesAsync ( ) ;
83107 }
84108 }
85109}
0 commit comments