Skip to content

Commit 63b6aa4

Browse files
Copilotdwarwick
andcommitted
Add SyncCollection helper method for collection updates
Co-authored-by: dwarwick <15970276+dwarwick@users.noreply.github.com>
1 parent ceaca55 commit 63b6aa4

File tree

1 file changed

+83
-66
lines changed

1 file changed

+83
-66
lines changed

JwtIdentity/Controllers/SurveyController.cs

Lines changed: 83 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -326,40 +326,22 @@ public async Task<ActionResult<SurveyViewModel>> PostSurvey(SurveyViewModel surv
326326

327327
if (existingMCQuestion != null && newMCQuestion != null)
328328
{
329-
// check if any options have changed
330-
foreach (var newOption in newMCQuestion.Options ?? new List<ChoiceOption>())
331-
{
332-
if (newOption.Id == 0)
333-
{ // new option
334-
newOption.MultipleChoiceQuestionId = passedInQuestion.Id;
335-
existingMCQuestion.Options.Add(newOption);
329+
// Sync the Options collection
330+
SyncCollection(
331+
existingCollection: existingMCQuestion.Options,
332+
incoming: newMCQuestion.Options ?? new List<ChoiceOption>(),
333+
keySelector: o => o.Id,
334+
entityKeySelector: o => o.Id,
335+
updateEntity: (existing, dto) =>
336+
{
337+
_context.Entry(existing).CurrentValues.SetValues(dto);
338+
},
339+
createEntity: dto =>
340+
{
341+
dto.MultipleChoiceQuestionId = passedInQuestion.Id;
342+
return dto;
336343
}
337-
else
338-
{ // existing option
339-
var existingOption = existingMCQuestion.Options.FirstOrDefault(o => o.Id == newOption.Id);
340-
341-
if (existingOption != null)
342-
{
343-
// Update all scalar properties using SetValues
344-
_context.Entry(existingOption).CurrentValues.SetValues(newOption);
345-
}
346-
}
347-
}
348-
349-
// remove any options that are no longer present
350-
var newOptionIds = (newMCQuestion.Options ?? new List<ChoiceOption>())
351-
.Where(o => o.Id != 0)
352-
.Select(o => o.Id)
353-
.ToHashSet();
354-
355-
var removedOptions = existingMCQuestion.Options
356-
.Where(o => o.Id != 0 && !newOptionIds.Contains(o.Id))
357-
.ToList();
358-
359-
if (removedOptions.Any())
360-
{
361-
_context.ChoiceOptions.RemoveRange(removedOptions);
362-
}
344+
);
363345
}
364346
break;
365347

@@ -379,40 +361,22 @@ public async Task<ActionResult<SurveyViewModel>> PostSurvey(SurveyViewModel surv
379361

380362
if (existingSAQuestion != null && newSAQuestion != null)
381363
{
382-
// check if any options have changed
383-
foreach (var newOption in newSAQuestion.Options ?? new List<ChoiceOption>())
384-
{
385-
if (newOption.Id == 0)
386-
{ // new option
387-
newOption.SelectAllThatApplyQuestionId = passedInQuestion.Id;
388-
existingSAQuestion.Options.Add(newOption);
389-
}
390-
else
391-
{ // existing option
392-
var existingOption = existingSAQuestion.Options.FirstOrDefault(o => o.Id == newOption.Id);
393-
394-
if (existingOption != null)
395-
{
396-
// Update all scalar properties using SetValues
397-
_context.Entry(existingOption).CurrentValues.SetValues(newOption);
398-
}
364+
// Sync the Options collection
365+
SyncCollection(
366+
existingCollection: existingSAQuestion.Options,
367+
incoming: newSAQuestion.Options ?? new List<ChoiceOption>(),
368+
keySelector: o => o.Id,
369+
entityKeySelector: o => o.Id,
370+
updateEntity: (existing, dto) =>
371+
{
372+
_context.Entry(existing).CurrentValues.SetValues(dto);
373+
},
374+
createEntity: dto =>
375+
{
376+
dto.SelectAllThatApplyQuestionId = passedInQuestion.Id;
377+
return dto;
399378
}
400-
}
401-
402-
// remove any options that are no longer present
403-
var newOptionIds = (newSAQuestion.Options ?? new List<ChoiceOption>())
404-
.Where(o => o.Id != 0)
405-
.Select(o => o.Id)
406-
.ToHashSet();
407-
408-
var removedOptions = existingSAQuestion.Options
409-
.Where(o => o.Id != 0 && !newOptionIds.Contains(o.Id))
410-
.ToList();
411-
412-
if (removedOptions.Any())
413-
{
414-
_context.ChoiceOptions.RemoveRange(removedOptions);
415-
}
379+
);
416380
}
417381
break;
418382
}
@@ -736,5 +700,58 @@ private bool SurveyExists(int id)
736700
{
737701
return _context.Surveys.Any(e => e.Id == id);
738702
}
703+
704+
/// <summary>
705+
/// Synchronizes a child collection by adding new entities, updating existing ones, and removing obsolete ones.
706+
/// </summary>
707+
private void SyncCollection<TEntity, TDto, TKey>(
708+
ICollection<TEntity> existingCollection,
709+
IEnumerable<TDto> incoming,
710+
Func<TDto, TKey> keySelector,
711+
Func<TEntity, TKey> entityKeySelector,
712+
Action<TEntity, TDto> updateEntity,
713+
Func<TDto, TEntity> createEntity)
714+
where TKey : notnull
715+
{
716+
// Get keys of incoming items (excluding new items with default key like 0)
717+
var incomingKeys = incoming
718+
.Select(keySelector)
719+
.Where(k => !EqualityComparer<TKey>.Default.Equals(k, default(TKey)))
720+
.ToHashSet();
721+
722+
// Remove items that are no longer in the incoming collection
723+
var toRemove = existingCollection
724+
.Where(e => !EqualityComparer<TKey>.Default.Equals(entityKeySelector(e), default(TKey))
725+
&& !incomingKeys.Contains(entityKeySelector(e)))
726+
.ToList();
727+
728+
foreach (var entity in toRemove)
729+
{
730+
existingCollection.Remove(entity);
731+
}
732+
733+
// Add or update incoming items
734+
foreach (var dto in incoming)
735+
{
736+
var key = keySelector(dto);
737+
var isNew = EqualityComparer<TKey>.Default.Equals(key, default(TKey));
738+
739+
if (isNew)
740+
{
741+
// Create and add new entity
742+
var newEntity = createEntity(dto);
743+
existingCollection.Add(newEntity);
744+
}
745+
else
746+
{
747+
// Update existing entity
748+
var existing = existingCollection.FirstOrDefault(e => EqualityComparer<TKey>.Default.Equals(entityKeySelector(e), key));
749+
if (existing != null)
750+
{
751+
updateEntity(existing, dto);
752+
}
753+
}
754+
}
755+
}
739756
}
740757
}

0 commit comments

Comments
 (0)