WebApi + ClientApp, GraphQL, Reflection

This commit is contained in:
Boris Milašinović
2026-05-06 20:55:05 +02:00
parent 8f7c704a90
commit 4fb3de19f6
196 changed files with 10395 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
using AutoMapper;
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Handlers.EF.CommandHandlers.Generic;
using Events.WebAPI.Handlers.EF.Data.Postgres;
using Events.WebAPI.Handlers.EF.Models;
using Microsoft.Extensions.Logging;
namespace Events.WebAPI.Handlers.EF.CommandHandlers;
public class EventsCommandsHandler : GenericCommandHandler<Event, EventDTO, int>
{
public EventsCommandsHandler(EventsContext ctx, ILogger<EventsCommandsHandler> logger, IMapper mapper)
: base(ctx, logger, mapper)
{
}
}

View File

@@ -0,0 +1,56 @@
using MobilityOne.Common.Commands;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Events.WebAPI.Contract.Command;
using Events.WebAPI.Contract.DTOs;
using AutoMapper;
namespace Events.WebAPI.Handlers.EF.CommandHandlers.Generic;
public class GenericCommandHandler<TDal, TDto, TPK> : IRequestHandler<AddCommand<TDto, TPK>, TPK>,
IRequestHandler<UpdateCommand<TDto>>,
IRequestHandler<DeleteCommand<TDto, TPK>>
where TDal: class, IHasIdAsPK<TPK>
where TDto: IHasIdAsPK<TPK>
where TPK : IEquatable<TPK>
{
protected DbContext Ctx { get; }
protected ILogger Logger { get; }
protected IMapper Mapper { get; }
protected GenericCommandHandler(DbContext ctx, ILogger logger, IMapper mapper)
{
Ctx = ctx;
Logger = logger;
Mapper = mapper;
}
public virtual async Task<TPK> Handle(AddCommand<TDto, TPK> request, CancellationToken cancellationToken)
{
var entity = Mapper.Map<TDto, TDal>(request.Dto);
Ctx.Add(entity);
await Ctx.SaveChangesAsync(cancellationToken);
return entity.Id;
}
public virtual async Task Handle(UpdateCommand<TDto> request, CancellationToken cancellationToken)
{
var entity = await Ctx.Set<TDal>().FindAsync(request.Dto.Id);
if (entity != null)
{
Mapper.Map(request.Dto, entity);
await Ctx.SaveChangesAsync(cancellationToken);
}
else
{
Logger.LogError($"UpdateCommand<{typeof(TDto).Name}> : Invalid id #{request.Dto.Id}");
throw new ArgumentException($"Invalid id: {request.Dto.Id}");
}
}
public virtual async Task Handle(DeleteCommand<TDto, TPK> request, CancellationToken cancellationToken)
{
await Ctx.Set<TDal>().Where(d => d.Id.Equals(request.Id)).ExecuteDeleteAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,16 @@
using AutoMapper;
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Handlers.EF.CommandHandlers.Generic;
using Events.WebAPI.Handlers.EF.Data.Postgres;
using Events.WebAPI.Handlers.EF.Models;
using Microsoft.Extensions.Logging;
namespace Events.WebAPI.Handlers.EF.CommandHandlers;
public class PeopleCommandsHandler : GenericCommandHandler<Person, PersonDTO, int>
{
public PeopleCommandsHandler(EventsContext ctx, ILogger<PeopleCommandsHandler> logger, IMapper mapper)
: base(ctx, logger, mapper)
{
}
}

View File

@@ -0,0 +1,93 @@
using AutoMapper;
using Events.WebAPI.Contract.Command;
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Contract.Messages;
using Events.WebAPI.Handlers.EF.CommandHandlers.Generic;
using Events.WebAPI.Handlers.EF.Data.Postgres;
using Events.WebAPI.Handlers.EF.Models;
using MassTransit;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using MobilityOne.Common.Commands;
namespace Events.WebAPI.Handlers.EF.CommandHandlers;
public class RegistrationsCommandsHandler : GenericCommandHandler<Registration, RegistrationDTO, int>
{
private readonly IPublishEndpoint publishEndpoint;
public RegistrationsCommandsHandler(
EventsContext ctx,
ILogger<RegistrationsCommandsHandler> logger,
IMapper mapper,
IPublishEndpoint publishEndpoint)
: base(ctx, logger, mapper)
{
this.publishEndpoint = publishEndpoint;
}
public override async Task<int> Handle(AddCommand<RegistrationDTO, int> request, CancellationToken cancellationToken)
{
int id = await base.Handle(request, cancellationToken);
await publishEndpoint.Publish(new RegistrationCreated
{
RegistrationId = id,
PersonId = request.Dto.PersonId,
EventId = request.Dto.EventId,
SportId = request.Dto.SportId
}, cancellationToken);
return id;
}
public override async Task Handle(UpdateCommand<RegistrationDTO> request, CancellationToken cancellationToken)
{
var entity = await Ctx.Set<Registration>().SingleOrDefaultAsync(r => r.Id == request.Dto.Id, cancellationToken);
if (entity == null)
{
Logger.LogError("UpdateCommand<{DtoName}> : Invalid id #{Id}", typeof(RegistrationDTO).Name, request.Dto.Id);
throw new ArgumentException($"Invalid id: {request.Dto.Id}");
}
int previousPersonId = entity.PersonId;
int previousEventId = entity.EventId;
int previousSportId = entity.SportId;
await base.Handle(request, cancellationToken);
await publishEndpoint.Publish(new RegistrationUpdated
{
RegistrationId = request.Dto.Id,
PersonId = request.Dto.PersonId,
EventId = request.Dto.EventId,
SportId = request.Dto.SportId,
PreviousPersonId = previousPersonId,
PreviousEventId = previousEventId,
PreviousSportId = previousSportId
}, cancellationToken);
}
public override async Task Handle(DeleteCommand<RegistrationDTO, int> request, CancellationToken cancellationToken)
{
var entity = await Ctx.Set<Registration>()
.AsNoTracking()
.SingleOrDefaultAsync(r => r.Id == request.Id, cancellationToken);
if (entity == null)
{
Logger.LogError("DeleteCommand<{DtoName}> : Invalid id #{Id}", typeof(RegistrationDTO).Name, request.Id);
throw new ArgumentException($"Invalid id: {request.Id}");
}
await base.Handle(request, cancellationToken);
await publishEndpoint.Publish(new RegistrationDeleted
{
RegistrationId = entity.Id,
PersonId = entity.PersonId,
EventId = entity.EventId,
SportId = entity.SportId
}, cancellationToken);
}
}

View File

@@ -0,0 +1,17 @@
using AutoMapper;
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Handlers.EF.CommandHandlers.Generic;
using Events.WebAPI.Handlers.EF.Data.Postgres;
using Events.WebAPI.Handlers.EF.Models;
using Microsoft.Extensions.Logging;
namespace Events.WebAPI.Handlers.EF.CommandHandlers
{
public class SportsCommandsHandler : GenericCommandHandler<Sport, SportDTO, int>
{
public SportsCommandsHandler(EventsContext ctx, ILogger<SportsCommandsHandler> logger, IMapper mapper)
: base(ctx, logger, mapper)
{
}
}
}

View File

@@ -0,0 +1,165 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable enable
using System;
using System.Collections.Generic;
using Events.WebAPI.Handlers.EF.Models;
using Microsoft.EntityFrameworkCore;
namespace Events.WebAPI.Handlers.EF.Data.Postgres;
public partial class EventsContext : DbContext
{
public EventsContext(DbContextOptions<EventsContext> options)
: base(options)
{
}
public virtual DbSet<Country> Countries { get; set; }
public virtual DbSet<Event> Events { get; set; }
public virtual DbSet<Person> People { get; set; }
public virtual DbSet<Registration> Registrations { get; set; }
public virtual DbSet<Sport> Sports { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Country>(entity =>
{
entity.HasKey(e => e.Code).HasName("country_pkey");
entity.ToTable("country");
entity.HasIndex(e => e.Name, "country_name_key").IsUnique();
entity.Property(e => e.Code)
.HasMaxLength(3)
.HasColumnName("code");
entity.Property(e => e.Alpha3)
.HasMaxLength(3)
.IsFixedLength()
.HasColumnName("alpha3");
entity.Property(e => e.Name)
.HasMaxLength(100)
.HasColumnName("name");
entity.Property(e => e.Translations)
.HasColumnType("jsonb")
.HasColumnName("translations");
});
modelBuilder.Entity<Event>(entity =>
{
entity.HasKey(e => e.Id).HasName("event_pkey");
entity.ToTable("event");
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.EventDate).HasColumnName("event_date");
entity.Property(e => e.Name)
.HasMaxLength(150)
.HasColumnName("name");
});
modelBuilder.Entity<Person>(entity =>
{
entity.HasKey(e => e.Id).HasName("person_pkey");
entity.ToTable("person");
entity.HasIndex(e => new { e.DocumentNumber, e.CountryCode }, "person_document_number_country_code_key").IsUnique();
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.AddressCountry)
.HasMaxLength(100)
.HasColumnName("address_country");
entity.Property(e => e.AddressLine)
.HasMaxLength(200)
.HasColumnName("address_line");
entity.Property(e => e.BirthDate).HasColumnName("birth_date");
entity.Property(e => e.City)
.HasMaxLength(100)
.HasColumnName("city");
entity.Property(e => e.ContactPhone)
.HasMaxLength(50)
.HasColumnName("contact_phone");
entity.Property(e => e.CountryCode)
.HasMaxLength(3)
.HasColumnName("country_code");
entity.Property(e => e.DocumentNumber)
.HasMaxLength(50)
.HasColumnName("document_number");
entity.Property(e => e.Email)
.HasMaxLength(255)
.HasColumnName("email");
entity.Property(e => e.FirstName)
.HasMaxLength(100)
.HasColumnName("first_name");
entity.Property(e => e.FirstNameTranscription)
.HasMaxLength(100)
.HasColumnName("first_name_transcription");
entity.Property(e => e.LastName)
.HasMaxLength(100)
.HasColumnName("last_name");
entity.Property(e => e.LastNameTranscription)
.HasMaxLength(100)
.HasColumnName("last_name_transcription");
entity.Property(e => e.PostalCode)
.HasMaxLength(20)
.HasColumnName("postal_code");
entity.HasOne(d => d.CountryCodeNavigation).WithMany(p => p.People)
.HasForeignKey(d => d.CountryCode)
.OnDelete(DeleteBehavior.ClientSetNull)
.HasConstraintName("person_country_code_fkey");
});
modelBuilder.Entity<Registration>(entity =>
{
entity.HasKey(e => e.Id).HasName("registration_pkey");
entity.ToTable("registration");
entity.HasIndex(e => new { e.PersonId, e.SportId, e.EventId }, "registration_person_id_sport_id_event_id_key").IsUnique();
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.EventId).HasColumnName("event_id");
entity.Property(e => e.PersonId).HasColumnName("person_id");
entity.Property(e => e.RegisteredAt)
.HasDefaultValueSql("CURRENT_TIMESTAMP")
.HasColumnName("registered_at");
entity.Property(e => e.SportId).HasColumnName("sport_id");
entity.HasOne(d => d.Event).WithMany(p => p.Registrations)
.HasForeignKey(d => d.EventId)
.HasConstraintName("registration_event_id_fkey");
entity.HasOne(d => d.Person).WithMany(p => p.Registrations)
.HasForeignKey(d => d.PersonId)
.HasConstraintName("registration_person_id_fkey");
entity.HasOne(d => d.Sport).WithMany(p => p.Registrations)
.HasForeignKey(d => d.SportId)
.HasConstraintName("registration_sport_id_fkey");
});
modelBuilder.Entity<Sport>(entity =>
{
entity.HasKey(e => e.Id).HasName("sport_pkey");
entity.ToTable("sport");
entity.HasIndex(e => e.Name, "sport_name_key").IsUnique();
entity.Property(e => e.Id).HasColumnName("id");
entity.Property(e => e.Name)
.HasMaxLength(100)
.HasColumnName("name");
});
OnModelCreatingPartial(modelBuilder);
}
partial void OnModelCreatingPartial(ModelBuilder modelBuilder);
}

View File

@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Events.WebAPI.Contract\Events.WebAPI.Contract.csproj" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="AutoMapper" Version="16.1.1" />
<PackageReference Include="MassTransit" Version="8.5.9" />
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="10.0.1" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,39 @@
using AutoMapper;
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Handlers.EF.Models;
namespace Events.WebAPI.Handlers.EF.Mappings;
public class EFMappingProfile : Profile
{
public EFMappingProfile()
{
CreateMap<Event, EventDTO>()
.ForMember(o => o.RegistrationsCount, opt => opt.MapFrom(src => src.Registrations.Count));
CreateMap<EventDTO, Event>()
.ForMember(o => o.Id, opt => opt.Ignore());
CreateMap<Person, PersonDTO>()
.ForMember(o => o.CountryName, opt => opt.MapFrom(src => src.CountryCodeNavigation.Name))
.ForMember(o => o.FullNameTranscription, opt => opt.MapFrom(src => src.FirstNameTranscription + " " + src.LastNameTranscription))
.ForMember(o => o.RegistrationsCount, opt => opt.MapFrom(src => src.Registrations.Count));
CreateMap<PersonDTO, Person>()
.ForMember(o => o.Id, opt => opt.Ignore());
CreateMap<Registration, RegistrationDTO>()
.ForMember(o => o.PersonName, opt => opt.MapFrom(src => src.Person.FirstName + " " + src.Person.LastName))
.ForMember(o => o.PersonTranscription, opt => opt.MapFrom(src => src.Person.FirstNameTranscription + " " + src.Person.LastNameTranscription))
.ForMember(o => o.PersonFirstNameTranscription, opt => opt.MapFrom(src => src.Person.FirstNameTranscription))
.ForMember(o => o.PersonLastNameTranscription, opt => opt.MapFrom(src => src.Person.LastNameTranscription))
.ForMember(o => o.CountryCode, opt => opt.MapFrom(src => src.Person.CountryCode))
.ForMember(o => o.CountryName, opt => opt.MapFrom(src => src.Person.CountryCodeNavigation.Name))
.ForMember(o => o.SportName, opt => opt.MapFrom(src => src.Sport.Name));
CreateMap<RegistrationDTO, Registration>()
.ForMember(o => o.Id, opt => opt.Ignore())
.ForMember(o => o.RegisteredAt, opt => opt.Ignore());
CreateMap<Sport, SportDTO>();
CreateMap<SportDTO, Sport>()
.ForMember(o => o.Id, opt => opt.Ignore());
}
}

View File

@@ -0,0 +1,7 @@
using Events.WebAPI.Contract.DTOs;
namespace Events.WebAPI.Handlers.EF.Models;
public partial class Event : IHasIdAsPK<int>
{
}

View File

@@ -0,0 +1,7 @@
using Events.WebAPI.Contract.DTOs;
namespace Events.WebAPI.Handlers.EF.Models;
public partial class Person : IHasIdAsPK<int>
{
}

View File

@@ -0,0 +1,7 @@
using Events.WebAPI.Contract.DTOs;
namespace Events.WebAPI.Handlers.EF.Models;
public partial class Registration : IHasIdAsPK<int>
{
}

View File

@@ -0,0 +1,7 @@
using Events.WebAPI.Contract.DTOs;
namespace Events.WebAPI.Handlers.EF.Models;
public partial class Sport : IHasIdAsPK<int>
{
}

View File

@@ -0,0 +1,19 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable enable
using System;
using System.Collections.Generic;
namespace Events.WebAPI.Handlers.EF.Models;
public partial class Country
{
public string Code { get; set; } = null!;
public string Alpha3 { get; set; } = null!;
public string Name { get; set; } = null!;
public string? Translations { get; set; }
public virtual ICollection<Person> People { get; set; } = new List<Person>();
}

View File

@@ -0,0 +1,17 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable enable
using System;
using System.Collections.Generic;
namespace Events.WebAPI.Handlers.EF.Models;
public partial class Event
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public DateOnly EventDate { get; set; }
public virtual ICollection<Registration> Registrations { get; set; } = new List<Registration>();
}

View File

@@ -0,0 +1,41 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable enable
using System;
using System.Collections.Generic;
namespace Events.WebAPI.Handlers.EF.Models;
public partial class Person
{
public int Id { get; set; }
public string? FirstName { get; set; }
public string? LastName { get; set; }
public string FirstNameTranscription { get; set; } = null!;
public string LastNameTranscription { get; set; } = null!;
public string? AddressLine { get; set; }
public string? PostalCode { get; set; }
public string? City { get; set; }
public string? AddressCountry { get; set; }
public string? Email { get; set; }
public string? ContactPhone { get; set; }
public DateOnly BirthDate { get; set; }
public string DocumentNumber { get; set; } = null!;
public string CountryCode { get; set; } = null!;
public virtual Country CountryCodeNavigation { get; set; } = null!;
public virtual ICollection<Registration> Registrations { get; set; } = new List<Registration>();
}

View File

@@ -0,0 +1,25 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable enable
using System;
using System.Collections.Generic;
namespace Events.WebAPI.Handlers.EF.Models;
public partial class Registration
{
public int Id { get; set; }
public int PersonId { get; set; }
public int SportId { get; set; }
public int EventId { get; set; }
public DateTime RegisteredAt { get; set; }
public virtual Event Event { get; set; } = null!;
public virtual Person Person { get; set; } = null!;
public virtual Sport Sport { get; set; } = null!;
}

View File

@@ -0,0 +1,15 @@
// <auto-generated> This file has been auto generated by EF Core Power Tools. </auto-generated>
#nullable enable
using System;
using System.Collections.Generic;
namespace Events.WebAPI.Handlers.EF.Models;
public partial class Sport
{
public int Id { get; set; }
public string Name { get; set; } = null!;
public virtual ICollection<Registration> Registrations { get; set; } = new List<Registration>();
}

View File

@@ -0,0 +1,37 @@
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Contract.LookupQueries;
using Events.WebAPI.Handlers.EF.Data.Postgres;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Events.WebAPI.Handlers.EF.QueryHandlers;
public class CountriesLookupQueryHandler : IRequestHandler<LookupCountryQuery, List<IdName<string>>>
{
private readonly EventsContext ctx;
public CountriesLookupQueryHandler(EventsContext ctx)
{
this.ctx = ctx;
}
public async Task<List<IdName<string>>> Handle(LookupCountryQuery request, CancellationToken cancellationToken)
{
var query = ctx.Countries.AsNoTracking();
if (!string.IsNullOrWhiteSpace(request.Text))
{
string text = request.Text.Trim();
query = query.Where(c => Microsoft.EntityFrameworkCore.EF.Functions.ILike(c.Name, $"%{text}%"));
}
return await query
.OrderBy(c => c.Name)
.Select(c => new IdName<string>
{
Id = c.Code,
Name = c.Name
})
.ToListAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,17 @@
using AutoMapper;
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Handlers.EF.Data.Postgres;
using Events.WebAPI.Handlers.EF.Models;
using Events.WebAPI.Handlers.EF.QueryHandlers.Generic;
using Microsoft.Extensions.Logging;
using Sieve.Services;
namespace Events.WebAPI.Handlers.EF.QueryHandlers;
public class EventsQueryHandler : GenericQueryHandler<EventDTO, Event, int>
{
public EventsQueryHandler(EventsContext ctx, ILogger<EventsQueryHandler> logger, IMapper mapper, ISieveProcessor sieveProcessor)
: base(ctx, logger, mapper, sieveProcessor)
{
}
}

View File

@@ -0,0 +1,97 @@
using AutoMapper;
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Contract.Queries.Generic;
using MediatR;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Sieve.Models;
using Sieve.Services;
namespace Events.WebAPI.Handlers.EF.QueryHandlers.Generic
{
public abstract class GenericQueryHandler<TDto, TDal, TPK> :
IRequestHandler<GetSingleItemQuery<TDto, TPK>, TDto?>,
IRequestHandler<GetCountQuery<TDto>, int>,
IRequestHandler<DoesItemExistsQuery<TDto, TPK>, bool>,
IRequestHandler<GetItemsQuery<TDto>, List<TDto>>
where TDal : class, IHasIdAsPK<TPK>
where TPK : IEquatable<TPK>
{
private readonly DbContext ctx;
protected readonly ILogger logger;
private readonly IMapper mapper;
private readonly ISieveProcessor sieveProcessor;
public GenericQueryHandler(DbContext ctx, ILogger logger, IMapper mapper, ISieveProcessor sieveProcessor)
{
this.ctx = ctx;
this.logger = logger;
this.mapper = mapper;
this.sieveProcessor = sieveProcessor;
}
public virtual async Task<int> Handle(GetCountQuery<TDto> request, CancellationToken cancellationToken)
{
var query = ctx.Set<TDal>().AsNoTracking();
IQueryable<TDto> projectedQuery = mapper.ProjectTo<TDto>(query);
SieveModel sieveModel = new SieveModel()
{
Filters = request.Filters,
};
var filteredQuery = sieveProcessor.Apply(sieveModel, projectedQuery, applyFiltering: true, applySorting: false, applyPagination: false);
int count = await filteredQuery.CountAsync(cancellationToken);
return count;
}
public virtual async Task<TDto?> Handle(GetSingleItemQuery<TDto, TPK> request, CancellationToken cancellationToken)
{
var query = ctx.Set<TDal>()
.AsNoTracking()
.Where(t => t.Id.Equals(request.Id));
IQueryable<TDto> projectedQuery = mapper.ProjectTo<TDto>(query);
var item = await projectedQuery.FirstOrDefaultAsync(cancellationToken);
return item;
}
public virtual async Task<bool> Handle(DoesItemExistsQuery<TDto, TPK> request, CancellationToken cancellationToken)
{
var query = ctx.Set<TDal>()
.AsNoTracking()
.Where(t => t.Id.Equals(request.Id));
bool exists = await query.AnyAsync(cancellationToken);
return exists;
}
public virtual async Task<List<TDto>> Handle(GetItemsQuery<TDto> request, CancellationToken cancellationToken)
{
var query = ctx.Set<TDal>().AsNoTracking();
IQueryable<TDto> projectedQuery = mapper.ProjectTo<TDto>(query);
SieveModel sieveModel = new SieveModel()
{
Filters = request.Filters,
Sorts = BuildSortExpression(request),
PageSize = request.PageSize,
Page = request.Page
};
var filteredQuery = sieveProcessor.Apply(sieveModel, projectedQuery, applyFiltering: true, applySorting: true, applyPagination: true);
var data = await filteredQuery.ToListAsync(cancellationToken);
return data;
}
private static string? BuildSortExpression(GetItemsQuery<TDto> request)
{
if (!string.IsNullOrWhiteSpace(request.Sort))
{
return request.Ascending ? request.Sort : "-" + request.Sort;
}
bool paginationRequested = request.Page.HasValue || request.PageSize.HasValue;
return paginationRequested ? nameof(IHasIdAsPK<int>.Id) : null;
}
}
}

View File

@@ -0,0 +1,48 @@
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Contract.LookupQueries;
using Events.WebAPI.Handlers.EF.Data.Postgres;
using MediatR;
using Microsoft.EntityFrameworkCore;
namespace Events.WebAPI.Handlers.EF.QueryHandlers;
public class PeopleLookupQueryHandler : IRequestHandler<LookupPeopleQuery, List<IdName<int>>>
{
private readonly EventsContext ctx;
public PeopleLookupQueryHandler(EventsContext ctx)
{
this.ctx = ctx;
}
public async Task<List<IdName<int>>> Handle(LookupPeopleQuery request, CancellationToken cancellationToken)
{
var query = ctx.People.AsNoTracking();
if (!string.IsNullOrWhiteSpace(request.Text))
{
string text = request.Text.Trim();
query = query.Where(p =>
global::Microsoft.EntityFrameworkCore.EF.Functions.ILike(
p.FirstNameTranscription + " " + p.LastNameTranscription,
$"%{text}%"));
}
if (!string.IsNullOrWhiteSpace(request.CountryCode))
{
string countryCode = request.CountryCode.Trim();
query = query.Where(p => p.CountryCode == countryCode);
}
return await query
.OrderBy(p => p.FirstNameTranscription)
.ThenBy(p => p.LastNameTranscription)
.Select(p => new IdName<int>
{
Id = p.Id,
Name = p.FirstName + " " + p.LastName,
Description = p.FirstNameTranscription + " " + p.LastNameTranscription
})
.ToListAsync(cancellationToken);
}
}

View File

@@ -0,0 +1,17 @@
using AutoMapper;
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Handlers.EF.Data.Postgres;
using Events.WebAPI.Handlers.EF.Models;
using Events.WebAPI.Handlers.EF.QueryHandlers.Generic;
using Microsoft.Extensions.Logging;
using Sieve.Services;
namespace Events.WebAPI.Handlers.EF.QueryHandlers;
public class PeopleQueryHandler : GenericQueryHandler<PersonDTO, Person, int>
{
public PeopleQueryHandler(EventsContext ctx, ILogger<PeopleQueryHandler> logger, IMapper mapper, ISieveProcessor sieveProcessor)
: base(ctx, logger, mapper, sieveProcessor)
{
}
}

View File

@@ -0,0 +1,17 @@
using AutoMapper;
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Handlers.EF.Data.Postgres;
using Events.WebAPI.Handlers.EF.Models;
using Events.WebAPI.Handlers.EF.QueryHandlers.Generic;
using Microsoft.Extensions.Logging;
using Sieve.Services;
namespace Events.WebAPI.Handlers.EF.QueryHandlers;
public class RegistrationsQueryHandler : GenericQueryHandler<RegistrationDTO, Registration, int>
{
public RegistrationsQueryHandler(EventsContext ctx, ILogger<RegistrationsQueryHandler> logger, IMapper mapper, ISieveProcessor sieveProcessor)
: base(ctx, logger, mapper, sieveProcessor)
{
}
}

View File

@@ -0,0 +1,18 @@
using AutoMapper;
using Events.WebAPI.Contract.DTOs;
using Events.WebAPI.Handlers.EF.Data.Postgres;
using Events.WebAPI.Handlers.EF.Models;
using Events.WebAPI.Handlers.EF.QueryHandlers.Generic;
using Microsoft.Extensions.Logging;
using Sieve.Services;
namespace Events.WebAPI.Handlers.EF.QueryHandlers;
public class SportsQueryHandler : GenericQueryHandler<SportDTO, Sport, int>
{
public SportsQueryHandler(EventsContext ctx, ILogger<SportsQueryHandler> logger, IMapper mapper, ISieveProcessor sieveProcessor)
: base(ctx, logger, mapper, sieveProcessor)
{
}
}

View File

@@ -0,0 +1,70 @@
{
"CodeGenerationMode": 6,
"ContextClassName": "EventsContext",
"ContextNamespace": null,
"FilterSchemas": false,
"IncludeConnectionString": false,
"IrregularWords": null,
"MinimumProductVersion": "2.6.1465",
"ModelNamespace": null,
"OutputContextPath": "Data\/Postgres",
"OutputPath": "Models",
"PluralRules": null,
"PreserveCasingWithRegex": true,
"ProjectRootNamespace": "Events.WebAPI.Handlers.EF",
"Schemas": null,
"SelectedHandlebarsLanguage": 2,
"SelectedToBeGenerated": 0,
"SingularRules": null,
"T4TemplatePath": null,
"Tables": [
{
"Name": "public.country",
"ObjectType": 0
},
{
"Name": "public.event",
"ObjectType": 0
},
{
"Name": "public.person",
"ObjectType": 0
},
{
"Name": "public.registration",
"ObjectType": 0
},
{
"Name": "public.sport",
"ObjectType": 0
}
],
"UiHint": null,
"UncountableWords": null,
"UseAsyncStoredProcedureCalls": true,
"UseBoolPropertiesWithoutDefaultSql": false,
"UseDatabaseNames": false,
"UseDatabaseNamesForRoutines": true,
"UseDateOnlyTimeOnly": true,
"UseDbContextSplitting": false,
"UseDecimalDataAnnotationForSprocResult": true,
"UseFluentApiOnly": true,
"UseHandleBars": false,
"UseHierarchyId": false,
"UseInflector": true,
"UseInternalAccessModifiersForSprocsAndFunctions": false,
"UseLegacyPluralizer": false,
"UseManyToManyEntity": false,
"UseNoDefaultConstructor": true,
"UseNoNavigations": false,
"UseNoObjectFilter": false,
"UseNodaTime": false,
"UseNullableReferences": true,
"UsePrefixNavigationNaming": false,
"UseSchemaFolders": false,
"UseSchemaNamespaces": false,
"UseSpatial": false,
"UseT4": false,
"UseT4Split": false,
"UseTypedTvpParameters": true
}