/// <summary> /// Updates a location using a <see cref="Delta"/> object. /// </summary> /// <param name="id">ID of the location to be updated.</param> /// <param name="delta"> /// Delta containing a list of location properties. Web Api does the magic of converting the JSON to /// a delta. /// </param> /// <returns> /// An asynchronous task result containing information needed to create an API response message. /// </returns> public override async Task <CommandResult <LocationBaseDto, Guid> > Update(Guid id, Delta <LocationBaseDto> delta) { // Thread.CurrentPrincipal is not available in the constrtor. Do not try and move this var uid = GetCurrentUser(); // User ID should always be available, but if not ... if (!uid.HasValue) { return(Command.Error <LocationBaseDto>(GeneralErrorCodes.TokenInvalid("UserId"))); } if (delta == null) { return(Command.Error <LocationBaseDto>(EntityErrorCode.EntityFormatIsInvalid)); } var location = await _context.GetLocationsForUser(uid.Value) .SingleOrDefaultAsync(l => l.Id == id) .ConfigureAwait(false); if (location == null) { return(Command.Error <LocationBaseDto>(EntityErrorCode.EntityNotFound)); } var locationDto = _mapper.Map(location, new LocationBaseDto()); delta.Patch(locationDto); var validationResponse = ValidatorUpdate.Validate(locationDto); if (locationDto.ParentId.HasValue) { var existingTask = _context.Locations.AnyAsync(l => l.Id == locationDto.ParentId.Value); if (!existingTask.Result) { validationResponse.FFErrors.Add(ValidationErrorCode.ForeignKeyValueDoesNotExist(nameof(Location.ParentId))); } else { // Check for circular references if (await LocationValidator.IsCircularReference(_context.Locations, locationDto, id)) { validationResponse.FFErrors.Add(ValidationErrorCode.CircularReferenceNotAllowed(nameof(Location.ParentId))); } } } // Check that Location Type exists if (locationDto.LocationTypeId != Guid.Empty && !_context.LocationTypes.Any(lt => lt.Id == locationDto.LocationTypeId)) { validationResponse.FFErrors.Add(ValidationErrorCode.ForeignKeyValueDoesNotExist(nameof(Location.LocationTypeId))); } // Including the original Id in the Patch request will not return an error but attempting to change the Id is not allowed. if (locationDto.Id != id) { validationResponse.FFErrors.Add(ValidationErrorCode.EntityIDUpdateNotAllowed("Id")); } // Check that unique fields are still unique if (_context.Locations.Any(l => l.Id != id && l.Name == locationDto.Name)) { validationResponse.FFErrors.Add(ValidationErrorCode.EntityPropertyDuplicateNotAllowed(nameof(Location.Name))); } if (validationResponse.IsInvalid) { return(Command.Error <LocationBaseDto>(validationResponse)); } _context.Locations.Attach(location); _mapper.Map(locationDto, location); location.SetAuditFieldsOnUpdate(uid.Value); await _context.SaveChangesAsync().ConfigureAwait(false); return(Command.NoContent <LocationBaseDto>()); }