Add missing uniqueness constraints for Recipe, Ingredient, Unit, and UnitType#1059
Merged
dgee2 merged 3 commits intoMay 23, 2026
Merged
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
This PR hardens the backend data model by enforcing business-unique names at the database layer (via unique indexes) and translating unique-constraint insert failures into 422 validation responses for key write paths.
Changes:
- Added unique indexes for
Recipe.Name,Ingredient.Name,UnitType.Name,Unit.Name, and filtered uniqueUnit.Abbreviation. - Added SQL Server unique-violation detection (
DbUpdateExceptionExtensions.IsUniqueConstraintViolation) and mapped duplicate create attempts toBusinessValidationException(422) for recipes/ingredients. - Added integration tests asserting 422 on duplicate
Recipe.NameandIngredient.Namecreation.
Reviewed changes
Copilot reviewed 8 out of 9 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| backend/MenuDB/Migrations/MenuDbContextModelSnapshot.cs | Updates EF snapshot to reflect new unique indexes. |
| backend/MenuDB/Migrations/20260522201428_AddUniqueNameConstraints.Designer.cs | Adds migration designer model including new unique indexes. |
| backend/MenuDB/Migrations/20260522201428_AddUniqueNameConstraints.cs | Creates/drops the new unique indexes in the database. |
| backend/MenuDB/MenuDbContext.cs | Defines unique indexes in the EF model configuration. |
| backend/MenuApi/Repositories/RecipeRepository.cs | Catches unique-constraint insert failures and returns 422 via BusinessValidationException. |
| backend/MenuApi/Repositories/IngredientRepository.cs | Same as above for ingredient creation. |
| backend/MenuApi/Exceptions/DbUpdateExceptionExtensions.cs | Adds helper to detect SQL Server unique constraint/index violations. |
| backend/MenuApi.Integration.Tests/RecipeIntegrationTests.cs | Adds integration test for duplicate recipe-name creation → 422. |
| backend/MenuApi.Integration.Tests/IngredientIntegrationTests.cs | Adds integration test for duplicate ingredient-name creation → 422. |
Files not reviewed (1)
- backend/MenuDB/Migrations/20260522201428_AddUniqueNameConstraints.Designer.cs: Language not supported
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
9ae5312 to
143aedc
Compare
…e, Unit.Name, Unit.Abbreviation - Add HasIndex(...).IsUnique() in MenuDbContext for all five business-unique columns - Unit.Abbreviation uses a filtered index (WHERE NOT NULL) to allow multiple nulls - Generate migration AddUniqueNameConstraints - Handle DbUpdateException unique constraint violations in IngredientRepository and RecipeRepository, surfacing them as BusinessValidationException (422) - Add DbUpdateExceptionExtensions.IsUniqueConstraintViolation() helper - Add integration tests for duplicate Recipe.Name and Ingredient.Name creation Closes #979 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
143aedc to
f9e057d
Compare
- Add pre-flight THROW guards in AddUniqueNameConstraints migration before creating UX_Recipe_Name and UX_Ingredient_Name indexes, so the migration fails with a clear actionable message if duplicate rows exist - Handle unique constraint violations in UpdateRecipeAsync by catching SqlException (2627/2601) directly (ExecuteUpdateAsync bypasses DbUpdateException) and surfacing as BusinessValidationException -> 422 - Add Update_Recipe_To_Duplicate_Name_Returns_UnprocessableEntity integration test Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- IngredientRepository: detach entity and IngredientUnits before re-querying in race-condition catch block so the DbContext is left in a clean state - DbUpdateExceptionExtensions: add IsUniqueConstraintViolation overload for SqlException so error codes (2627/2601) are defined in one place only - RecipeRepository.UpdateRecipeAsync: use the new SqlException extension method instead of hardcoded error numbers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Summary
Hardens the database schema by adding unique constraints where business uniqueness is confirmed but was previously unenforced.
Changes
New unique indexes (migration:
AddUniqueNameConstraints)RecipeNameUX_Recipe_NameIngredientNameUX_Ingredient_NameUnitTypeNameUX_UnitType_NameUnitNameUX_Unit_NameUnitAbbreviationUX_Unit_AbbreviationWHERE NOT NULL); seeded data - zero migration riskApplication-layer violation handling
DbUpdateExceptionExtensions.IsUniqueConstraintViolation()helpers (SQL Server error codes 2627/2601) for bothDbUpdateExceptionandSqlExceptionRecipeRepository.CreateRecipeAsynccatches unique constraint violations and surfaces them asBusinessValidationException-> 422 Unprocessable EntityRecipeRepository.UpdateRecipeAsynccatches unique constraint violations and surfaces them asBusinessValidationException-> 422 Unprocessable EntityIngredientRepository.CreateIngredientAsyncuses a lookup-before-insert (idempotent) pattern from PR Prevent duplicate inserts for canonical/reference rows #978: duplicate ingredient creation returns 200 OK with the existing ingredient. A race-condition catch block detaches the failed entity and re-queries to return the existing row cleanly.Integration tests
Recipe.Namecreation -> 422Recipe.Nameon update (PUT) -> 422Migration risk
Recipe.Name/Ingredient.Name: If any pre-existing rows share a name the migration will fail. A pre-flightTHROWguard is included in the migration script to fail early with a clear, actionable message rather than an opaque SQL error.UnitType,Unit.Name,Unit.Abbreviation: Seeded reference data with no duplicate risk.