diff --git a/DbFirst.API/Controllers/MassDataController.cs b/DbFirst.API/Controllers/MassDataController.cs new file mode 100644 index 0000000..96d09f3 --- /dev/null +++ b/DbFirst.API/Controllers/MassDataController.cs @@ -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>> 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> 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> Upsert(MassDataWriteDto dto, CancellationToken cancellationToken) + { + var result = await _mediator.Send(new UpsertMassDataByCustomerNameCommand(dto), cancellationToken); + return Ok(result); + } +} diff --git a/DbFirst.API/Program.cs b/DbFirst.API/Program.cs index 58e1536..98c6e0b 100644 --- a/DbFirst.API/Program.cs +++ b/DbFirst.API/Program.cs @@ -52,6 +52,7 @@ builder.Services.AddInfrastructure(builder.Configuration); builder.Services.AddApplication(); builder.Services.AddScoped(); +builder.Services.AddScoped(); builder.Services.AddDevExpressControls(); builder.Services.AddSignalR(); diff --git a/DbFirst.API/appsettings.json b/DbFirst.API/appsettings.json index d34692d..02a6d09 100644 --- a/DbFirst.API/appsettings.json +++ b/DbFirst.API/appsettings.json @@ -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" diff --git a/DbFirst.Application/MassData/Commands/UpsertMassDataByCustomerNameCommand.cs b/DbFirst.Application/MassData/Commands/UpsertMassDataByCustomerNameCommand.cs new file mode 100644 index 0000000..f719910 --- /dev/null +++ b/DbFirst.Application/MassData/Commands/UpsertMassDataByCustomerNameCommand.cs @@ -0,0 +1,5 @@ +using MediatR; + +namespace DbFirst.Application.MassData.Commands; + +public record UpsertMassDataByCustomerNameCommand(MassDataWriteDto Dto) : IRequest; diff --git a/DbFirst.Application/MassData/Commands/UpsertMassDataByCustomerNameHandler.cs b/DbFirst.Application/MassData/Commands/UpsertMassDataByCustomerNameHandler.cs new file mode 100644 index 0000000..e301fc9 --- /dev/null +++ b/DbFirst.Application/MassData/Commands/UpsertMassDataByCustomerNameHandler.cs @@ -0,0 +1,24 @@ +using AutoMapper; +using DbFirst.Application.Repositories; +using MediatR; + +namespace DbFirst.Application.MassData.Commands; + +public class UpsertMassDataByCustomerNameHandler : IRequestHandler +{ + private readonly IMassDataRepository _repository; + private readonly IMapper _mapper; + + public UpsertMassDataByCustomerNameHandler(IMassDataRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + + public async Task 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(updated); + } +} diff --git a/DbFirst.Application/MassData/MassDataProfile.cs b/DbFirst.Application/MassData/MassDataProfile.cs new file mode 100644 index 0000000..b0f260c --- /dev/null +++ b/DbFirst.Application/MassData/MassDataProfile.cs @@ -0,0 +1,12 @@ +using AutoMapper; +using DbFirst.Domain.Entities; + +namespace DbFirst.Application.MassData; + +public class MassDataProfile : Profile +{ + public MassDataProfile() + { + CreateMap(); + } +} diff --git a/DbFirst.Application/MassData/MassDataReadDto.cs b/DbFirst.Application/MassData/MassDataReadDto.cs new file mode 100644 index 0000000..db2fa09 --- /dev/null +++ b/DbFirst.Application/MassData/MassDataReadDto.cs @@ -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; } +} diff --git a/DbFirst.Application/MassData/MassDataWriteDto.cs b/DbFirst.Application/MassData/MassDataWriteDto.cs new file mode 100644 index 0000000..e1cacb5 --- /dev/null +++ b/DbFirst.Application/MassData/MassDataWriteDto.cs @@ -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; } +} diff --git a/DbFirst.Application/MassData/Queries/GetAllMassDataHandler.cs b/DbFirst.Application/MassData/Queries/GetAllMassDataHandler.cs new file mode 100644 index 0000000..e3003d0 --- /dev/null +++ b/DbFirst.Application/MassData/Queries/GetAllMassDataHandler.cs @@ -0,0 +1,23 @@ +using AutoMapper; +using DbFirst.Application.Repositories; +using MediatR; + +namespace DbFirst.Application.MassData.Queries; + +public class GetAllMassDataHandler : IRequestHandler> +{ + private readonly IMassDataRepository _repository; + private readonly IMapper _mapper; + + public GetAllMassDataHandler(IMassDataRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + + public async Task> Handle(GetAllMassDataQuery request, CancellationToken cancellationToken) + { + var items = await _repository.GetAllAsync(request.Skip, request.Take, cancellationToken); + return _mapper.Map>(items); + } +} diff --git a/DbFirst.Application/MassData/Queries/GetAllMassDataQuery.cs b/DbFirst.Application/MassData/Queries/GetAllMassDataQuery.cs new file mode 100644 index 0000000..84b350a --- /dev/null +++ b/DbFirst.Application/MassData/Queries/GetAllMassDataQuery.cs @@ -0,0 +1,5 @@ +using MediatR; + +namespace DbFirst.Application.MassData.Queries; + +public record GetAllMassDataQuery(int? Skip, int? Take) : IRequest>; diff --git a/DbFirst.Application/MassData/Queries/GetMassDataByCustomerNameHandler.cs b/DbFirst.Application/MassData/Queries/GetMassDataByCustomerNameHandler.cs new file mode 100644 index 0000000..7d518c9 --- /dev/null +++ b/DbFirst.Application/MassData/Queries/GetMassDataByCustomerNameHandler.cs @@ -0,0 +1,23 @@ +using AutoMapper; +using DbFirst.Application.Repositories; +using MediatR; + +namespace DbFirst.Application.MassData.Queries; + +public class GetMassDataByCustomerNameHandler : IRequestHandler +{ + private readonly IMassDataRepository _repository; + private readonly IMapper _mapper; + + public GetMassDataByCustomerNameHandler(IMassDataRepository repository, IMapper mapper) + { + _repository = repository; + _mapper = mapper; + } + + public async Task Handle(GetMassDataByCustomerNameQuery request, CancellationToken cancellationToken) + { + var item = await _repository.GetByCustomerNameAsync(request.CustomerName, cancellationToken); + return item == null ? null : _mapper.Map(item); + } +} diff --git a/DbFirst.Application/MassData/Queries/GetMassDataByCustomerNameQuery.cs b/DbFirst.Application/MassData/Queries/GetMassDataByCustomerNameQuery.cs new file mode 100644 index 0000000..26d75a6 --- /dev/null +++ b/DbFirst.Application/MassData/Queries/GetMassDataByCustomerNameQuery.cs @@ -0,0 +1,5 @@ +using MediatR; + +namespace DbFirst.Application.MassData.Queries; + +public record GetMassDataByCustomerNameQuery(string CustomerName) : IRequest; diff --git a/DbFirst.Application/Repositories/IMassDataRepository.cs b/DbFirst.Application/Repositories/IMassDataRepository.cs new file mode 100644 index 0000000..b578dc9 --- /dev/null +++ b/DbFirst.Application/Repositories/IMassDataRepository.cs @@ -0,0 +1,10 @@ +using DbFirst.Domain.Entities; + +namespace DbFirst.Application.Repositories; + +public interface IMassDataRepository +{ + Task> GetAllAsync(int? skip = null, int? take = null, CancellationToken cancellationToken = default); + Task GetByCustomerNameAsync(string customerName, CancellationToken cancellationToken = default); + Task UpsertByCustomerNameAsync(string customerName, decimal amount, bool statusFlag, string category, CancellationToken cancellationToken = default); +} diff --git a/DbFirst.Domain/Entities/Massdata.cs b/DbFirst.Domain/Entities/Massdata.cs new file mode 100644 index 0000000..03b12e9 --- /dev/null +++ b/DbFirst.Domain/Entities/Massdata.cs @@ -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; } +} diff --git a/DbFirst.Infrastructure/DependencyInjection.cs b/DbFirst.Infrastructure/DependencyInjection.cs index d01436f..4537270 100644 --- a/DbFirst.Infrastructure/DependencyInjection.cs +++ b/DbFirst.Infrastructure/DependencyInjection.cs @@ -11,6 +11,10 @@ public static class DependencyInjection services.Configure(configuration.GetSection("TableConfigurations")); services.AddDbContext(options => options.UseSqlServer(configuration.GetConnectionString("DefaultConnection"))); + + services.AddDbContext(options => + options.UseSqlServer(configuration.GetConnectionString("MassDataConnection"))); + return services; } } diff --git a/DbFirst.Infrastructure/MassDataDbContext.cs b/DbFirst.Infrastructure/MassDataDbContext.cs new file mode 100644 index 0000000..1975663 --- /dev/null +++ b/DbFirst.Infrastructure/MassDataDbContext.cs @@ -0,0 +1,44 @@ +using DbFirst.Domain.Entities; +using Microsoft.EntityFrameworkCore; + +namespace DbFirst.Infrastructure; + +public class MassDataDbContext : DbContext +{ + public MassDataDbContext(DbContextOptions options) + : base(options) + { + } + + public virtual DbSet Massdata { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity(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"); + }); + } +} diff --git a/DbFirst.Infrastructure/Repositories/MassDataRepository.cs b/DbFirst.Infrastructure/Repositories/MassDataRepository.cs new file mode 100644 index 0000000..e054a98 --- /dev/null +++ b/DbFirst.Infrastructure/Repositories/MassDataRepository.cs @@ -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> GetAllAsync(CancellationToken cancellationToken = default) + { + return await _db.Massdata.AsNoTracking().ToListAsync(cancellationToken); + } + + public async Task GetByCustomerNameAsync(string customerName, CancellationToken cancellationToken = default) + { + return await _db.Massdata.AsNoTracking() + .FirstOrDefaultAsync(x => x.CustomerName == customerName, cancellationToken); + } + + public async Task> 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 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; + } +}