@@ -22,17 +22,15 @@ public class DefaultEntityRepository<TEntity>
22
22
public DefaultEntityRepository (
23
23
IJsonApiContext jsonApiContext ,
24
24
IDbContextResolver contextResolver ,
25
- ResourceDefinition < TEntity > resourceDefinition = null
26
- )
25
+ ResourceDefinition < TEntity > resourceDefinition = null )
27
26
: base ( jsonApiContext , contextResolver , resourceDefinition )
28
27
{ }
29
28
30
29
public DefaultEntityRepository (
31
30
ILoggerFactory loggerFactory ,
32
31
IJsonApiContext jsonApiContext ,
33
- IDbContextResolver contextResolver ,
34
- ResourceDefinition < TEntity > resourceDefinition = null
35
- )
32
+ IDbContextResolver contextResolver ,
33
+ ResourceDefinition < TEntity > resourceDefinition = null )
36
34
: base ( loggerFactory , jsonApiContext , contextResolver , resourceDefinition )
37
35
{ }
38
36
}
@@ -55,8 +53,7 @@ public class DefaultEntityRepository<TEntity, TId>
55
53
public DefaultEntityRepository (
56
54
IJsonApiContext jsonApiContext ,
57
55
IDbContextResolver contextResolver ,
58
- ResourceDefinition < TEntity > resourceDefinition = null
59
- )
56
+ ResourceDefinition < TEntity > resourceDefinition = null )
60
57
{
61
58
_context = contextResolver . GetContext ( ) ;
62
59
_dbSet = contextResolver . GetDbSet < TEntity > ( ) ;
@@ -69,8 +66,7 @@ public DefaultEntityRepository(
69
66
ILoggerFactory loggerFactory ,
70
67
IJsonApiContext jsonApiContext ,
71
68
IDbContextResolver contextResolver ,
72
- ResourceDefinition < TEntity > resourceDefinition = null
73
- )
69
+ ResourceDefinition < TEntity > resourceDefinition = null )
74
70
{
75
71
_context = contextResolver . GetContext ( ) ;
76
72
_dbSet = contextResolver . GetDbSet < TEntity > ( ) ;
@@ -151,14 +147,15 @@ public virtual async Task<TEntity> CreateAsync(TEntity entity)
151
147
foreach ( var relationshipAttr in _jsonApiContext . RelationshipsToUpdate ? . Keys )
152
148
{
153
149
var trackedRelationshipValue = GetTrackedRelationshipValue ( relationshipAttr , entity , out bool wasAlreadyTracked ) ;
154
- // LoadInverseRelationships(trackedRelationshipValue, relationshipAttribute)
150
+ LoadInverseRelationships ( trackedRelationshipValue , relationshipAttr ) ;
155
151
if ( wasAlreadyTracked )
156
152
{
157
153
/// We only need to reassign the relationship value to the to-be-added
158
154
/// entity when we're using a different instance (because this different one
159
155
/// was already tracked) than the one assigned to the to-be-created entity.
160
156
AssignRelationshipValue ( entity , trackedRelationshipValue , relationshipAttr ) ;
161
- } else if ( relationshipAttr is HasManyThroughAttribute throughAttr )
157
+ }
158
+ else if ( relationshipAttr is HasManyThroughAttribute throughAttr )
162
159
{
163
160
/// even if we don't have to reassign anything because of already tracked
164
161
/// 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)
171
168
return entity ;
172
169
}
173
170
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
+
174
191
175
192
/// <inheritdoc />
176
193
public void DetachRelationshipPointers ( TEntity entity )
177
194
{
178
195
179
196
foreach ( var relationshipAttr in _jsonApiContext . RelationshipsToUpdate . Keys )
180
197
{
181
- if ( relationshipAttr is HasOneAttribute hasOneAttr )
198
+ if ( relationshipAttr is HasOneAttribute hasOneAttr )
182
199
{
183
200
var relationshipValue = GetEntityResourceSeparationValue ( entity , hasOneAttr ) ?? ( IIdentifiable ) hasOneAttr . GetValue ( entity ) ;
184
201
if ( relationshipValue == null ) continue ;
185
202
_context . Entry ( relationshipValue ) . State = EntityState . Detached ;
186
203
187
204
}
188
- else
205
+ else
189
206
{
190
207
IEnumerable < IIdentifiable > relationshipValueList = ( IEnumerable < IIdentifiable > ) relationshipAttr . GetValue ( entity ) ;
191
208
/// 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)
224
241
{
225
242
LoadCurrentRelationships ( oldEntity , relationshipAttr ) ;
226
243
var trackedRelationshipValue = GetTrackedRelationshipValue ( relationshipAttr , updatedEntity , out bool wasAlreadyTracked ) ;
227
- // LoadInverseRelationships(trackedRelationshipValue, relationshipAttribute)
244
+ LoadInverseRelationships ( trackedRelationshipValue , relationshipAttr ) ;
228
245
AssignRelationshipValue ( oldEntity , trackedRelationshipValue , relationshipAttr ) ;
229
246
}
230
247
await _context . SaveChangesAsync ( ) ;
@@ -241,26 +258,27 @@ public virtual async Task<TEntity> UpdateAsync(TId id, TEntity updatedEntity)
241
258
private object GetTrackedRelationshipValue ( RelationshipAttribute relationshipAttr , TEntity entity , out bool wasAlreadyAttached )
242
259
{
243
260
wasAlreadyAttached = false ;
244
- if ( relationshipAttr is HasOneAttribute hasOneAttribute )
261
+ if ( relationshipAttr is HasOneAttribute hasOneAttr )
245
262
{
246
263
/// 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 ) ;
251
268
}
252
269
else
253
270
{
254
271
IEnumerable < IIdentifiable > relationshipValueList = ( IEnumerable < IIdentifiable > ) relationshipAttr . GetValue ( entity ) ;
255
272
/// This adds support for resource-entity separation in the case of one-to-many.
256
273
/// 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 ;
259
276
if ( relationshipValueList == null ) return null ;
260
277
return GetTrackedManyRelationshipValue ( relationshipValueList , relationshipAttr , ref wasAlreadyAttached ) ;
261
278
}
262
279
}
263
280
281
+ // helper method used in GetTrackedRelationshipValue. See comments there.
264
282
private IList GetTrackedManyRelationshipValue ( IEnumerable < IIdentifiable > relationshipValueList , RelationshipAttribute relationshipAttr , ref bool wasAlreadyAttached )
265
283
{
266
284
if ( relationshipValueList == null ) return null ;
@@ -272,7 +290,7 @@ private IList GetTrackedManyRelationshipValue(IEnumerable<IIdentifiable> relatio
272
290
/// this will point to the Resource type in the case of entity resource
273
291
/// separation. We should consider to store entity type on
274
292
/// the relationship attribute too.
275
- entityType = pointer . GetType ( ) ;
293
+ entityType = entityType ?? pointer . GetType ( ) ;
276
294
var tracked = AttachOrGetTracked ( pointer ) ;
277
295
if ( tracked != null ) _wasAlreadyAttached = true ;
278
296
return Convert . ChangeType ( tracked ?? pointer , entityType ) ;
@@ -281,9 +299,9 @@ private IList GetTrackedManyRelationshipValue(IEnumerable<IIdentifiable> relatio
281
299
return ( IList ) trackedPointerCollection ;
282
300
}
283
301
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 )
285
304
{
286
-
287
305
var tracked = AttachOrGetTracked ( relationshipValue ) ;
288
306
if ( tracked != null ) wasAlreadyAttached = true ;
289
307
return tracked ?? relationshipValue ;
@@ -298,7 +316,7 @@ public async Task UpdateRelationshipsAsync(object parent, RelationshipAttribute
298
316
// of the property...
299
317
var typeToUpdate = ( relationship is HasManyThroughAttribute hasManyThrough )
300
318
? hasManyThrough . ThroughType
301
- : relationship . DependentType ;
319
+ : relationship . Type ;
302
320
303
321
var genericProcessor = _genericProcessorFactory . GetProcessor < IGenericProcessor > ( typeof ( GenericProcessor < > ) , typeToUpdate ) ;
304
322
await genericProcessor . UpdateRelationshipsAsync ( parent , relationship , relationshipIds ) ;
@@ -346,7 +364,7 @@ public virtual IQueryable<TEntity> Include(IQueryable<TEntity> entities, string
346
364
: $ "{ internalRelationshipPath } .{ relationship . RelationshipPath } ";
347
365
348
366
if ( i < relationshipChain . Length )
349
- entity = _jsonApiContext . ResourceGraph . GetContextEntity ( relationship . DependentType ) ;
367
+ entity = _jsonApiContext . ResourceGraph . GetContextEntity ( relationship . Type ) ;
350
368
}
351
369
352
370
return entities . Include ( internalRelationshipPath ) ;
@@ -357,9 +375,7 @@ public virtual async Task<IEnumerable<TEntity>> PageAsync(IQueryable<TEntity> en
357
375
{
358
376
if ( pageNumber >= 0 )
359
377
{
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 ( ) ;
363
379
}
364
380
365
381
// 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
369
385
int virtualFirstIndex = numberOfEntities - pageSize * Math . Abs ( pageNumber ) ;
370
386
int numberOfElementsInPage = Math . Min ( pageSize , virtualFirstIndex + pageSize ) ;
371
387
372
- return await ToListAsync ( entities
388
+ return await entities
373
389
. Skip ( virtualFirstIndex )
374
- . Take ( numberOfElementsInPage ) ) ;
390
+ . Take ( numberOfElementsInPage )
391
+ . ToListAsync ( ) ;
375
392
}
376
393
377
394
/// <inheritdoc />
0 commit comments