@@ -22,17 +22,15 @@ public class DefaultEntityRepository<TEntity>
2222 public DefaultEntityRepository (
2323 IJsonApiContext jsonApiContext ,
2424 IDbContextResolver contextResolver ,
25- ResourceDefinition < TEntity > resourceDefinition = null
26- )
25+ ResourceDefinition < TEntity > resourceDefinition = null )
2726 : base ( jsonApiContext , contextResolver , resourceDefinition )
2827 { }
2928
3029 public DefaultEntityRepository (
3130 ILoggerFactory loggerFactory ,
3231 IJsonApiContext jsonApiContext ,
33- IDbContextResolver contextResolver ,
34- ResourceDefinition < TEntity > resourceDefinition = null
35- )
32+ IDbContextResolver contextResolver ,
33+ ResourceDefinition < TEntity > resourceDefinition = null )
3634 : base ( loggerFactory , jsonApiContext , contextResolver , resourceDefinition )
3735 { }
3836 }
@@ -55,8 +53,7 @@ public class DefaultEntityRepository<TEntity, TId>
5553 public DefaultEntityRepository (
5654 IJsonApiContext jsonApiContext ,
5755 IDbContextResolver contextResolver ,
58- ResourceDefinition < TEntity > resourceDefinition = null
59- )
56+ ResourceDefinition < TEntity > resourceDefinition = null )
6057 {
6158 _context = contextResolver . GetContext ( ) ;
6259 _dbSet = contextResolver . GetDbSet < TEntity > ( ) ;
@@ -69,8 +66,7 @@ public DefaultEntityRepository(
6966 ILoggerFactory loggerFactory ,
7067 IJsonApiContext jsonApiContext ,
7168 IDbContextResolver contextResolver ,
72- ResourceDefinition < TEntity > resourceDefinition = null
73- )
69+ ResourceDefinition < TEntity > resourceDefinition = null )
7470 {
7571 _context = contextResolver . GetContext ( ) ;
7672 _dbSet = contextResolver . GetDbSet < TEntity > ( ) ;
@@ -151,14 +147,15 @@ public virtual async Task<TEntity> CreateAsync(TEntity entity)
151147 foreach ( var relationshipAttr in _jsonApiContext . RelationshipsToUpdate ? . Keys )
152148 {
153149 var trackedRelationshipValue = GetTrackedRelationshipValue ( relationshipAttr , entity , out bool wasAlreadyTracked ) ;
154- // LoadInverseRelationships(trackedRelationshipValue, relationshipAttribute)
150+ LoadInverseRelationships ( trackedRelationshipValue , relationshipAttr ) ;
155151 if ( wasAlreadyTracked )
156152 {
157153 /// We only need to reassign the relationship value to the to-be-added
158154 /// entity when we're using a different instance (because this different one
159155 /// was already tracked) than the one assigned to the to-be-created entity.
160156 AssignRelationshipValue ( entity , trackedRelationshipValue , relationshipAttr ) ;
161- } else if ( relationshipAttr is HasManyThroughAttribute throughAttr )
157+ }
158+ else if ( relationshipAttr is HasManyThroughAttribute throughAttr )
162159 {
163160 /// even if we don't have to reassign anything because of already tracked
164161 /// entities, we still need to assign the "through" entities in the case of many-to-many.
@@ -171,21 +168,41 @@ public virtual async Task<TEntity> CreateAsync(TEntity entity)
171168 return entity ;
172169 }
173170
171+ /// <summary>
172+ /// Loads the inverse relationships to prevent foreign key constraints from being violated
173+ /// to support implicit removes, see https://github.com/json-api-dotnet/JsonApiDotNetCore/issues/502.
174+ /// </summary>
175+ private void LoadInverseRelationships ( object trackedRelationshipValue , RelationshipAttribute relationshipAttr )
176+ {
177+ if ( relationshipAttr . InverseNavigation == null ) return ;
178+ if ( relationshipAttr is HasOneAttribute hasOneAttr )
179+ {
180+ _context . Entry ( ( IIdentifiable ) trackedRelationshipValue ) . Reference ( hasOneAttr . InverseNavigation ) . Load ( ) ;
181+ }
182+ else if ( relationshipAttr is HasManyAttribute hasManyAttr )
183+ {
184+ foreach ( IIdentifiable relationshipValue in ( IList ) trackedRelationshipValue )
185+ {
186+ _context . Entry ( ( IIdentifiable ) trackedRelationshipValue ) . Reference ( hasManyAttr . InverseNavigation ) . Load ( ) ;
187+ }
188+ }
189+ }
190+
174191
175192 /// <inheritdoc />
176193 public void DetachRelationshipPointers ( TEntity entity )
177194 {
178195
179196 foreach ( var relationshipAttr in _jsonApiContext . RelationshipsToUpdate . Keys )
180197 {
181- if ( relationshipAttr is HasOneAttribute hasOneAttr )
198+ if ( relationshipAttr is HasOneAttribute hasOneAttr )
182199 {
183200 var relationshipValue = GetEntityResourceSeparationValue ( entity , hasOneAttr ) ?? ( IIdentifiable ) hasOneAttr . GetValue ( entity ) ;
184201 if ( relationshipValue == null ) continue ;
185202 _context . Entry ( relationshipValue ) . State = EntityState . Detached ;
186203
187204 }
188- else
205+ else
189206 {
190207 IEnumerable < IIdentifiable > relationshipValueList = ( IEnumerable < IIdentifiable > ) relationshipAttr . GetValue ( entity ) ;
191208 /// This adds support for resource-entity separation in the case of one-to-many.
@@ -224,7 +241,7 @@ public virtual async Task<TEntity> UpdateAsync(TId id, TEntity updatedEntity)
224241 {
225242 LoadCurrentRelationships ( oldEntity , relationshipAttr ) ;
226243 var trackedRelationshipValue = GetTrackedRelationshipValue ( relationshipAttr , updatedEntity , out bool wasAlreadyTracked ) ;
227- // LoadInverseRelationships(trackedRelationshipValue, relationshipAttribute)
244+ LoadInverseRelationships ( trackedRelationshipValue , relationshipAttr ) ;
228245 AssignRelationshipValue ( oldEntity , trackedRelationshipValue , relationshipAttr ) ;
229246 }
230247 await _context . SaveChangesAsync ( ) ;
@@ -241,26 +258,27 @@ public virtual async Task<TEntity> UpdateAsync(TId id, TEntity updatedEntity)
241258 private object GetTrackedRelationshipValue ( RelationshipAttribute relationshipAttr , TEntity entity , out bool wasAlreadyAttached )
242259 {
243260 wasAlreadyAttached = false ;
244- if ( relationshipAttr is HasOneAttribute hasOneAttribute )
261+ if ( relationshipAttr is HasOneAttribute hasOneAttr )
245262 {
246263 /// This adds support for resource-entity separation in the case of one-to-one.
247- var relationshipValue = GetEntityResourceSeparationValue ( entity , hasOneAttribute ) ?? ( IIdentifiable ) hasOneAttribute . GetValue ( entity ) ;
248- if ( relationshipValue == null )
249- return null ;
250- return GetTrackedHasOneRelationshipValue ( relationshipValue , hasOneAttribute , ref wasAlreadyAttached ) ;
264+ var relationshipValue = GetEntityResourceSeparationValue ( entity , hasOneAttr ) ?? ( IIdentifiable ) hasOneAttr . GetValue ( entity ) ;
265+ if ( relationshipValue == null )
266+ return null ;
267+ return GetTrackedHasOneRelationshipValue ( relationshipValue , hasOneAttr , ref wasAlreadyAttached ) ;
251268 }
252269 else
253270 {
254271 IEnumerable < IIdentifiable > relationshipValueList = ( IEnumerable < IIdentifiable > ) relationshipAttr . GetValue ( entity ) ;
255272 /// This adds support for resource-entity separation in the case of one-to-many.
256273 /// todo: currently there is no support for many to many relations.
257- if ( relationshipAttr is HasManyAttribute hasMany )
258- relationshipValueList = GetEntityResourceSeparationValue ( entity , hasMany ) ?? relationshipValueList ;
274+ if ( relationshipAttr is HasManyAttribute hasMany )
275+ relationshipValueList = GetEntityResourceSeparationValue ( entity , hasMany ) ?? relationshipValueList ;
259276 if ( relationshipValueList == null ) return null ;
260277 return GetTrackedManyRelationshipValue ( relationshipValueList , relationshipAttr , ref wasAlreadyAttached ) ;
261278 }
262279 }
263280
281+ // helper method used in GetTrackedRelationshipValue. See comments there.
264282 private IList GetTrackedManyRelationshipValue ( IEnumerable < IIdentifiable > relationshipValueList , RelationshipAttribute relationshipAttr , ref bool wasAlreadyAttached )
265283 {
266284 if ( relationshipValueList == null ) return null ;
@@ -272,7 +290,7 @@ private IList GetTrackedManyRelationshipValue(IEnumerable<IIdentifiable> relatio
272290 /// this will point to the Resource type in the case of entity resource
273291 /// separation. We should consider to store entity type on
274292 /// the relationship attribute too.
275- entityType = pointer . GetType ( ) ;
293+ entityType = entityType ?? pointer . GetType ( ) ;
276294 var tracked = AttachOrGetTracked ( pointer ) ;
277295 if ( tracked != null ) _wasAlreadyAttached = true ;
278296 return Convert . ChangeType ( tracked ?? pointer , entityType ) ;
@@ -281,9 +299,9 @@ private IList GetTrackedManyRelationshipValue(IEnumerable<IIdentifiable> relatio
281299 return ( IList ) trackedPointerCollection ;
282300 }
283301
284- private IIdentifiable GetTrackedHasOneRelationshipValue ( IIdentifiable relationshipValue , HasOneAttribute hasOneAttribute , ref bool wasAlreadyAttached )
302+ // helper method used in GetTrackedRelationshipValue. See comments there.
303+ private IIdentifiable GetTrackedHasOneRelationshipValue ( IIdentifiable relationshipValue , HasOneAttribute hasOneAttr , ref bool wasAlreadyAttached )
285304 {
286-
287305 var tracked = AttachOrGetTracked ( relationshipValue ) ;
288306 if ( tracked != null ) wasAlreadyAttached = true ;
289307 return tracked ?? relationshipValue ;
@@ -298,7 +316,7 @@ public async Task UpdateRelationshipsAsync(object parent, RelationshipAttribute
298316 // of the property...
299317 var typeToUpdate = ( relationship is HasManyThroughAttribute hasManyThrough )
300318 ? hasManyThrough . ThroughType
301- : relationship . DependentType ;
319+ : relationship . Type ;
302320
303321 var genericProcessor = _genericProcessorFactory . GetProcessor < IGenericProcessor > ( typeof ( GenericProcessor < > ) , typeToUpdate ) ;
304322 await genericProcessor . UpdateRelationshipsAsync ( parent , relationship , relationshipIds ) ;
@@ -346,7 +364,7 @@ public virtual IQueryable<TEntity> Include(IQueryable<TEntity> entities, string
346364 : $ "{ internalRelationshipPath } .{ relationship . RelationshipPath } ";
347365
348366 if ( i < relationshipChain . Length )
349- entity = _jsonApiContext . ResourceGraph . GetContextEntity ( relationship . DependentType ) ;
367+ entity = _jsonApiContext . ResourceGraph . GetContextEntity ( relationship . Type ) ;
350368 }
351369
352370 return entities . Include ( internalRelationshipPath ) ;
@@ -357,9 +375,7 @@ public virtual async Task<IEnumerable<TEntity>> PageAsync(IQueryable<TEntity> en
357375 {
358376 if ( pageNumber >= 0 )
359377 {
360- // the IQueryable returned from the hook executor is sometimes consumed here.
361- // In this case, it does not support .ToListAsync(), so we use the method below.
362- return await this . ToListAsync ( entities . PageForward ( pageSize , pageNumber ) ) ;
378+ return await entities . PageForward ( pageSize , pageNumber ) . ToListAsync ( ) ;
363379 }
364380
365381 // since EntityFramework does not support IQueryable.Reverse(), we need to know the number of queried entities
@@ -369,9 +385,10 @@ public virtual async Task<IEnumerable<TEntity>> PageAsync(IQueryable<TEntity> en
369385 int virtualFirstIndex = numberOfEntities - pageSize * Math . Abs ( pageNumber ) ;
370386 int numberOfElementsInPage = Math . Min ( pageSize , virtualFirstIndex + pageSize ) ;
371387
372- return await ToListAsync ( entities
388+ return await entities
373389 . Skip ( virtualFirstIndex )
374- . Take ( numberOfElementsInPage ) ) ;
390+ . Take ( numberOfElementsInPage )
391+ . ToListAsync ( ) ;
375392 }
376393
377394 /// <inheritdoc />
0 commit comments