@@ -210,58 +210,89 @@ public void DetachRelationshipPointers(TEntity entity)
210210 /// This is used to allow creation of HasMany relationships when the
211211 /// dependent side of the relationship already exists.
212212 /// </summary>
213- private void AttachHasManyPointers ( TEntity entity )
213+ private void AttachHasManyAndHasManyThroughPointers ( TEntity entity )
214214 {
215215 var relationships = _jsonApiContext . HasManyRelationshipPointers . Get ( ) ;
216- foreach ( var relationship in relationships )
216+
217+ foreach ( var attribute in relationships . Keys )
217218 {
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+ }
220224 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 ;
222239 }
223240 }
224241
225- private void AttachHasMany ( TEntity entity , HasManyAttribute relationship , IList pointers )
242+ private void AttachHasMany ( TEntity entity , RelationshipAttribute attribute , Dictionary < RelationshipAttribute , IList > relationships )
226243 {
227- if ( relationship . EntityPropertyName != null )
244+ IEnumerable < IIdentifiable > pointers ;
245+ if ( attribute is HasManyThroughAttribute hasManyThrough )
228246 {
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
237249 {
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 > ( ) ;
243251 }
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 ;
244263 }
245264
246- private void AttachHasManyThrough ( TEntity entity , HasManyThroughAttribute hasManyThrough , IList pointers )
265+ private void AttachHasManyThrough ( TEntity entity , HasManyThroughAttribute hasManyThrough , Dictionary < RelationshipAttribute , IList > relationships )
247266 {
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 ;
254276
255277 foreach ( var pointer in pointers )
256278 {
257279 if ( _context . EntityIsTracked ( pointer as IIdentifiable ) == false )
258280 _context . Entry ( pointer ) . State = EntityState . Unchanged ;
259- var throughInstance = Activator . CreateInstance ( hasManyThrough . ThroughType ) ;
260281
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 ) ;
263293
264- throughRelationshipCollection . Add ( throughInstance ) ;
294+ throughRelationshipCollection . Add ( throughInstance ) ;
295+ }
265296 }
266297 }
267298
@@ -276,31 +307,16 @@ private void AttachHasOnePointers(TEntity entity)
276307
277308 foreach ( var relationship in relationships )
278309 {
279- var pointer = GetValueFromRelationship ( entity , relationship ) ;
310+ var pointer = GetRelationshipPointer ( entity , relationship ) ;
280311 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 ;
298314 }
299315 }
300316
301- IIdentifiable GetValueFromRelationship ( TEntity principalEntity , KeyValuePair < RelationshipAttribute , IIdentifiable > relationship )
317+ IIdentifiable GetRelationshipPointer ( TEntity principalEntity , KeyValuePair < HasOneAttribute , IIdentifiable > relationship )
302318 {
303- HasOneAttribute hasOne = ( HasOneAttribute ) relationship . Key ;
319+ HasOneAttribute hasOne = relationship . Key ;
304320 if ( hasOne . EntityPropertyName != null )
305321 {
306322 var relatedEntity = principalEntity . GetType ( ) . GetProperty ( hasOne . EntityPropertyName ) ? . GetValue ( principalEntity ) ;
@@ -309,6 +325,36 @@ IIdentifiable GetValueFromRelationship(TEntity principalEntity, KeyValuePair<Rel
309325 return relationship . Value ;
310326 }
311327
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+
312358
313359 /// <inheritdoc />
314360 public virtual async Task < TEntity > UpdateAsync ( TId id , TEntity entity )
0 commit comments