public async Task <IActionResult> PartiuallyUpdateMovieAsync( [FromRoute, SwaggerParameter(Description = "Id of movie to update", Required = true)] Guid movieId, [FromBody, SwaggerParameter(Description = "Jsonpatch operation document to update", Required = true)] JsonPatchDocument <MovieToUpdateDto> patchDoc, [FromHeader(Name = "Accept"), SwaggerParameter(Description = "media type to request betwen json or json+hateoas")] string mediaType) { if (patchDoc == null) { return(BadRequest()); } var movieFromDb = await _movieRepository.GetMovieAsync(movieId); // upserting if movie does not already exist // TODO: research if upserting is neccesary in patching if (movieFromDb == null) { var movieToCreate = new MovieToUpdateDto(); patchDoc.ApplyTo(movieToCreate, ModelState); if (!ModelState.IsValid) { new ProccessingEntityObjectResultErrors(ModelState); } var movieToAddToDb = Mapper.Map <Movie>(movieToCreate); movieToAddToDb.Id = movieId; _movieRepository.AddMovie(movieToAddToDb); if (!await _movieRepository.SaveChangesAsync()) { _logger.LogError($"Upserting movie: {movieId} failed on save"); } // loop on foreach Operations => if op.path == "GenreIds" => loop on value which contains list/array? of genreIds foreach (var op in patchDoc.Operations) { if (op.path.ToLower() == "genreids") { //movieToPatch.GenreIds = (List<Guid>)op.value; var genreIds = op.value.ToString(); // TODO make list guid instead of string so you dont have to parse on each use. What if only one, still split? // TODO EXTENDED check if lenght is > 0 before all this, also check if valid Guids var splitGenreIds = genreIds.Split(","); switch (op.op) { // check if genre already exists on movie before creating case "add": { for (int i = 0; i < splitGenreIds.Length; i++) { //create moviegenre object with genre id and movie id from movieToCreate object MovieGenre movieGenreToAdd = new MovieGenre { MovieId = movieId, GenreId = Guid.Parse(splitGenreIds[i]) }; movieGenreToAdd.Id = Guid.NewGuid(); _movieGenreRepository.AddMovieGenre(movieGenreToAdd); } break; } case "replace": { // easy solution delete all moviegenres with id, add new ids var moviegenresExist = await _movieGenreRepository.GetAllMovieGenresByMovieIdAsync(movieId); // delete all existing many-to-many genre relationships for movie foreach (var moviegenre in moviegenresExist) { _movieGenreRepository.DeleteMovieGenre(moviegenre); } // if any genre is added to movie, create many-to-many relationship for each genre for (int i = 0; i < splitGenreIds.Length; i++) { // create moviegenre object with genre id and movie id from movieToCreate object MovieGenre movieGenreToAdd = new MovieGenre { MovieId = movieId, GenreId = Guid.Parse(splitGenreIds[i]) }; movieGenreToAdd.Id = Guid.NewGuid(); _movieGenreRepository.AddMovieGenre(movieGenreToAdd); } break; } default: // tell user operation not allowed? break; } } } // save changes to database after all operations // check if state is modified? this will always get run if (!await _movieGenreRepository.SaveChangesAsync()) { _logger.LogError("Saving changes to database while dealing with a moviegenre failed"); } var movieToReturn = Mapper.Map <MovieDto>(movieToAddToDb); if (mediaType == "application/vnd.biob.json+hateoas") { var links = CreateLinksForMovies(movieToReturn.Id, null); var linkedMovie = movieToReturn.ShapeData(null) as IDictionary <string, object>; linkedMovie.Add("links", links); return(CreatedAtRoute("GetMovie", new { movieId = movieToReturn.Id }, linkedMovie)); } else { return(CreatedAtRoute("GetMovie", new { movieId = movieToReturn.Id }, movieToReturn)); } } var movieToPatch = Mapper.Map <MovieToUpdateDto>(movieFromDb); patchDoc.ApplyTo(movieToPatch, ModelState); if (!ModelState.IsValid) { new ProccessingEntityObjectResultErrors(ModelState); } Mapper.Map(movieToPatch, movieFromDb); _movieRepository.UpdateMovie(movieFromDb); if (!await _movieRepository.SaveChangesAsync()) { _logger.LogError($"Partially updating movie: {movieId} failed on save"); } // loop on foreach Operations => if op.path == "GenreIds" => loop on value which contains list/array? of genreIds foreach (var op in patchDoc.Operations) { if (op.path.ToLower() == "genreids") { //movieToPatch.GenreIds = (List<Guid>)op.value; var genreIds = op.value.ToString(); // TODO make list guid instead of string so you dont have to parse on each use. What if only one, still split? // TODO EXTENDED check if lenght is > 0 before all this, also check if valid Guids var splitGenreIds = genreIds.Split(","); switch (op.op) { case "add": { for (int i = 0; i < splitGenreIds.Length; i++) { // checking if genre already exists on movie // will fail if we use wrapper class IsDeleted checker, NOT USING ATM var moviegenreToAdd = await _movieGenreRepository.GetMovieGenreByMovieIdGenreIdAsync(movieId, Guid.Parse(splitGenreIds[i])); if (moviegenreToAdd == null) { //create moviegenre object with genre id and movie id from movieToCreate object MovieGenre movieGenreToAdd = new MovieGenre { MovieId = movieId, GenreId = Guid.Parse(splitGenreIds[i]) }; movieGenreToAdd.Id = Guid.NewGuid(); _movieGenreRepository.AddMovieGenre(movieGenreToAdd); } } break; } case "replace": { // easy solution delete all moviegenres with id, add new ids var moviegenresExist = await _movieGenreRepository.GetAllMovieGenresByMovieIdAsync(movieId); // delete all existing many-to-many genre relationships for movie foreach (var moviegenre in moviegenresExist) { _movieGenreRepository.DeleteMovieGenre(moviegenre); } // if any genre is added to movie, create many-to-many relationship for each genre for (int i = 0; i < splitGenreIds.Length; i++) { // create moviegenre object with genre id and movie id from movieToCreate object MovieGenre movieGenreToAdd = new MovieGenre { MovieId = movieId, GenreId = Guid.Parse(splitGenreIds[i]) }; movieGenreToAdd.Id = Guid.NewGuid(); _movieGenreRepository.AddMovieGenre(movieGenreToAdd); } break; } case "remove": { for (int i = 0; i < splitGenreIds.Length; i++) { var moviegenreToDelete = await _movieGenreRepository.GetMovieGenreByMovieIdGenreIdAsync(movieId, Guid.Parse(splitGenreIds[i])); _movieGenreRepository.DeleteMovieGenre(moviegenreToDelete); } break; } default: // tell user operation not allowed? break; } } } // save changes to database after all operations // check if state is modified? this will always get run if (!await _movieGenreRepository.SaveChangesAsync()) { _logger.LogError("Saving changes to database while dealing with a moviegenre failed"); } return(NoContent()); }