@@ -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