Refactor email repository and query handling
Replaced `IMailRepository` with `IReceivedEmailRepository` to improve modularity and functionality. Updated `IReceivedEmailRepository` to make the `EmailAccount` parameter optional in the `FindAsync` method. Added `ReceivedEmailRepository` with advanced filtering capabilities, including account, flags, text, UID, date, and recipient filters. Implemented deferred materialization for recipient filtering due to EF Core limitations. Registered `IReceivedEmailRepository` in dependency injection. Updated `ReadEmailQueryHandler` to use the new repository. Improved query performance by applying filters conditionally.
This commit is contained in:
@@ -6,6 +6,6 @@ namespace DigitalData.MessagingService.Application.Common.Interfaces.Repositorie
|
|||||||
|
|
||||||
public interface IReceivedEmailRepository : IRepository<ReceivedEmail>
|
public interface IReceivedEmailRepository : IRepository<ReceivedEmail>
|
||||||
{
|
{
|
||||||
public Task<IEnumerable<ReceivedEmail>> FindAsync(MailSearchFilter mailSearchFilter, EmailAccount accountQuery, CancellationToken cancellationToken = default);
|
public Task<IEnumerable<ReceivedEmail>> FindAsync(MailSearchFilter mailSearchFilter, EmailAccount? accountQuery = null, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -28,7 +28,7 @@ public record ReadEmailQuery : IRequest<IEnumerable<ReceivedEmailDto>>
|
|||||||
public MailSearchFilter Mail { get; init; } = new();
|
public MailSearchFilter Mail { get; init; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ReadEmailQueryHandler(IMapper Mapper, ILogger<ReadEmailQueryHandler> Logger, IRepository<EmailAccount> EmailAccountRepo, IMailRepository MailRepo) : IRequestHandler<ReadEmailQuery, IEnumerable<ReceivedEmailDto>>
|
public class ReadEmailQueryHandler(IMapper Mapper, ILogger<ReadEmailQueryHandler> Logger, IRepository<EmailAccount> EmailAccountRepo, IReceivedEmailRepository MailRepo) : IRequestHandler<ReadEmailQuery, IEnumerable<ReceivedEmailDto>>
|
||||||
{
|
{
|
||||||
public async Task<IEnumerable<ReceivedEmailDto>> Handle(ReadEmailQuery request, CancellationToken cancellationToken)
|
public async Task<IEnumerable<ReceivedEmailDto>> Handle(ReadEmailQuery request, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ public static class DependencyInjection
|
|||||||
|
|
||||||
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
|
services.AddScoped(typeof(IRepository<>), typeof(Repository<>));
|
||||||
|
|
||||||
|
services.AddScoped<IReceivedEmailRepository, ReceivedEmailRepository>();
|
||||||
|
|
||||||
// AutoMapper - Register entity self-mappings (T -> T) for generic repository
|
// AutoMapper - Register entity self-mappings (T -> T) for generic repository
|
||||||
services.AddAutoMapper(config => config.AddMaps(typeof(EntitySelfMappingProfile).Assembly));
|
services.AddAutoMapper(config => config.AddMaps(typeof(EntitySelfMappingProfile).Assembly));
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using DigitalData.MessagingService.Application.Common.Dto.MailSearch;
|
||||||
|
using DigitalData.MessagingService.Application.Common.Interfaces.Repositories;
|
||||||
|
using DigitalData.MessagingService.Domain.Entities;
|
||||||
|
using DigitalData.MessagingService.Infrastructure.Persistence;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
namespace DigitalData.MessagingService.Infrastructure.Repositories;
|
||||||
|
|
||||||
|
public class ReceivedEmailRepository(MessagingServiceDbContext Context, IMapper Mapper) : Repository<ReceivedEmail>(Context, Mapper), IReceivedEmailRepository
|
||||||
|
{
|
||||||
|
public async Task<IEnumerable<ReceivedEmail>> FindAsync(MailSearchFilter mailSearchFilter, EmailAccount? accountQuery = null, CancellationToken cancellationToken = default)
|
||||||
|
{
|
||||||
|
IQueryable<ReceivedEmail> query = DbSet;
|
||||||
|
|
||||||
|
// ── Account filter ─────────────────────────────────────────────────────
|
||||||
|
if (accountQuery is not null)
|
||||||
|
query = query.Where(x => x.AccountId == accountQuery.Id);
|
||||||
|
|
||||||
|
// ── Flag filters ───────────────────────────────────────────────────────
|
||||||
|
if (mailSearchFilter.UnseenOnly)
|
||||||
|
query = query.Where(x => !x.IsSeen);
|
||||||
|
|
||||||
|
// ── Text filters ───────────────────────────────────────────────────────
|
||||||
|
if (!string.IsNullOrWhiteSpace(mailSearchFilter.SubjectContains))
|
||||||
|
query = query.Where(x => x.Subject.Contains(mailSearchFilter.SubjectContains));
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(mailSearchFilter.SenderContains))
|
||||||
|
query = query.Where(x => x.From.Contains(mailSearchFilter.SenderContains));
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(mailSearchFilter.BodyContains))
|
||||||
|
query = query.Where(x => x.TextBody.Contains(mailSearchFilter.BodyContains)
|
||||||
|
|| x.HtmlBody.Contains(mailSearchFilter.BodyContains));
|
||||||
|
|
||||||
|
// ── UID filter ─────────────────────────────────────────────────────────
|
||||||
|
if (mailSearchFilter.Uid is { } uid)
|
||||||
|
{
|
||||||
|
if (uid.Absolute.HasValue)
|
||||||
|
query = query.Where(x => x.Uid == uid.Absolute.Value);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (uid.Min.HasValue)
|
||||||
|
query = query.Where(x => x.Uid >= uid.Min.Value);
|
||||||
|
if (uid.Max.HasValue)
|
||||||
|
query = query.Where(x => x.Uid <= uid.Max.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Date filter ────────────────────────────────────────────────────────
|
||||||
|
if (mailSearchFilter.Date is { } date)
|
||||||
|
{
|
||||||
|
if (date.After.HasValue)
|
||||||
|
query = query.Where(x => x.Date >= date.After.Value);
|
||||||
|
if (date.Before.HasValue)
|
||||||
|
query = query.Where(x => x.Date <= date.Before.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ── Attachments ────────────────────────────────────────────────────────
|
||||||
|
if (mailSearchFilter.WithAttachments)
|
||||||
|
query = query.Include(x => x.Attachments);
|
||||||
|
|
||||||
|
// ── Sort ───────────────────────────────────────────────────────────────
|
||||||
|
query = mailSearchFilter.SortOrder == MailSortOrder.OldestFirst
|
||||||
|
? query.OrderBy(x => x.Date)
|
||||||
|
: query.OrderByDescending(x => x.Date);
|
||||||
|
|
||||||
|
// ── Limit ──────────────────────────────────────────────────────────────
|
||||||
|
if (mailSearchFilter.MaxCount.HasValue)
|
||||||
|
query = query.Take(mailSearchFilter.MaxCount.Value);
|
||||||
|
|
||||||
|
// ── RecipientContains: To/Cc are IEnumerable<string> (nvarchar(max)) ──
|
||||||
|
// EF Core cannot translate collection predicates on these columns to SQL.
|
||||||
|
// Materialization is deferred until after other DB-side filters narrow the set.
|
||||||
|
var results = await query.ToListAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (!string.IsNullOrWhiteSpace(mailSearchFilter.RecipientContains))
|
||||||
|
results = [.. results
|
||||||
|
.Where(x => x.To.Any(t => t.Contains(mailSearchFilter.RecipientContains, StringComparison.OrdinalIgnoreCase))
|
||||||
|
|| x.Cc.Any(c => c.Contains(mailSearchFilter.RecipientContains, StringComparison.OrdinalIgnoreCase)))];
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user