Skip to content

Commit e8d1096

Browse files
committed
refactor: AssignHasMany pointers
1 parent 5f3e04b commit e8d1096

File tree

1 file changed

+97
-51
lines changed

1 file changed

+97
-51
lines changed

src/JsonApiDotNetCore/Data/DefaultEntityRepository.cs

Lines changed: 97 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)