Support selecting update procedure for catalog updates
Added CatalogUpdateProcedure enum to domain. CatalogWriteDto now includes UpdateProcedure property in both application and BlazorWasm layers. Catalogs.razor form allows users to choose between PRTBMY_CATALOG_UPDATE and PRTBMY_CATALOG_SAVE when editing. Repository, service, and handler layers updated to pass and use the selected procedure. Default remains Update. Updated comments and TODOs for clarity and future refactoring.
This commit is contained in:
@@ -54,10 +54,6 @@ public class CatalogsController : ControllerBase
|
|||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
if (!string.Equals(current.CatTitle, dto.CatTitle, StringComparison.Ordinal))
|
|
||||||
{
|
|
||||||
return BadRequest("CatTitle cannot be changed.");
|
|
||||||
}
|
|
||||||
|
|
||||||
var updated = await _mediator.Send(new UpdateCatalogCommand(id, dto), cancellationToken);
|
var updated = await _mediator.Send(new UpdateCatalogCommand(id, dto), cancellationToken);
|
||||||
if (updated == null)
|
if (updated == null)
|
||||||
|
|||||||
@@ -1,27 +1,14 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using DbFirst.Domain.Repositories;
|
using DbFirst.Domain.Repositories;
|
||||||
using DbFirst.Domain.Entities;
|
using DbFirst.Domain.Entities;
|
||||||
|
using DbFirst.Domain;
|
||||||
|
|
||||||
namespace DbFirst.Application.Catalogs;
|
namespace DbFirst.Application.Catalogs;
|
||||||
|
|
||||||
//TODO: create generic service to reduce code duplication
|
//TODO: create generic service to reduce code duplication
|
||||||
|
//TODO (TR): kod tekrar?n? azaltmak için generic bir servis/basit CRUD altyap?s? ekleyin
|
||||||
/* Copilot's Response:
|
|
||||||
A generic CRUD base service adds little value in your case:
|
|
||||||
|
|
||||||
Pros:
|
|
||||||
• Less boilerplate for simple entities without special logic.
|
|
||||||
• Uniform CRUD signatures.
|
|
||||||
|
|
||||||
Cons/Practical here:
|
|
||||||
• Domain logic differs per entity(unique title check, setting audit fields, forbidding title changes, stored procs with output GUID).
|
|
||||||
• Generic services tend to be diluted by virtual methods/hooks for special cases—ending up with per-entity overrides and little real gain.
|
|
||||||
• With stored procedures and output parameters, the pattern doesn’t fit cleanly because operations aren’t symmetric (separate procs for insert/update/delete).
|
|
||||||
|
|
||||||
Conclusion: For this solution a generic service would be more overhead than benefit. If you later have multiple very similar entities without special logic,
|
|
||||||
you could consider a lightweight generic interface/base; for now, the specialized service implementation is cleaner. */
|
|
||||||
|
|
||||||
//TODO: implement CQRS pattern with MediatR
|
//TODO: implement CQRS pattern with MediatR
|
||||||
|
//TODO (TR): CQRS desenini MediatR ile uygulay?n
|
||||||
public class CatalogService : ICatalogService
|
public class CatalogService : ICatalogService
|
||||||
{
|
{
|
||||||
private readonly ICatalogRepository _repository;
|
private readonly ICatalogRepository _repository;
|
||||||
@@ -79,7 +66,8 @@ public class CatalogService : ICatalogService
|
|||||||
entity.ChangedWho = "system";
|
entity.ChangedWho = "system";
|
||||||
entity.ChangedWhen = DateTime.UtcNow;
|
entity.ChangedWhen = DateTime.UtcNow;
|
||||||
|
|
||||||
var updated = await _repository.UpdateAsync(id, entity, cancellationToken);
|
var procedure = dto.UpdateProcedure;
|
||||||
|
var updated = await _repository.UpdateAsync(id, entity, procedure, cancellationToken);
|
||||||
return updated == null ? null : _mapper.Map<CatalogReadDto>(updated);
|
return updated == null ? null : _mapper.Map<CatalogReadDto>(updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
using DbFirst.Domain;
|
||||||
|
|
||||||
namespace DbFirst.Application.Catalogs;
|
namespace DbFirst.Application.Catalogs;
|
||||||
|
|
||||||
public class CatalogWriteDto
|
public class CatalogWriteDto
|
||||||
{
|
{
|
||||||
public string CatTitle { get; set; } = null!;
|
public string CatTitle { get; set; } = null!;
|
||||||
public string CatString { get; set; } = null!;
|
public string CatString { get; set; } = null!;
|
||||||
|
public CatalogUpdateProcedure UpdateProcedure { get; set; } = CatalogUpdateProcedure.Update;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
using DbFirst.Domain.Entities;
|
using DbFirst.Domain.Entities;
|
||||||
using DbFirst.Domain.Repositories;
|
using DbFirst.Domain.Repositories;
|
||||||
|
using DbFirst.Domain;
|
||||||
using MediatR;
|
using MediatR;
|
||||||
|
|
||||||
namespace DbFirst.Application.Catalogs.Commands;
|
namespace DbFirst.Application.Catalogs.Commands;
|
||||||
@@ -32,7 +33,8 @@ public class UpdateCatalogHandler : IRequestHandler<UpdateCatalogCommand, Catalo
|
|||||||
entity.ChangedWho = "system";
|
entity.ChangedWho = "system";
|
||||||
entity.ChangedWhen = DateTime.UtcNow;
|
entity.ChangedWhen = DateTime.UtcNow;
|
||||||
|
|
||||||
var updated = await _repository.UpdateAsync(request.Id, entity, cancellationToken);
|
var procedure = request.Dto.UpdateProcedure;
|
||||||
|
var updated = await _repository.UpdateAsync(request.Id, entity, procedure, cancellationToken);
|
||||||
return updated == null ? null : _mapper.Map<CatalogReadDto>(updated);
|
return updated == null ? null : _mapper.Map<CatalogReadDto>(updated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,4 +4,5 @@ public class CatalogWriteDto
|
|||||||
{
|
{
|
||||||
public string CatTitle { get; set; } = string.Empty;
|
public string CatTitle { get; set; } = string.Empty;
|
||||||
public string CatString { get; set; } = string.Empty;
|
public string CatString { get; set; } = string.Empty;
|
||||||
|
public int UpdateProcedure { get; set; } = 0; // 0 = Update, 1 = Save
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,19 @@ else if (!string.IsNullOrWhiteSpace(infoMessage))
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@if (isEditing)
|
||||||
|
{
|
||||||
|
<div class="row g-3 mt-2">
|
||||||
|
<div class="col-md-6">
|
||||||
|
<label class="form-label">Update-Prozedur</label>
|
||||||
|
<InputSelect class="form-control" @bind-Value="formModel.UpdateProcedure">
|
||||||
|
<option value="0">PRTBMY_CATALOG_UPDATE</option>
|
||||||
|
<option value="1">PRTBMY_CATALOG_SAVE</option>
|
||||||
|
</InputSelect>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
<div class="mt-3 d-flex gap-2">
|
<div class="mt-3 d-flex gap-2">
|
||||||
<button type="submit" class="btn btn-success">@((isEditing ? "Speichern" : "Anlegen"))</button>
|
<button type="submit" class="btn btn-success">@((isEditing ? "Speichern" : "Anlegen"))</button>
|
||||||
<button type="button" class="btn btn-secondary" @onclick="CancelEdit">Abbrechen</button>
|
<button type="button" class="btn btn-secondary" @onclick="CancelEdit">Abbrechen</button>
|
||||||
@@ -132,7 +145,8 @@ else
|
|||||||
formModel = new CatalogWriteDto
|
formModel = new CatalogWriteDto
|
||||||
{
|
{
|
||||||
CatTitle = item.CatTitle,
|
CatTitle = item.CatTitle,
|
||||||
CatString = item.CatString
|
CatString = item.CatString,
|
||||||
|
UpdateProcedure = 0
|
||||||
};
|
};
|
||||||
editingId = item.Guid;
|
editingId = item.Guid;
|
||||||
isEditing = true;
|
isEditing = true;
|
||||||
|
|||||||
7
DbFirst.Domain/CatalogUpdateProcedure.cs
Normal file
7
DbFirst.Domain/CatalogUpdateProcedure.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace DbFirst.Domain;
|
||||||
|
|
||||||
|
public enum CatalogUpdateProcedure
|
||||||
|
{
|
||||||
|
Update = 0,
|
||||||
|
Save = 1
|
||||||
|
}
|
||||||
@@ -1,33 +1,14 @@
|
|||||||
using DbFirst.Domain.Entities;
|
using DbFirst.Domain.Entities;
|
||||||
|
using DbFirst.Domain;
|
||||||
|
|
||||||
namespace DbFirst.Domain.Repositories;
|
namespace DbFirst.Domain.Repositories;
|
||||||
|
|
||||||
// TODO: instead of creating interface per entity, consider using generic repository pattern (eg. IRepository<T>) to reduce code duplication.
|
|
||||||
|
|
||||||
/* Copilot's Response:
|
|
||||||
|
|
||||||
A generic Repository<T> isn’t really worthwhile here:
|
|
||||||
• Reads from the view are generic, but inserts/updates/deletes go through stored procedures with special parameters/output GUIDs.You’d need lots of exceptions/overrides—little gain.
|
|
||||||
• Operations aren’t symmetric (separate procs for insert/update/delete with output handling and reload), so a one-size-fits-all CRUD pattern doesn’t fit well.
|
|
||||||
• Better to keep the specialized repo.If you want reuse, extract small helpers (e.g., for proc calls/output parameters/reload) instead of forcing a generic repository. */
|
|
||||||
|
|
||||||
// TODO: move to application layer as a part of clean architecture
|
|
||||||
|
|
||||||
/* Copilot's Response:
|
|
||||||
|
|
||||||
• Common: Keep the interface in the Domain layer, implementation in Infrastructure. Domain defines the data access contract, Infrastructure provides the implementation.
|
|
||||||
That matches typical DDD/Clean Architecture.
|
|
||||||
• Alternative: Move the interface to the Application layer. Then only Application depends on Infrastructure, Domain stays isolated from infra contracts.
|
|
||||||
Pro: Domain contains only models/rules.
|
|
||||||
Con: If Domain logic needs repos, it would depend on Application, weakening the layer separation.
|
|
||||||
Recommendation: Keep the interface in Domain. It’s conventional, clean, and avoids a Domain -> Application dependency. Implementation stays in Infrastructure. */
|
|
||||||
|
|
||||||
public interface ICatalogRepository
|
public interface ICatalogRepository
|
||||||
{
|
{
|
||||||
Task<List<VwmyCatalog>> GetAllAsync(CancellationToken cancellationToken = default);
|
Task<List<VwmyCatalog>> GetAllAsync(CancellationToken cancellationToken = default);
|
||||||
Task<VwmyCatalog?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
|
Task<VwmyCatalog?> GetByIdAsync(int id, CancellationToken cancellationToken = default);
|
||||||
Task<VwmyCatalog?> GetByTitleAsync(string title, CancellationToken cancellationToken = default);
|
Task<VwmyCatalog?> GetByTitleAsync(string title, CancellationToken cancellationToken = default);
|
||||||
Task<VwmyCatalog> InsertAsync(VwmyCatalog catalog, CancellationToken cancellationToken = default);
|
Task<VwmyCatalog> InsertAsync(VwmyCatalog catalog, CancellationToken cancellationToken = default);
|
||||||
Task<VwmyCatalog?> UpdateAsync(int id, VwmyCatalog catalog, CancellationToken cancellationToken = default);
|
Task<VwmyCatalog?> UpdateAsync(int id, VwmyCatalog catalog, CatalogUpdateProcedure procedure, CancellationToken cancellationToken = default);
|
||||||
Task<bool> DeleteAsync(int id, CancellationToken cancellationToken = default);
|
Task<bool> DeleteAsync(int id, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ using System.Text;
|
|||||||
using System.Threading.Channels;
|
using System.Threading.Channels;
|
||||||
using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
|
using static Microsoft.EntityFrameworkCore.DbLoggerCategory;
|
||||||
using static System.Runtime.InteropServices.JavaScript.JSType;
|
using static System.Runtime.InteropServices.JavaScript.JSType;
|
||||||
|
using DbFirst.Domain;
|
||||||
|
|
||||||
namespace DbFirst.Infrastructure.Repositories;
|
namespace DbFirst.Infrastructure.Repositories;
|
||||||
|
|
||||||
@@ -78,7 +79,7 @@ public class CatalogRepository : ICatalogRepository
|
|||||||
return created;
|
return created;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<VwmyCatalog?> UpdateAsync(int id, VwmyCatalog catalog, CancellationToken cancellationToken = default)
|
public async Task<VwmyCatalog?> UpdateAsync(int id, VwmyCatalog catalog, CatalogUpdateProcedure procedure, CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
catalog.Guid = id;
|
catalog.Guid = id;
|
||||||
|
|
||||||
@@ -91,8 +92,12 @@ public class CatalogRepository : ICatalogRepository
|
|||||||
var catStringParam = new SqlParameter("@CAT_STRING", catalog.CatString);
|
var catStringParam = new SqlParameter("@CAT_STRING", catalog.CatString);
|
||||||
var changedWhoParam = new SqlParameter("@CHANGED_WHO", (object?)catalog.ChangedWho ?? DBNull.Value);
|
var changedWhoParam = new SqlParameter("@CHANGED_WHO", (object?)catalog.ChangedWho ?? DBNull.Value);
|
||||||
|
|
||||||
|
var procName = procedure == CatalogUpdateProcedure.Save
|
||||||
|
? "PRTBMY_CATALOG_SAVE"
|
||||||
|
: "PRTBMY_CATALOG_UPDATE";
|
||||||
|
|
||||||
await _db.Database.ExecuteSqlRawAsync(
|
await _db.Database.ExecuteSqlRawAsync(
|
||||||
"EXEC dbo.PRTBMY_CATALOG_UPDATE @CAT_TITLE, @CAT_STRING, @CHANGED_WHO, @GUID OUTPUT",
|
$"EXEC dbo.{procName} @CAT_TITLE, @CAT_STRING, @CHANGED_WHO, @GUID OUTPUT",
|
||||||
parameters: new[] { catTitleParam, catStringParam, changedWhoParam, guidParam },
|
parameters: new[] { catTitleParam, catStringParam, changedWhoParam, guidParam },
|
||||||
cancellationToken: cancellationToken);
|
cancellationToken: cancellationToken);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user