Add MassData API with CQRS, repository, and DbContext
Introduce MassData feature with new API endpoints for querying and upserting records by customer name. Add DTOs, AutoMapper profile, MediatR CQRS handlers, repository pattern, and MassDataDbContext. Register new services in DI and add MassDataConnection to configuration. Upsert uses stored procedure. Enables full CRUD for Massdata via dedicated API.
This commit is contained in:
46
DbFirst.API/Controllers/MassDataController.cs
Normal file
46
DbFirst.API/Controllers/MassDataController.cs
Normal file
@@ -0,0 +1,46 @@
|
||||
using DbFirst.Application.MassData;
|
||||
using DbFirst.Application.MassData.Commands;
|
||||
using DbFirst.Application.MassData.Queries;
|
||||
using MediatR;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace DbFirst.API.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class MassDataController : ControllerBase
|
||||
{
|
||||
private readonly IMediator _mediator;
|
||||
|
||||
public MassDataController(IMediator mediator)
|
||||
{
|
||||
_mediator = mediator;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
public async Task<ActionResult<IEnumerable<MassDataReadDto>>> GetAll([FromQuery] int? skip, [FromQuery] int? take, CancellationToken cancellationToken)
|
||||
{
|
||||
var resolvedTake = take is null or <= 0 ? 200 : take;
|
||||
var result = await _mediator.Send(new GetAllMassDataQuery(skip, resolvedTake), cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpGet("{customerName}")]
|
||||
public async Task<ActionResult<MassDataReadDto>> GetByCustomerName(string customerName, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _mediator.Send(new GetMassDataByCustomerNameQuery(customerName), cancellationToken);
|
||||
if (result == null)
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
return Ok(result);
|
||||
}
|
||||
|
||||
[HttpPost("upsert")]
|
||||
public async Task<ActionResult<MassDataReadDto>> Upsert(MassDataWriteDto dto, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await _mediator.Send(new UpsertMassDataByCustomerNameCommand(dto), cancellationToken);
|
||||
return Ok(result);
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,7 @@ builder.Services.AddInfrastructure(builder.Configuration);
|
||||
builder.Services.AddApplication();
|
||||
|
||||
builder.Services.AddScoped<ICatalogRepository, CatalogRepository>();
|
||||
builder.Services.AddScoped<IMassDataRepository, MassDataRepository>();
|
||||
|
||||
builder.Services.AddDevExpressControls();
|
||||
builder.Services.AddSignalR();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;TrustServerCertificate=True;"
|
||||
"DefaultConnection": "Server=SDD-VMP04-SQL17\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;TrustServerCertificate=True;",
|
||||
"MassDataConnection": "Server=SDD-VMP04-SQL19\\DD_DEVELOP01;Database=DD_ECM;User Id=sa;Password=dd;TrustServerCertificate=True;"
|
||||
},
|
||||
"Dashboard": {
|
||||
"BaseUrl": "https://localhost:7204"
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
using MediatR;
|
||||
|
||||
namespace DbFirst.Application.MassData.Commands;
|
||||
|
||||
public record UpsertMassDataByCustomerNameCommand(MassDataWriteDto Dto) : IRequest<MassDataReadDto>;
|
||||
@@ -0,0 +1,24 @@
|
||||
using AutoMapper;
|
||||
using DbFirst.Application.Repositories;
|
||||
using MediatR;
|
||||
|
||||
namespace DbFirst.Application.MassData.Commands;
|
||||
|
||||
public class UpsertMassDataByCustomerNameHandler : IRequestHandler<UpsertMassDataByCustomerNameCommand, MassDataReadDto>
|
||||
{
|
||||
private readonly IMassDataRepository _repository;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public UpsertMassDataByCustomerNameHandler(IMassDataRepository repository, IMapper mapper)
|
||||
{
|
||||
_repository = repository;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<MassDataReadDto> Handle(UpsertMassDataByCustomerNameCommand request, CancellationToken cancellationToken)
|
||||
{
|
||||
var dto = request.Dto;
|
||||
var updated = await _repository.UpsertByCustomerNameAsync(dto.CustomerName, dto.Amount, dto.StatusFlag, dto.Category, cancellationToken);
|
||||
return _mapper.Map<MassDataReadDto>(updated);
|
||||
}
|
||||
}
|
||||
12
DbFirst.Application/MassData/MassDataProfile.cs
Normal file
12
DbFirst.Application/MassData/MassDataProfile.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
using AutoMapper;
|
||||
using DbFirst.Domain.Entities;
|
||||
|
||||
namespace DbFirst.Application.MassData;
|
||||
|
||||
public class MassDataProfile : Profile
|
||||
{
|
||||
public MassDataProfile()
|
||||
{
|
||||
CreateMap<Massdata, MassDataReadDto>();
|
||||
}
|
||||
}
|
||||
12
DbFirst.Application/MassData/MassDataReadDto.cs
Normal file
12
DbFirst.Application/MassData/MassDataReadDto.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace DbFirst.Application.MassData;
|
||||
|
||||
public class MassDataReadDto
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string CustomerName { get; set; } = string.Empty;
|
||||
public decimal Amount { get; set; }
|
||||
public string Category { get; set; } = string.Empty;
|
||||
public bool StatusFlag { get; set; }
|
||||
public DateTime AddedWhen { get; set; }
|
||||
public DateTime? ChangedWhen { get; set; }
|
||||
}
|
||||
9
DbFirst.Application/MassData/MassDataWriteDto.cs
Normal file
9
DbFirst.Application/MassData/MassDataWriteDto.cs
Normal file
@@ -0,0 +1,9 @@
|
||||
namespace DbFirst.Application.MassData;
|
||||
|
||||
public class MassDataWriteDto
|
||||
{
|
||||
public string CustomerName { get; set; } = string.Empty;
|
||||
public decimal Amount { get; set; }
|
||||
public string Category { get; set; } = string.Empty;
|
||||
public bool StatusFlag { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
using AutoMapper;
|
||||
using DbFirst.Application.Repositories;
|
||||
using MediatR;
|
||||
|
||||
namespace DbFirst.Application.MassData.Queries;
|
||||
|
||||
public class GetAllMassDataHandler : IRequestHandler<GetAllMassDataQuery, List<MassDataReadDto>>
|
||||
{
|
||||
private readonly IMassDataRepository _repository;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public GetAllMassDataHandler(IMassDataRepository repository, IMapper mapper)
|
||||
{
|
||||
_repository = repository;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<List<MassDataReadDto>> Handle(GetAllMassDataQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var items = await _repository.GetAllAsync(request.Skip, request.Take, cancellationToken);
|
||||
return _mapper.Map<List<MassDataReadDto>>(items);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
using MediatR;
|
||||
|
||||
namespace DbFirst.Application.MassData.Queries;
|
||||
|
||||
public record GetAllMassDataQuery(int? Skip, int? Take) : IRequest<List<MassDataReadDto>>;
|
||||
@@ -0,0 +1,23 @@
|
||||
using AutoMapper;
|
||||
using DbFirst.Application.Repositories;
|
||||
using MediatR;
|
||||
|
||||
namespace DbFirst.Application.MassData.Queries;
|
||||
|
||||
public class GetMassDataByCustomerNameHandler : IRequestHandler<GetMassDataByCustomerNameQuery, MassDataReadDto?>
|
||||
{
|
||||
private readonly IMassDataRepository _repository;
|
||||
private readonly IMapper _mapper;
|
||||
|
||||
public GetMassDataByCustomerNameHandler(IMassDataRepository repository, IMapper mapper)
|
||||
{
|
||||
_repository = repository;
|
||||
_mapper = mapper;
|
||||
}
|
||||
|
||||
public async Task<MassDataReadDto?> Handle(GetMassDataByCustomerNameQuery request, CancellationToken cancellationToken)
|
||||
{
|
||||
var item = await _repository.GetByCustomerNameAsync(request.CustomerName, cancellationToken);
|
||||
return item == null ? null : _mapper.Map<MassDataReadDto>(item);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
using MediatR;
|
||||
|
||||
namespace DbFirst.Application.MassData.Queries;
|
||||
|
||||
public record GetMassDataByCustomerNameQuery(string CustomerName) : IRequest<MassDataReadDto?>;
|
||||
10
DbFirst.Application/Repositories/IMassDataRepository.cs
Normal file
10
DbFirst.Application/Repositories/IMassDataRepository.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using DbFirst.Domain.Entities;
|
||||
|
||||
namespace DbFirst.Application.Repositories;
|
||||
|
||||
public interface IMassDataRepository
|
||||
{
|
||||
Task<List<Massdata>> GetAllAsync(int? skip = null, int? take = null, CancellationToken cancellationToken = default);
|
||||
Task<Massdata?> GetByCustomerNameAsync(string customerName, CancellationToken cancellationToken = default);
|
||||
Task<Massdata> UpsertByCustomerNameAsync(string customerName, decimal amount, bool statusFlag, string category, CancellationToken cancellationToken = default);
|
||||
}
|
||||
12
DbFirst.Domain/Entities/Massdata.cs
Normal file
12
DbFirst.Domain/Entities/Massdata.cs
Normal file
@@ -0,0 +1,12 @@
|
||||
namespace DbFirst.Domain.Entities;
|
||||
|
||||
public class Massdata
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string CustomerName { get; set; } = string.Empty;
|
||||
public decimal Amount { get; set; }
|
||||
public string Category { get; set; } = string.Empty;
|
||||
public bool StatusFlag { get; set; }
|
||||
public DateTime AddedWhen { get; set; }
|
||||
public DateTime? ChangedWhen { get; set; }
|
||||
}
|
||||
@@ -11,6 +11,10 @@ public static class DependencyInjection
|
||||
services.Configure<TableConfigurations>(configuration.GetSection("TableConfigurations"));
|
||||
services.AddDbContext<ApplicationDbContext>(options =>
|
||||
options.UseSqlServer(configuration.GetConnectionString("DefaultConnection")));
|
||||
|
||||
services.AddDbContext<MassDataDbContext>(options =>
|
||||
options.UseSqlServer(configuration.GetConnectionString("MassDataConnection")));
|
||||
|
||||
return services;
|
||||
}
|
||||
}
|
||||
|
||||
44
DbFirst.Infrastructure/MassDataDbContext.cs
Normal file
44
DbFirst.Infrastructure/MassDataDbContext.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using DbFirst.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DbFirst.Infrastructure;
|
||||
|
||||
public class MassDataDbContext : DbContext
|
||||
{
|
||||
public MassDataDbContext(DbContextOptions<MassDataDbContext> options)
|
||||
: base(options)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual DbSet<Massdata> Massdata { get; set; }
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
modelBuilder.Entity<Massdata>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id);
|
||||
entity.ToTable("MASSDATA");
|
||||
|
||||
entity.Property(e => e.Id).HasColumnName("ID");
|
||||
entity.Property(e => e.CustomerName)
|
||||
.HasMaxLength(200)
|
||||
.IsUnicode(false)
|
||||
.HasColumnName("CustomerName");
|
||||
entity.Property(e => e.Amount)
|
||||
.HasColumnType("decimal(12,2)")
|
||||
.HasColumnName("Amount");
|
||||
entity.Property(e => e.Category)
|
||||
.HasMaxLength(100)
|
||||
.IsUnicode(false)
|
||||
.HasColumnName("Category");
|
||||
entity.Property(e => e.StatusFlag)
|
||||
.HasColumnName("StatusFlag");
|
||||
entity.Property(e => e.AddedWhen)
|
||||
.HasColumnType("datetime")
|
||||
.HasColumnName("ADDED_WHEN");
|
||||
entity.Property(e => e.ChangedWhen)
|
||||
.HasColumnType("datetime")
|
||||
.HasColumnName("CHANGED_WHEN");
|
||||
});
|
||||
}
|
||||
}
|
||||
66
DbFirst.Infrastructure/Repositories/MassDataRepository.cs
Normal file
66
DbFirst.Infrastructure/Repositories/MassDataRepository.cs
Normal file
@@ -0,0 +1,66 @@
|
||||
using System.Data;
|
||||
using DbFirst.Application.Repositories;
|
||||
using DbFirst.Domain.Entities;
|
||||
using Microsoft.Data.SqlClient;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace DbFirst.Infrastructure.Repositories;
|
||||
|
||||
public class MassDataRepository : IMassDataRepository
|
||||
{
|
||||
private readonly MassDataDbContext _db;
|
||||
|
||||
public MassDataRepository(MassDataDbContext db)
|
||||
{
|
||||
_db = db;
|
||||
}
|
||||
|
||||
public async Task<List<Massdata>> GetAllAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _db.Massdata.AsNoTracking().ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<Massdata?> GetByCustomerNameAsync(string customerName, CancellationToken cancellationToken = default)
|
||||
{
|
||||
return await _db.Massdata.AsNoTracking()
|
||||
.FirstOrDefaultAsync(x => x.CustomerName == customerName, cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<List<Massdata>> GetAllAsync(int? skip = null, int? take = null, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var query = _db.Massdata.AsNoTracking().OrderBy(x => x.Id).AsQueryable();
|
||||
|
||||
if (skip.HasValue)
|
||||
{
|
||||
query = query.Skip(skip.Value);
|
||||
}
|
||||
|
||||
if (take.HasValue)
|
||||
{
|
||||
query = query.Take(take.Value);
|
||||
}
|
||||
|
||||
return await query.ToListAsync(cancellationToken);
|
||||
}
|
||||
|
||||
public async Task<Massdata> UpsertByCustomerNameAsync(string customerName, decimal amount, bool statusFlag, string category, CancellationToken cancellationToken = default)
|
||||
{
|
||||
var customerParam = new SqlParameter("@CustomerName", SqlDbType.VarChar, 200) { Value = customerName };
|
||||
var amountParam = new SqlParameter("@Amount", SqlDbType.Decimal) { Value = amount, Precision = 12, Scale = 2 };
|
||||
var statusParam = new SqlParameter("@StatusFlag", SqlDbType.Bit) { Value = statusFlag };
|
||||
var categoryParam = new SqlParameter("@Category", SqlDbType.VarChar, 100) { Value = category };
|
||||
|
||||
await _db.Database.ExecuteSqlRawAsync(
|
||||
"EXEC dbo.PRMassdata_UpsertByCustomerName @CustomerName, @Amount, @StatusFlag, @Category",
|
||||
parameters: new[] { customerParam, amountParam, statusParam, categoryParam },
|
||||
cancellationToken: cancellationToken);
|
||||
|
||||
var updated = await GetByCustomerNameAsync(customerName, cancellationToken);
|
||||
if (updated == null)
|
||||
{
|
||||
throw new InvalidOperationException("Upsert completed but record could not be loaded.");
|
||||
}
|
||||
|
||||
return updated;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user