From eabf60923d0ba3d0cea5a73e98273859db59e901 Mon Sep 17 00:00:00 2001 From: OlgunR Date: Mon, 12 Jan 2026 12:58:41 +0100 Subject: [PATCH] Add SP-based update/delete for Catalogs, new endpoints Added support for updating and deleting Catalog records via SQL Server stored procedures. Introduced PUT /catalogs/sp and DELETE /catalogs/sp/{id} endpoints in CatalogsController. Extended service and repository interfaces and implementations to handle stored procedure operations. Added Microsoft.Data.SqlClient package for direct SQL execution. Repository checks for record existence before SP calls to prevent unintended behavior. --- DbFirst.API/Controllers/CatalogsController.cs | 22 +++++++++ .../Catalogs/CatalogService.cs | 12 +++++ .../Catalogs/ICatalogService.cs | 2 + .../Repositories/ICatalogRepository.cs | 2 + .../DbFirst.Infrastructure.csproj | 1 + .../Repositories/CatalogRepository.cs | 48 +++++++++++++++++++ 6 files changed, 87 insertions(+) diff --git a/DbFirst.API/Controllers/CatalogsController.cs b/DbFirst.API/Controllers/CatalogsController.cs index 2d2bea9..d94cec4 100644 --- a/DbFirst.API/Controllers/CatalogsController.cs +++ b/DbFirst.API/Controllers/CatalogsController.cs @@ -50,6 +50,17 @@ public class CatalogsController : ControllerBase return NoContent(); } + [HttpPut("sp")] + public async Task> UpdateWithStoredProcedure(CatalogDto dto, CancellationToken cancellationToken) + { + var updated = await _service.UpdateWithStoredProcedureAsync(dto, cancellationToken); + if (updated == null) + { + return BadRequest(); + } + return Ok(updated); + } + [HttpDelete("{id:int}")] public async Task Delete(int id, CancellationToken cancellationToken) { @@ -60,4 +71,15 @@ public class CatalogsController : ControllerBase } return NoContent(); } + + [HttpDelete("sp/{id:int}")] + public async Task DeleteWithStoredProcedure(int id, CancellationToken cancellationToken) + { + var deleted = await _service.DeleteWithStoredProcedureAsync(id, cancellationToken); + if (!deleted) + { + return NotFound(); + } + return NoContent(); + } } diff --git a/DbFirst.Application/Catalogs/CatalogService.cs b/DbFirst.Application/Catalogs/CatalogService.cs index 4690387..52344f0 100644 --- a/DbFirst.Application/Catalogs/CatalogService.cs +++ b/DbFirst.Application/Catalogs/CatalogService.cs @@ -40,8 +40,20 @@ public class CatalogService : ICatalogService return await _repository.UpdateAsync(id, domainItem, cancellationToken); } + public async Task UpdateWithStoredProcedureAsync(CatalogDto dto, CancellationToken cancellationToken = default) + { + var domainItem = _mapper.Map(dto); + var updated = await _repository.UpdateWithStoredProcedureAsync(domainItem, cancellationToken); + return updated == null ? null : _mapper.Map(updated); + } + public async Task DeleteAsync(int id, CancellationToken cancellationToken = default) { return await _repository.DeleteAsync(id, cancellationToken); } + + public async Task DeleteWithStoredProcedureAsync(int id, CancellationToken cancellationToken = default) + { + return await _repository.DeleteWithStoredProcedureAsync(id, cancellationToken); + } } diff --git a/DbFirst.Application/Catalogs/ICatalogService.cs b/DbFirst.Application/Catalogs/ICatalogService.cs index 24101b0..a973fb5 100644 --- a/DbFirst.Application/Catalogs/ICatalogService.cs +++ b/DbFirst.Application/Catalogs/ICatalogService.cs @@ -6,5 +6,7 @@ public interface ICatalogService Task GetByIdAsync(int id, CancellationToken cancellationToken = default); Task CreateAsync(CatalogDto dto, CancellationToken cancellationToken = default); Task UpdateAsync(int id, CatalogDto dto, CancellationToken cancellationToken = default); + Task UpdateWithStoredProcedureAsync(CatalogDto dto, CancellationToken cancellationToken = default); Task DeleteAsync(int id, CancellationToken cancellationToken = default); + Task DeleteWithStoredProcedureAsync(int id, CancellationToken cancellationToken = default); } diff --git a/DbFirst.Domain/Repositories/ICatalogRepository.cs b/DbFirst.Domain/Repositories/ICatalogRepository.cs index 92e3dfd..a65b48d 100644 --- a/DbFirst.Domain/Repositories/ICatalogRepository.cs +++ b/DbFirst.Domain/Repositories/ICatalogRepository.cs @@ -8,5 +8,7 @@ public interface ICatalogRepository Task GetByIdAsync(int id, CancellationToken cancellationToken = default); Task AddAsync(Catalog catalog, CancellationToken cancellationToken = default); Task UpdateAsync(int id, Catalog catalog, CancellationToken cancellationToken = default); + Task UpdateWithStoredProcedureAsync(Catalog catalog, CancellationToken cancellationToken = default); Task DeleteAsync(int id, CancellationToken cancellationToken = default); + Task DeleteWithStoredProcedureAsync(int id, CancellationToken cancellationToken = default); } diff --git a/DbFirst.Infrastructure/DbFirst.Infrastructure.csproj b/DbFirst.Infrastructure/DbFirst.Infrastructure.csproj index e9c23bf..01e5904 100644 --- a/DbFirst.Infrastructure/DbFirst.Infrastructure.csproj +++ b/DbFirst.Infrastructure/DbFirst.Infrastructure.csproj @@ -14,6 +14,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/DbFirst.Infrastructure/Repositories/CatalogRepository.cs b/DbFirst.Infrastructure/Repositories/CatalogRepository.cs index f209804..7b66f0b 100644 --- a/DbFirst.Infrastructure/Repositories/CatalogRepository.cs +++ b/DbFirst.Infrastructure/Repositories/CatalogRepository.cs @@ -2,7 +2,9 @@ using AutoMapper; using DbFirst.Domain.DomainEntities; using DbFirst.Domain.Repositories; using DbFirst.Infrastructure.ScaffoldEntities; +using Microsoft.Data.SqlClient; using Microsoft.EntityFrameworkCore; +using System.Data; namespace DbFirst.Infrastructure.Repositories; @@ -51,6 +53,35 @@ public class CatalogRepository : ICatalogRepository return true; } + public async Task UpdateWithStoredProcedureAsync(Catalog catalog, CancellationToken cancellationToken = default) + { + // ensure the record exists by CAT_TITLE to avoid insert behavior of the SP + var exists = await _db.TbmyCatalogs.AsNoTracking().AnyAsync(x => x.CatTitle == catalog.CatTitle, cancellationToken); + if (!exists) + { + return null; + } + + var catTitleParam = new SqlParameter("@CAT_TITLE", catalog.CatTitle); + var catStringParam = new SqlParameter("@CAT_STRING", catalog.CatString); + var changedWhoParam = new SqlParameter("@CHANGED_WHO", (object?)catalog.ChangedWho ?? DBNull.Value); + var guidOutParam = new SqlParameter("@GUID", SqlDbType.Int) { Direction = ParameterDirection.Output }; + + await _db.Database.ExecuteSqlRawAsync( + "EXEC dbo.PRTBMY_CATALOG_UPDATE @CAT_TITLE, @CAT_STRING, @CHANGED_WHO, @GUID OUTPUT", + parameters: new[] { catTitleParam, catStringParam, changedWhoParam, guidOutParam }, + cancellationToken: cancellationToken); + + if (guidOutParam.Value == DBNull.Value) + { + return null; + } + + var guid = (int)guidOutParam.Value; + var entity = await _db.TbmyCatalogs.AsNoTracking().FirstOrDefaultAsync(x => x.Guid == guid, cancellationToken); + return entity == null ? new Catalog { Guid = guid, CatTitle = catalog.CatTitle, CatString = catalog.CatString, ChangedWho = catalog.ChangedWho } : _mapper.Map(entity); + } + public async Task DeleteAsync(int id, CancellationToken cancellationToken = default) { var entity = await _db.TbmyCatalogs.FirstOrDefaultAsync(x => x.Guid == id, cancellationToken); @@ -63,4 +94,21 @@ public class CatalogRepository : ICatalogRepository await _db.SaveChangesAsync(cancellationToken); return true; } + + public async Task DeleteWithStoredProcedureAsync(int id, CancellationToken cancellationToken = default) + { + var exists = await _db.TbmyCatalogs.AsNoTracking().AnyAsync(x => x.Guid == id, cancellationToken); + if (!exists) + { + return false; + } + + var guidParam = new SqlParameter("@GUID", id); + await _db.Database.ExecuteSqlRawAsync( + "EXEC dbo.PRTBMY_CATALOG_DELETE @GUID", + parameters: new[] { guidParam }, + cancellationToken: cancellationToken); + + return true; + } }