@@ -210,58 +210,89 @@ public void DetachRelationshipPointers(TEntity entity)
210
210
/// This is used to allow creation of HasMany relationships when the
211
211
/// dependent side of the relationship already exists.
212
212
/// </summary>
213
- private void AttachHasManyPointers ( TEntity entity )
213
+ private void AttachHasManyAndHasManyThroughPointers ( TEntity entity )
214
214
{
215
215
var relationships = _jsonApiContext . HasManyRelationshipPointers . Get ( ) ;
216
- foreach ( var relationship in relationships )
216
+
217
+ foreach ( var attribute in relationships . Keys )
217
218
{
218
- if ( relationship . Key is HasManyThroughAttribute hasManyThrough )
219
- AttachHasManyThrough ( entity , hasManyThrough , relationship . Value ) ;
219
+ IEnumerable < IIdentifiable > pointers ;
220
+ if ( attribute is HasManyThroughAttribute hasManyThrough )
221
+ {
222
+ pointers = relationships [ attribute ] . Cast < IIdentifiable > ( ) ;
223
+ }
220
224
else
221
- AttachHasMany ( entity , relationship . Key as HasManyAttribute , relationship . Value ) ;
225
+ {
226
+ pointers = GetRelationshipPointers ( entity , ( HasManyAttribute ) attribute ) ?? relationships [ attribute ] . Cast < IIdentifiable > ( ) ;
227
+ }
228
+
229
+ if ( pointers == null ) continue ;
230
+ bool alreadyTracked = false ;
231
+ var newPointerCollection = pointers . Select ( pointer =>
232
+ {
233
+ var tracked = AttachOrGetTracked ( pointer ) ;
234
+ if ( tracked != null ) alreadyTracked = true ;
235
+ return tracked ?? pointer ;
236
+ } ) ;
237
+
238
+ if ( alreadyTracked ) relationships [ attribute ] = ( IList ) newPointerCollection ;
222
239
}
223
240
}
224
241
225
- private void AttachHasMany ( TEntity entity , HasManyAttribute relationship , IList pointers )
242
+ private void AttachHasMany ( TEntity entity , RelationshipAttribute attribute , Dictionary < RelationshipAttribute , IList > relationships )
226
243
{
227
- if ( relationship . EntityPropertyName != null )
244
+ IEnumerable < IIdentifiable > pointers ;
245
+ if ( attribute is HasManyThroughAttribute hasManyThrough )
228
246
{
229
- var relatedList = ( IList ) entity . GetType ( ) . GetProperty ( relationship . EntityPropertyName ) ? . GetValue ( entity ) ;
230
- foreach ( var related in relatedList )
231
- {
232
- if ( _context . EntityIsTracked ( ( IIdentifiable ) related ) == false )
233
- _context . Entry ( related ) . State = EntityState . Unchanged ;
234
- }
235
- }
236
- else
247
+ pointers = relationships [ attribute ] . Cast < IIdentifiable > ( ) ;
248
+ } else
237
249
{
238
- foreach ( var pointer in pointers )
239
- {
240
- if ( _context . EntityIsTracked ( ( IIdentifiable ) pointer ) == false )
241
- _context . Entry ( pointer ) . State = EntityState . Unchanged ;
242
- }
250
+ pointers = GetRelationshipPointers ( entity , ( HasManyAttribute ) attribute ) ?? relationships [ attribute ] . Cast < IIdentifiable > ( ) ;
243
251
}
252
+
253
+ if ( pointers == null ) return ;
254
+ bool alreadyTracked = false ;
255
+ var newPointerCollection = pointers . Select ( pointer =>
256
+ {
257
+ var tracked = AttachOrGetTracked ( pointer ) ;
258
+ if ( tracked != null ) alreadyTracked = true ;
259
+ return tracked ?? pointer ;
260
+ } ) ;
261
+
262
+ if ( alreadyTracked ) relationships [ attribute ] = ( IList ) newPointerCollection ;
244
263
}
245
264
246
- private void AttachHasManyThrough ( TEntity entity , HasManyThroughAttribute hasManyThrough , IList pointers )
265
+ private void AttachHasManyThrough ( TEntity entity , HasManyThroughAttribute hasManyThrough , Dictionary < RelationshipAttribute , IList > relationships )
247
266
{
248
- // create the collection (e.g. List<ArticleTag>)
249
- // this type MUST implement IList so we can build the collection
250
- // if this is problematic, we _could_ reflect on the type and find an Add method
251
- // or we might be able to create a proxy type and implement the enumerator
252
- var throughRelationshipCollection = Activator . CreateInstance ( hasManyThrough . ThroughProperty . PropertyType ) as IList ;
253
- hasManyThrough . ThroughProperty . SetValue ( entity , throughRelationshipCollection ) ;
267
+ var pointers = relationships [ hasManyThrough ] . Cast < IIdentifiable > ( ) ;
268
+ bool alreadyTracked = false ;
269
+ var newPointerCollection = pointers . Select ( pointer =>
270
+ {
271
+ var tracked = AttachOrGetTracked ( pointer ) ;
272
+ if ( tracked != null ) alreadyTracked = true ;
273
+ return tracked ?? pointer ;
274
+ } ) ;
275
+ if ( alreadyTracked ) relationships [ hasManyAttribute ] = ( IList ) newPointerCollection ;
254
276
255
277
foreach ( var pointer in pointers )
256
278
{
257
279
if ( _context . EntityIsTracked ( pointer as IIdentifiable ) == false )
258
280
_context . Entry ( pointer ) . State = EntityState . Unchanged ;
259
- var throughInstance = Activator . CreateInstance ( hasManyThrough . ThroughType ) ;
260
281
261
- hasManyThrough . LeftProperty . SetValue ( throughInstance , entity ) ;
262
- hasManyThrough . RightProperty . SetValue ( throughInstance , pointer ) ;
282
+ var throughRelationshipCollection = Activator . CreateInstance ( hasManyThrough . ThroughProperty . PropertyType ) as IList ;
283
+ hasManyThrough . ThroughProperty . SetValue ( entity , throughRelationshipCollection ) ;
284
+
285
+ foreach ( var pointer in pointers )
286
+ {
287
+ if ( _context . EntityIsTracked ( pointer as IIdentifiable ) == false )
288
+ _context . Entry ( pointer ) . State = EntityState . Unchanged ;
289
+ var throughInstance = Activator . CreateInstance ( hasManyThrough . ThroughType ) ;
290
+
291
+ hasManyThrough . LeftProperty . SetValue ( throughInstance , entity ) ;
292
+ hasManyThrough . RightProperty . SetValue ( throughInstance , pointer ) ;
263
293
264
- throughRelationshipCollection . Add ( throughInstance ) ;
294
+ throughRelationshipCollection . Add ( throughInstance ) ;
295
+ }
265
296
}
266
297
}
267
298
@@ -276,31 +307,16 @@ private void AttachHasOnePointers(TEntity entity)
276
307
277
308
foreach ( var relationship in relationships )
278
309
{
279
- var pointer = GetValueFromRelationship ( entity , relationship ) ;
310
+ var pointer = GetRelationshipPointer ( entity , relationship ) ;
280
311
if ( pointer == null ) return ;
281
- var trackedEntity = _context . GetTrackedEntity ( pointer ) ;
282
-
283
- // useful article: https://stackoverflow.com/questions/30987806/dbset-attachentity-vs-dbcontext-entryentity-state-entitystate-modified
284
- if ( trackedEntity == null )
285
- {
286
- /// the relationship pointer is new to EF Core, but we are sure
287
- /// it exists in the database (json:api spec), so we attach it.
288
- _context . Entry ( pointer ) . State = EntityState . Unchanged ;
289
- } else
290
- {
291
- /// there already was an instance of this type and ID tracked
292
- /// by EF Core. Reattaching is not allowed, and from now on we
293
- /// will use the already attached one instead. (This entry might
294
- /// contain updated fields as a result of Business logic.
295
- relationships [ relationship . Key ] = trackedEntity ;
296
- }
297
-
312
+ var tracked = AttachOrGetTracked ( pointer ) ;
313
+ if ( tracked != null ) relationships [ relationship . Key ] = tracked ;
298
314
}
299
315
}
300
316
301
- IIdentifiable GetValueFromRelationship ( TEntity principalEntity , KeyValuePair < RelationshipAttribute , IIdentifiable > relationship )
317
+ IIdentifiable GetRelationshipPointer ( TEntity principalEntity , KeyValuePair < HasOneAttribute , IIdentifiable > relationship )
302
318
{
303
- HasOneAttribute hasOne = ( HasOneAttribute ) relationship . Key ;
319
+ HasOneAttribute hasOne = relationship . Key ;
304
320
if ( hasOne . EntityPropertyName != null )
305
321
{
306
322
var relatedEntity = principalEntity . GetType ( ) . GetProperty ( hasOne . EntityPropertyName ) ? . GetValue ( principalEntity ) ;
@@ -309,6 +325,36 @@ IIdentifiable GetValueFromRelationship(TEntity principalEntity, KeyValuePair<Rel
309
325
return relationship . Value ;
310
326
}
311
327
328
+ IEnumerable < IIdentifiable > GetRelationshipPointers ( TEntity entity , HasManyAttribute attribute )
329
+ {
330
+ if ( attribute . EntityPropertyName == null )
331
+ {
332
+ return null ;
333
+ }
334
+ return ( ( IEnumerable ) ( entity . GetType ( ) . GetProperty ( attribute . EntityPropertyName ) ? . GetValue ( entity ) ) ) . Cast < IIdentifiable > ( ) ;
335
+ }
336
+
337
+ // useful article: https://stackoverflow.com/questions/30987806/dbset-attachentity-vs-dbcontext-entryentity-state-entitystate-modified
338
+ IIdentifiable AttachOrGetTracked ( IIdentifiable pointer )
339
+ {
340
+ var trackedEntity = _context . GetTrackedEntity ( pointer ) ;
341
+
342
+
343
+ if ( trackedEntity != null )
344
+ {
345
+ /// there already was an instance of this type and ID tracked
346
+ /// by EF Core. Reattaching will produce a conflict, and from now on we
347
+ /// will use the already attached one instead. (This entry might
348
+ /// contain updated fields as a result of business logic)
349
+ return trackedEntity ;
350
+ }
351
+
352
+ /// the relationship pointer is new to EF Core, but we are sure
353
+ /// it exists in the database (json:api spec), so we attach it.
354
+ _context . Entry ( pointer ) . State = EntityState . Unchanged ;
355
+ return null ;
356
+ }
357
+
312
358
313
359
/// <inheritdoc />
314
360
public virtual async Task < TEntity > UpdateAsync ( TId id , TEntity entity )
0 commit comments