From ccd97fe9d6f58ef8750f3a7a73bd1f744811432f Mon Sep 17 00:00:00 2001 From: OlgunR Date: Wed, 14 Jan 2026 12:50:58 +0100 Subject: [PATCH] Refactor catalog CRUD to use stored procedures only Removed direct usage of TbmyCatalog entity and DbSet. All create, update, and delete operations now use stored procedures. Entity lookups and mappings are performed via the VwmyCatalog view. Updated AutoMapper profile and DbContext configuration accordingly. Catalog creation now sets ChangedWho/ChangedWhen to "system" and current UTC time. --- .../Catalogs/CatalogService.cs | 4 +- .../ApplicationDbContext.cs | 38 +------- .../Mappings/CatalogInfrastructureProfile.cs | 1 - .../Repositories/CatalogRepository.cs | 88 ++++++++++--------- .../ScaffoldEntities/TbmyCatalog.cs | 24 ----- 5 files changed, 49 insertions(+), 106 deletions(-) delete mode 100644 DbFirst.Infrastructure/ScaffoldEntities/TbmyCatalog.cs diff --git a/DbFirst.Application/Catalogs/CatalogService.cs b/DbFirst.Application/Catalogs/CatalogService.cs index 1e2f5ce..d4488c2 100644 --- a/DbFirst.Application/Catalogs/CatalogService.cs +++ b/DbFirst.Application/Catalogs/CatalogService.cs @@ -32,8 +32,8 @@ public class CatalogService : ICatalogService var domainItem = _mapper.Map(dto); domainItem.AddedWho = "system"; domainItem.AddedWhen = DateTime.UtcNow; - domainItem.ChangedWho = null; - domainItem.ChangedWhen = null; + domainItem.ChangedWho = "system"; + domainItem.ChangedWhen = DateTime.UtcNow; var created = await _repository.AddAsync(domainItem, cancellationToken); return _mapper.Map(created); diff --git a/DbFirst.Infrastructure/ApplicationDbContext.cs b/DbFirst.Infrastructure/ApplicationDbContext.cs index 3f7ecea..7065533 100644 --- a/DbFirst.Infrastructure/ApplicationDbContext.cs +++ b/DbFirst.Infrastructure/ApplicationDbContext.cs @@ -10,49 +10,13 @@ public partial class ApplicationDbContext : DbContext { } - public virtual DbSet TbmyCatalogs { get; set; } public virtual DbSet VwmyCatalogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { - modelBuilder.Entity(entity => - { - entity.HasKey(e => e.Guid); - - entity.ToTable("TBMY_CATALOG"); - - entity.HasIndex(e => e.CatTitle, "UQ_TBMY_CATALOG_TITLE").IsUnique(); - - entity.Property(e => e.Guid).HasColumnName("GUID"); - entity.Property(e => e.AddedWhen) - .HasDefaultValueSql("(getdate())") - .HasColumnType("datetime") - .HasColumnName("ADDED_WHEN"); - entity.Property(e => e.AddedWho) - .HasMaxLength(30) - .IsUnicode(false) - .HasDefaultValue("SYSTEM") - .HasColumnName("ADDED_WHO"); - entity.Property(e => e.CatString) - .HasMaxLength(900) - .IsUnicode(false) - .HasColumnName("CAT_STRING"); - entity.Property(e => e.CatTitle) - .HasMaxLength(100) - .IsUnicode(false) - .HasColumnName("CAT_TITLE"); - entity.Property(e => e.ChangedWhen) - .HasColumnType("datetime") - .HasColumnName("CHANGED_WHEN"); - entity.Property(e => e.ChangedWho) - .HasMaxLength(30) - .IsUnicode(false) - .HasColumnName("CHANGED_WHO"); - }); - modelBuilder.Entity(entity => { - entity.HasNoKey(); + entity.HasKey(e => e.Guid); entity.ToView("VWMY_CATALOG"); diff --git a/DbFirst.Infrastructure/Mappings/CatalogInfrastructureProfile.cs b/DbFirst.Infrastructure/Mappings/CatalogInfrastructureProfile.cs index e8e2c01..887e621 100644 --- a/DbFirst.Infrastructure/Mappings/CatalogInfrastructureProfile.cs +++ b/DbFirst.Infrastructure/Mappings/CatalogInfrastructureProfile.cs @@ -8,7 +8,6 @@ public class CatalogInfrastructureProfile : Profile { public CatalogInfrastructureProfile() { - CreateMap().ReverseMap(); CreateMap(); } } diff --git a/DbFirst.Infrastructure/Repositories/CatalogRepository.cs b/DbFirst.Infrastructure/Repositories/CatalogRepository.cs index db74c3e..293d2b2 100644 --- a/DbFirst.Infrastructure/Repositories/CatalogRepository.cs +++ b/DbFirst.Infrastructure/Repositories/CatalogRepository.cs @@ -33,24 +33,26 @@ public class CatalogRepository : ICatalogRepository public async Task AddAsync(Catalog catalog, CancellationToken cancellationToken = default) { - var entity = _mapper.Map(catalog); - _db.TbmyCatalogs.Add(entity); - await _db.SaveChangesAsync(cancellationToken); - return _mapper.Map(entity); + var created = await UpsertWithStoredProcedureAsync(catalog, cancellationToken); + if (created == null) + { + throw new InvalidOperationException("Failed to create catalog via stored procedure."); + } + + return created; } public async Task UpdateAsync(int id, Catalog catalog, CancellationToken cancellationToken = default) { - var entity = await _db.TbmyCatalogs.FirstOrDefaultAsync(x => x.Guid == id, cancellationToken); - if (entity == null) + var existing = await _db.VwmyCatalogs.AsNoTracking().AnyAsync(x => x.Guid == id, cancellationToken); + if (!existing) { return false; } - _mapper.Map(catalog, entity); - entity.Guid = id; - await _db.SaveChangesAsync(cancellationToken); - return true; + catalog.Guid = id; + var updated = await UpsertWithStoredProcedureAsync(catalog, cancellationToken); + return updated != null; } public async Task UpdateWithStoredProcedureAsync(Catalog catalog, CancellationToken cancellationToken = default) @@ -60,59 +62,61 @@ public class CatalogRepository : ICatalogRepository return null; } - var existing = await _db.TbmyCatalogs.AsNoTracking().FirstOrDefaultAsync(x => x.Guid == catalog.Guid, cancellationToken); - if (existing == null) + var existing = await _db.VwmyCatalogs.AsNoTracking().AnyAsync(x => x.Guid == catalog.Guid, cancellationToken); + if (!existing) { return null; } - var catTitleParam = new SqlParameter("@CAT_TITLE", existing.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 = existing.CatTitle, CatString = catalog.CatString, ChangedWho = catalog.ChangedWho } : _mapper.Map(entity); + return await UpsertWithStoredProcedureAsync(catalog, cancellationToken); } public async Task DeleteAsync(int id, CancellationToken cancellationToken = default) { - var entity = await _db.TbmyCatalogs.FirstOrDefaultAsync(x => x.Guid == id, cancellationToken); - if (entity == null) + var exists = await _db.VwmyCatalogs.AsNoTracking().AnyAsync(x => x.Guid == id, cancellationToken); + if (!exists) { return false; } - _db.TbmyCatalogs.Remove(entity); - await _db.SaveChangesAsync(cancellationToken); + var guidParam = new SqlParameter("@GUID", id); + await _db.Database.ExecuteSqlRawAsync( + "EXEC dbo.PRTBMY_CATALOG_DELETE @GUID", + parameters: new[] { guidParam }, + cancellationToken: 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 await DeleteAsync(id, cancellationToken); + } + + private async Task UpsertWithStoredProcedureAsync(Catalog catalog, CancellationToken cancellationToken) + { + var guidParam = new SqlParameter("@GUID", SqlDbType.Int) { - return false; - } + Direction = ParameterDirection.InputOutput, + Value = catalog.Guid == 0 ? DBNull.Value : catalog.Guid + }; + + 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 guidParam = new SqlParameter("@GUID", id); await _db.Database.ExecuteSqlRawAsync( - "EXEC dbo.PRTBMY_CATALOG_DELETE @GUID", - parameters: new[] { guidParam }, + "EXEC dbo.PRTBMY_CATALOG_UPDATE @CAT_TITLE, @CAT_STRING, @CHANGED_WHO, @GUID OUTPUT", + parameters: new[] { catTitleParam, catStringParam, changedWhoParam, guidParam }, cancellationToken: cancellationToken); - return true; + if (guidParam.Value == DBNull.Value) + { + return null; + } + + var guid = (int)guidParam.Value; + var viewRow = await _db.VwmyCatalogs.AsNoTracking().FirstOrDefaultAsync(x => x.Guid == guid, cancellationToken); + return viewRow == null ? null : _mapper.Map(viewRow); } } diff --git a/DbFirst.Infrastructure/ScaffoldEntities/TbmyCatalog.cs b/DbFirst.Infrastructure/ScaffoldEntities/TbmyCatalog.cs deleted file mode 100644 index 625f208..0000000 --- a/DbFirst.Infrastructure/ScaffoldEntities/TbmyCatalog.cs +++ /dev/null @@ -1,24 +0,0 @@ -namespace DbFirst.Infrastructure.ScaffoldEntities; - -public partial class TbmyCatalog -{ - public int Guid { get; set; } - - public string CatTitle { get; set; } = null!; - - public string CatString { get; set; } = null!; - - public string AddedWho { get; set; } = null!; - - public DateTime AddedWhen { get; set; } - - public string? ChangedWho { get; set; } - - public DateTime? ChangedWhen { get; set; } - - // = null!; ist nur eine Null-Unterdrückung für Non‑Nullable-Referenztypen. Hintergrund: Die Spalten CatTitle, CatString, AddedWho - // sind in der DB als NOT NULL definiert, also generiert der Scaffold string (ohne ?). Damit der Compiler bei aktivierter Nullable-Analyse - // nicht warnt („non-nullable property is uninitialized“), wird ein Dummy-Init auf null! gesetzt. Das ! sagt dem Compiler „ich garantiere, - // dass zur Laufzeit ein Wert gesetzt wird“. Bei string? ChangedWho/DateTime? ChangedWhen sind die Spalten nullable, daher kein null! nötig. - // Bei Value Types wie int/DateTime braucht es ebenfalls kein Init, da sie Standardwerte haben. -}