diff --git a/services/Directory/FilterLists.Directory.Api/Endpoints.cs b/services/Directory/FilterLists.Directory.Api/Endpoints.cs index ea6d71d76..7546ca592 100644 --- a/services/Directory/FilterLists.Directory.Api/Endpoints.cs +++ b/services/Directory/FilterLists.Directory.Api/Endpoints.cs @@ -4,7 +4,6 @@ using Microsoft.OpenApi.Any; using Microsoft.OpenApi.Models; using static FilterLists.Directory.Api.OpenApi.OpenApiTags; -using static FilterLists.Directory.Application.Queries.GetListDetails; namespace FilterLists.Directory.Api; @@ -14,8 +13,9 @@ internal static void MapEndpoints(this WebApplication app) { app.MapGet("/languages", (IMediator mediator, CancellationToken ct) => - mediator.CreateStream(new GetLanguages.Request(), ct) + mediator.Send(new GetLanguages.Request(), ct) ) + .Produces>() .WithOpenApi(operation => new OpenApiOperation(operation) { Tags = [LanguagesTag], @@ -25,8 +25,9 @@ internal static void MapEndpoints(this WebApplication app) app.MapGet("/licenses", (IMediator mediator, CancellationToken ct) => - mediator.CreateStream(new GetLicenses.Request(), ct) + mediator.Send(new GetLicenses.Request(), ct) ) + .Produces>() .WithOpenApi(operation => new OpenApiOperation(operation) { Tags = [LicensesTag], @@ -36,8 +37,9 @@ internal static void MapEndpoints(this WebApplication app) app.MapGet("/lists", (IMediator mediator, CancellationToken ct) => - mediator.CreateStream(new GetLists.Request(), ct) + mediator.Send(new GetLists.Request(), ct) ) + .Produces>() .WithOpenApi(operation => new OpenApiOperation(operation) { Tags = [FilterListsTag], @@ -45,14 +47,17 @@ internal static void MapEndpoints(this WebApplication app) OperationId = nameof(GetLists) }); - app.MapGet("/lists/{id:int}", async Task, NotFound>> - (int id, IMediator mediator, CancellationToken ct) => - { - var list = await mediator.Send(new Request(id), ct); - return list is not null - ? TypedResults.Ok(list) - : TypedResults.NotFound(); - }) + app.MapGet("/lists/{id:int}", + async Task, NotFound>> (int id, IMediator mediator, + CancellationToken ct) => + { + var list = await mediator.Send(new GetListDetails.Request(id), ct); + return list is not null + ? TypedResults.Ok(list) + : TypedResults.NotFound(); + }) + .Produces() + .Produces(StatusCodes.Status404NotFound) .WithOpenApi(operation => new OpenApiOperation(operation) { Tags = [FilterListsTag], @@ -73,8 +78,9 @@ internal static void MapEndpoints(this WebApplication app) app.MapGet("/maintainers", (IMediator mediator, CancellationToken ct) => - mediator.CreateStream(new GetMaintainers.Request(), ct) + mediator.Send(new GetMaintainers.Request(), ct) ) + .Produces>() .WithOpenApi(operation => new OpenApiOperation(operation) { Tags = [MaintainersTag], @@ -84,8 +90,9 @@ internal static void MapEndpoints(this WebApplication app) app.MapGet("/software", (IMediator mediator, CancellationToken ct) => - mediator.CreateStream(new GetSoftware.Request(), ct) + mediator.Send(new GetSoftware.Request(), ct) ) + .Produces>() .WithOpenApi(operation => new OpenApiOperation(operation) { Tags = [SoftwareTag], @@ -95,8 +102,9 @@ internal static void MapEndpoints(this WebApplication app) app.MapGet("/syntaxes", (IMediator mediator, CancellationToken ct) => - mediator.CreateStream(new GetSyntaxes.Request(), ct) + mediator.Send(new GetSyntaxes.Request(), ct) ) + .Produces>() .WithOpenApi(operation => new OpenApiOperation(operation) { Tags = [SyntaxesTag], @@ -106,8 +114,9 @@ internal static void MapEndpoints(this WebApplication app) app.MapGet("/tags", (IMediator mediator, CancellationToken ct) => - mediator.CreateStream(new GetTags.Request(), ct) + mediator.Send(new GetTags.Request(), ct) ) + .Produces>() .WithOpenApi(operation => new OpenApiOperation(operation) { Tags = [TagsTag], diff --git a/services/Directory/FilterLists.Directory.Application/ConfigurationExtensions.cs b/services/Directory/FilterLists.Directory.Application/ConfigurationExtensions.cs index 5a45794c0..bed445bc3 100644 --- a/services/Directory/FilterLists.Directory.Application/ConfigurationExtensions.cs +++ b/services/Directory/FilterLists.Directory.Application/ConfigurationExtensions.cs @@ -9,9 +9,6 @@ public static class ConfigurationExtensions public static void AddApplication(this IHostApplicationBuilder builder) { builder.AddInfrastructure(); - builder.Services.AddMediatR(cfg => - { - cfg.RegisterServicesFromAssembly(typeof(ConfigurationExtensions).Assembly); - }); + builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(ConfigurationExtensions).Assembly)); } } \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Application/Queries/GetLanguages.cs b/services/Directory/FilterLists.Directory.Application/Queries/GetLanguages.cs index f1c023556..9fcdf9e3d 100644 --- a/services/Directory/FilterLists.Directory.Application/Queries/GetLanguages.cs +++ b/services/Directory/FilterLists.Directory.Application/Queries/GetLanguages.cs @@ -1,41 +1,36 @@ -using System.Runtime.CompilerServices; -using FilterLists.Directory.Infrastructure; -using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; +using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; using MediatR; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Hybrid; namespace FilterLists.Directory.Application.Queries; public static class GetLanguages { - private static readonly Func> Query = - EF.CompileAsyncQuery((QueryDbContext ctx) => - ctx.Languages - .Where(l => l.FilterListLanguages.Any()) - .OrderBy(l => l.Iso6391) - .Select(l => new Response( - l.Id, - l.Iso6391, - l.Name, - l.FilterListLanguages - .OrderBy(fl => fl.FilterListId) - .Select(fl => fl.FilterListId) - )) - .TagWith(nameof(GetLanguages)) - ); + public sealed record Request : IRequest>; - public sealed record Request : IStreamRequest; - - private sealed class Handler(QueryDbContext ctx, IMemoryCache cache) : IStreamRequestHandler + private sealed class Handler(QueryDbContext ctx, HybridCache cache) : IRequestHandler> { - public async IAsyncEnumerable Handle(Request request, [EnumeratorCancellation] CancellationToken ct) + public async Task> Handle(Request request, CancellationToken ct) { - await foreach (var language in cache.GetOrCreateAsyncEnumerable( - nameof(GetLanguages), - Query(ctx).WithCancellation(ct)) - .WithCancellation(ct)) - yield return language; + const string key = nameof(GetLanguages); + return await cache.GetOrCreateAsync( + key, + async cancel => + await ctx.Languages + .Where(l => l.FilterListLanguages.Any()) + .OrderBy(l => l.Iso6391) + .Select(l => new Response( + l.Id, + l.Iso6391, + l.Name, + l.FilterListLanguages + .OrderBy(fl => fl.FilterListId) + .Select(fl => fl.FilterListId) + )) + .TagWith(key) + .ToListAsync(cancel), + cancellationToken: ct); } } diff --git a/services/Directory/FilterLists.Directory.Application/Queries/GetLicenses.cs b/services/Directory/FilterLists.Directory.Application/Queries/GetLicenses.cs index 09184c3c3..140417b19 100644 --- a/services/Directory/FilterLists.Directory.Application/Queries/GetLicenses.cs +++ b/services/Directory/FilterLists.Directory.Application/Queries/GetLicenses.cs @@ -1,44 +1,39 @@ -using System.Runtime.CompilerServices; -using FilterLists.Directory.Infrastructure; -using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; +using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; using MediatR; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Hybrid; namespace FilterLists.Directory.Application.Queries; public static class GetLicenses { - private static readonly Func> Query = - EF.CompileAsyncQuery((QueryDbContext ctx) => - ctx.Licenses - .Where(l => l.FilterLists.Any()) - .OrderBy(l => l.Id) - .Select(l => new Response( - l.Id, - l.Name, - l.Url, - l.PermitsModification, - l.PermitsDistribution, - l.PermitsCommercialUse, - l.FilterLists - .OrderBy(f => f.Id) - .Select(f => f.Id) - )) - .TagWith(nameof(GetLicenses)) - ); + public sealed record Request : IRequest>; - public sealed record Request : IStreamRequest; - - private sealed class Handler(QueryDbContext ctx, IMemoryCache cache) : IStreamRequestHandler + private sealed class Handler(QueryDbContext ctx, HybridCache cache) : IRequestHandler> { - public async IAsyncEnumerable Handle(Request request, [EnumeratorCancellation] CancellationToken ct) + public async Task> Handle(Request request, CancellationToken ct) { - await foreach (var license in cache.GetOrCreateAsyncEnumerable( - nameof(GetLicenses), - Query(ctx).WithCancellation(ct)) - .WithCancellation(ct)) - yield return license; + const string key = nameof(GetLicenses); + return await cache.GetOrCreateAsync( + key, + async cancel => + await ctx.Licenses + .Where(l => l.FilterLists.Any()) + .OrderBy(l => l.Id) + .Select(l => new Response( + l.Id, + l.Name, + l.Url, + l.PermitsModification, + l.PermitsDistribution, + l.PermitsCommercialUse, + l.FilterLists + .OrderBy(f => f.Id) + .Select(f => f.Id) + )) + .TagWith(key) + .ToListAsync(cancel), + cancellationToken: ct); } } diff --git a/services/Directory/FilterLists.Directory.Application/Queries/GetListDetails.cs b/services/Directory/FilterLists.Directory.Application/Queries/GetListDetails.cs index 4643e36a6..adb9ea936 100644 --- a/services/Directory/FilterLists.Directory.Application/Queries/GetListDetails.cs +++ b/services/Directory/FilterLists.Directory.Application/Queries/GetListDetails.cs @@ -1,80 +1,81 @@ using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; using MediatR; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Hybrid; namespace FilterLists.Directory.Application.Queries; public static class GetListDetails { - private static readonly Func> Query = - EF.CompileAsyncQuery((QueryDbContext ctx, int id) => - ctx.FilterLists - .Select(f => new Response - { - Id = f.Id, - Name = f.Name, - Description = f.Description, - LicenseId = f.LicenseId, - SyntaxIds = f.FilterListSyntaxes - .OrderBy(fs => fs.SyntaxId) - .Select(fs => fs.SyntaxId), - LanguageIds = f.FilterListLanguages - .OrderBy(fl => fl.LanguageId) - .Select(fl => fl.LanguageId), - TagIds = f.FilterListTags - .OrderBy(ft => ft.TagId) - .Select(ft => ft.TagId), - ViewUrls = f.ViewUrls - .OrderBy(u => u.SegmentNumber) - .ThenBy(u => u.Primariness) - .Select(u => new ViewUrlResponse - ( - u.SegmentNumber, - u.Primariness, - u.Url - )), - HomeUrl = f.HomeUrl, - OnionUrl = f.OnionUrl, - PolicyUrl = f.PolicyUrl, - SubmissionUrl = f.SubmissionUrl, - IssuesUrl = f.IssuesUrl, - ForumUrl = f.ForumUrl, - ChatUrl = f.ChatUrl, - EmailAddress = f.EmailAddress, - DonateUrl = f.DonateUrl, - MaintainerIds = f.FilterListMaintainers - .OrderBy(fm => fm.MaintainerId) - .Select(fm => fm.MaintainerId), - UpstreamFilterListIds = f.UpstreamFilterLists - .OrderBy(ff => ff.UpstreamFilterListId) - .Select(ff => ff.UpstreamFilterListId), - ForkFilterListIds = f.ForkFilterLists - .OrderBy(ff => ff.ForkFilterListId) - .Select(ff => ff.ForkFilterListId), - IncludedInFilterListIds = f.IncludedInFilterLists - .OrderBy(fm => fm.IncludedInFilterListId) - .Select(fm => fm.IncludedInFilterListId), - IncludesFilterListIds = f.IncludesFilterLists - .OrderBy(fm => fm.IncludesFilterListId) - .Select(fm => fm.IncludesFilterListId), - DependencyFilterListIds = f.DependencyFilterLists - .OrderBy(fd => fd.DependencyFilterListId) - .Select(fd => fd.DependencyFilterListId), - DependentFilterListIds = f.DependentFilterLists - .OrderBy(fd => fd.DependentFilterListId) - .Select(fd => fd.DependentFilterListId) - }) - .TagWith(nameof(GetListDetails)) - .SingleOrDefault(f => f.Id == id)); - public sealed record Request(int Id) : IRequest; - private sealed class Handler(QueryDbContext ctx, IMemoryCache cache) : IRequestHandler + private sealed class Handler(QueryDbContext ctx, HybridCache cache) : IRequestHandler { - public Task Handle(Request request, CancellationToken _) + public async Task Handle(Request request, CancellationToken ct) { - return cache.GetOrCreateAsync($"{nameof(GetListDetails)}_{request.Id}", _ => Query(ctx, request.Id)); + var key = $"{nameof(GetListDetails)}_{request.Id}"; + return await cache.GetOrCreateAsync( + key, + async cancel => + await ctx.FilterLists + .Select(f => new Response + { + Id = f.Id, + Name = f.Name, + Description = f.Description, + LicenseId = f.LicenseId, + SyntaxIds = f.FilterListSyntaxes + .OrderBy(fs => fs.SyntaxId) + .Select(fs => fs.SyntaxId), + LanguageIds = f.FilterListLanguages + .OrderBy(fl => fl.LanguageId) + .Select(fl => fl.LanguageId), + TagIds = f.FilterListTags + .OrderBy(ft => ft.TagId) + .Select(ft => ft.TagId), + ViewUrls = f.ViewUrls + .OrderBy(u => u.SegmentNumber) + .ThenBy(u => u.Primariness) + .Select(u => new ViewUrlResponse + ( + u.SegmentNumber, + u.Primariness, + u.Url + )), + HomeUrl = f.HomeUrl, + OnionUrl = f.OnionUrl, + PolicyUrl = f.PolicyUrl, + SubmissionUrl = f.SubmissionUrl, + IssuesUrl = f.IssuesUrl, + ForumUrl = f.ForumUrl, + ChatUrl = f.ChatUrl, + EmailAddress = f.EmailAddress, + DonateUrl = f.DonateUrl, + MaintainerIds = f.FilterListMaintainers + .OrderBy(fm => fm.MaintainerId) + .Select(fm => fm.MaintainerId), + UpstreamFilterListIds = f.UpstreamFilterLists + .OrderBy(ff => ff.UpstreamFilterListId) + .Select(ff => ff.UpstreamFilterListId), + ForkFilterListIds = f.ForkFilterLists + .OrderBy(ff => ff.ForkFilterListId) + .Select(ff => ff.ForkFilterListId), + IncludedInFilterListIds = f.IncludedInFilterLists + .OrderBy(fm => fm.IncludedInFilterListId) + .Select(fm => fm.IncludedInFilterListId), + IncludesFilterListIds = f.IncludesFilterLists + .OrderBy(fm => fm.IncludesFilterListId) + .Select(fm => fm.IncludesFilterListId), + DependencyFilterListIds = f.DependencyFilterLists + .OrderBy(fd => fd.DependencyFilterListId) + .Select(fd => fd.DependencyFilterListId), + DependentFilterListIds = f.DependentFilterLists + .OrderBy(fd => fd.DependentFilterListId) + .Select(fd => fd.DependentFilterListId) + }) + .TagWith(key) + .SingleOrDefaultAsync(l => l.Id == request.Id, cancel), + cancellationToken: ct); } } diff --git a/services/Directory/FilterLists.Directory.Application/Queries/GetLists.cs b/services/Directory/FilterLists.Directory.Application/Queries/GetLists.cs index d2187832e..80a57d0ef 100644 --- a/services/Directory/FilterLists.Directory.Application/Queries/GetLists.cs +++ b/services/Directory/FilterLists.Directory.Application/Queries/GetLists.cs @@ -1,56 +1,51 @@ -using System.Runtime.CompilerServices; -using FilterLists.Directory.Infrastructure; -using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; +using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; using MediatR; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Hybrid; namespace FilterLists.Directory.Application.Queries; public static class GetLists { - private static readonly Func> Query = - EF.CompileAsyncQuery((QueryDbContext ctx) => - ctx.FilterLists - .OrderBy(f => f.Id) - .Select(f => new Response - ( - f.Id, - f.Name, - f.Description, - f.LicenseId, - f.FilterListSyntaxes - .OrderBy(fs => fs.SyntaxId) - .Select(fs => fs.SyntaxId), - f.FilterListLanguages - .OrderBy(fl => fl.LanguageId) - .Select(fl => fl.LanguageId), - f.FilterListTags - .OrderBy(ft => ft.TagId) - .Select(ft => ft.TagId), - f.ViewUrls - .OrderBy(u => u.SegmentNumber) - .ThenBy(u => u.Primariness) - .Select(u => u.Url) - .FirstOrDefault(), - f.FilterListMaintainers - .OrderBy(fm => fm.MaintainerId) - .Select(fm => fm.MaintainerId) - )) - .TagWith(nameof(GetLists)) - ); + public sealed record Request : IRequest>; - public sealed record Request : IStreamRequest; - - private sealed class Handler(QueryDbContext ctx, IMemoryCache cache) : IStreamRequestHandler + private sealed class Handler(QueryDbContext ctx, HybridCache cache) : IRequestHandler> { - public async IAsyncEnumerable Handle(Request request, [EnumeratorCancellation] CancellationToken ct) + public async Task> Handle(Request request, CancellationToken ct) { - await foreach (var list in cache.GetOrCreateAsyncEnumerable( - nameof(GetLists), - Query(ctx).WithCancellation(ct)) - .WithCancellation(ct)) - yield return list; + const string key = nameof(GetLists); + return await cache.GetOrCreateAsync( + key, + async cancel => + await ctx.FilterLists + .OrderBy(f => f.Id) + .Select(f => new Response + ( + f.Id, + f.Name, + f.Description, + f.LicenseId, + f.FilterListSyntaxes + .OrderBy(fs => fs.SyntaxId) + .Select(fs => fs.SyntaxId), + f.FilterListLanguages + .OrderBy(fl => fl.LanguageId) + .Select(fl => fl.LanguageId), + f.FilterListTags + .OrderBy(ft => ft.TagId) + .Select(ft => ft.TagId), + f.ViewUrls + .OrderBy(u => u.SegmentNumber) + .ThenBy(u => u.Primariness) + .Select(u => u.Url) + .FirstOrDefault(), + f.FilterListMaintainers + .OrderBy(fm => fm.MaintainerId) + .Select(fm => fm.MaintainerId) + )) + .TagWith(key) + .ToListAsync(cancel), + cancellationToken: ct); } } diff --git a/services/Directory/FilterLists.Directory.Application/Queries/GetMaintainers.cs b/services/Directory/FilterLists.Directory.Application/Queries/GetMaintainers.cs index 9cb05331e..505c9ad7b 100644 --- a/services/Directory/FilterLists.Directory.Application/Queries/GetMaintainers.cs +++ b/services/Directory/FilterLists.Directory.Application/Queries/GetMaintainers.cs @@ -1,43 +1,38 @@ -using System.Runtime.CompilerServices; -using FilterLists.Directory.Infrastructure; -using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; +using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; using MediatR; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Hybrid; namespace FilterLists.Directory.Application.Queries; public static class GetMaintainers { - private static readonly Func> Query = - EF.CompileAsyncQuery((QueryDbContext ctx) => - ctx.Maintainers - .Where(m => m.FilterListMaintainers.Any()) - .OrderBy(m => m.Id) - .Select(m => new Response( - m.Id, - m.Name, - m.Url, - m.EmailAddress, - m.TwitterHandle, - m.FilterListMaintainers - .OrderBy(fm => fm.FilterListId) - .Select(fm => fm.FilterListId) - )) - .TagWith(nameof(GetMaintainers)) - ); + public sealed record Request : IRequest>; - public sealed record Request : IStreamRequest; - - private sealed class Handler(QueryDbContext ctx, IMemoryCache cache) : IStreamRequestHandler + private sealed class Handler(QueryDbContext ctx, HybridCache cache) : IRequestHandler> { - public async IAsyncEnumerable Handle(Request request, [EnumeratorCancellation] CancellationToken ct) + public async Task> Handle(Request request, CancellationToken ct) { - await foreach (var maintainer in cache.GetOrCreateAsyncEnumerable( - nameof(GetMaintainers), - Query(ctx).WithCancellation(ct)) - .WithCancellation(ct)) - yield return maintainer; + const string key = nameof(GetMaintainers); + return await cache.GetOrCreateAsync( + key, + async cancel => + await ctx.Maintainers + .Where(m => m.FilterListMaintainers.Any()) + .OrderBy(m => m.Id) + .Select(m => new Response( + m.Id, + m.Name, + m.Url, + m.EmailAddress, + m.TwitterHandle, + m.FilterListMaintainers + .OrderBy(fm => fm.FilterListId) + .Select(fm => fm.FilterListId) + )) + .TagWith(key) + .ToListAsync(cancel), + cancellationToken: ct); } } diff --git a/services/Directory/FilterLists.Directory.Application/Queries/GetSoftware.cs b/services/Directory/FilterLists.Directory.Application/Queries/GetSoftware.cs index 2eda35c7b..733fd7f3b 100644 --- a/services/Directory/FilterLists.Directory.Application/Queries/GetSoftware.cs +++ b/services/Directory/FilterLists.Directory.Application/Queries/GetSoftware.cs @@ -1,46 +1,40 @@ -using System.Runtime.CompilerServices; -using FilterLists.Directory.Infrastructure; -using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; +using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; using MediatR; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Hybrid; namespace FilterLists.Directory.Application.Queries; public static class GetSoftware { - private static readonly Func> Query = - EF.CompileAsyncQuery((QueryDbContext ctx) => - ctx.Software - .Where(s => s.SoftwareSyntaxes - .Any(ss => ss.Syntax.FilterListSyntaxes.Any())) - .OrderBy(s => s.Id) - .Select(s => new Response - ( - s.Id, - s.Name, - s.Description, - s.HomeUrl, - s.DownloadUrl, - s.SupportsAbpUrlScheme, - s.SoftwareSyntaxes - .OrderBy(ss => ss.SyntaxId) - .Select(ss => ss.SyntaxId) - )) - .TagWith(nameof(GetSoftware)) - ); + public sealed record Request : IRequest>; - public sealed record Request : IStreamRequest; - - private sealed class Handler(QueryDbContext ctx, IMemoryCache cache) : IStreamRequestHandler + private sealed class Handler(QueryDbContext ctx, HybridCache cache) : IRequestHandler> { - public async IAsyncEnumerable Handle(Request request, [EnumeratorCancellation] CancellationToken ct) + public async Task> Handle(Request request, CancellationToken ct) { - await foreach (var software in cache.GetOrCreateAsyncEnumerable( - nameof(GetSoftware), - Query(ctx).WithCancellation(ct)) - .WithCancellation(ct)) - yield return software; + const string key = nameof(GetSoftware); + return await cache.GetOrCreateAsync( + key, + async cancel => + await ctx.Software + .Where(s => s.SoftwareSyntaxes.Any(ss => ss.Syntax.FilterListSyntaxes.Any())) + .OrderBy(s => s.Id) + .Select(s => new Response + ( + s.Id, + s.Name, + s.Description, + s.HomeUrl, + s.DownloadUrl, + s.SupportsAbpUrlScheme, + s.SoftwareSyntaxes + .OrderBy(ss => ss.SyntaxId) + .Select(ss => ss.SyntaxId) + )) + .TagWith(key) + .ToListAsync(cancel), + cancellationToken: ct); } } diff --git a/services/Directory/FilterLists.Directory.Application/Queries/GetSyntaxes.cs b/services/Directory/FilterLists.Directory.Application/Queries/GetSyntaxes.cs index 77bc5055e..6425833cf 100644 --- a/services/Directory/FilterLists.Directory.Application/Queries/GetSyntaxes.cs +++ b/services/Directory/FilterLists.Directory.Application/Queries/GetSyntaxes.cs @@ -1,46 +1,41 @@ -using System.Runtime.CompilerServices; -using FilterLists.Directory.Infrastructure; -using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; +using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; using MediatR; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Hybrid; namespace FilterLists.Directory.Application.Queries; public static class GetSyntaxes { - private static readonly Func> Query = - EF.CompileAsyncQuery((QueryDbContext ctx) => - ctx.Syntaxes - .Where(s => s.FilterListSyntaxes.Any()) - .OrderBy(s => s.Id) - .Select(s => new Response - ( - s.Id, - s.Name, - s.Description, - s.Url, - s.FilterListSyntaxes - .OrderBy(fs => fs.FilterListId) - .Select(fs => fs.FilterListId), - s.SoftwareSyntaxes - .OrderBy(ss => ss.SoftwareId) - .Select(ss => ss.SoftwareId) - )) - .TagWith(nameof(GetSyntaxes)) - ); + public sealed record Request : IRequest>; - public sealed record Request : IStreamRequest; - - private sealed class Handler(QueryDbContext ctx, IMemoryCache cache) : IStreamRequestHandler + private sealed class Handler(QueryDbContext ctx, HybridCache cache) : IRequestHandler> { - public async IAsyncEnumerable Handle(Request request, [EnumeratorCancellation] CancellationToken ct) + public async Task> Handle(Request request, CancellationToken ct) { - await foreach (var syntax in cache.GetOrCreateAsyncEnumerable( - nameof(GetSyntaxes), - Query(ctx).WithCancellation(ct)) - .WithCancellation(ct)) - yield return syntax; + const string key = nameof(GetSyntaxes); + return await cache.GetOrCreateAsync( + key, + async cancel => + await ctx.Syntaxes + .Where(s => s.FilterListSyntaxes.Any()) + .OrderBy(s => s.Id) + .Select(s => new Response + ( + s.Id, + s.Name, + s.Description, + s.Url, + s.FilterListSyntaxes + .OrderBy(fs => fs.FilterListId) + .Select(fs => fs.FilterListId), + s.SoftwareSyntaxes + .OrderBy(ss => ss.SoftwareId) + .Select(ss => ss.SoftwareId) + )) + .TagWith(key) + .ToListAsync(cancel), + cancellationToken: ct); } } diff --git a/services/Directory/FilterLists.Directory.Application/Queries/GetTags.cs b/services/Directory/FilterLists.Directory.Application/Queries/GetTags.cs index 97163126f..1428caaa3 100644 --- a/services/Directory/FilterLists.Directory.Application/Queries/GetTags.cs +++ b/services/Directory/FilterLists.Directory.Application/Queries/GetTags.cs @@ -1,42 +1,37 @@ -using System.Runtime.CompilerServices; -using FilterLists.Directory.Infrastructure; -using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; +using FilterLists.Directory.Infrastructure.Persistence.Queries.Context; using MediatR; using Microsoft.EntityFrameworkCore; -using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Caching.Hybrid; namespace FilterLists.Directory.Application.Queries; public static class GetTags { - private static readonly Func> Query = - EF.CompileAsyncQuery((QueryDbContext ctx) => - ctx.Tags - .Where(t => t.FilterListTags.Any()) - .OrderBy(t => t.Id) - .Select(t => new Response - ( - t.Id, - t.Name, - t.Description, - t.FilterListTags - .OrderBy(flt => flt.FilterListId) - .Select(flt => flt.FilterListId) - )) - .TagWith(nameof(GetTags)) - ); + public sealed record Request : IRequest>; - public sealed record Request : IStreamRequest; - - private sealed class Handler(QueryDbContext ctx, IMemoryCache cache) : IStreamRequestHandler + private sealed class Handler(QueryDbContext ctx, HybridCache cache) : IRequestHandler> { - public async IAsyncEnumerable Handle(Request request, [EnumeratorCancellation] CancellationToken ct) + public async Task> Handle(Request request, CancellationToken ct) { - await foreach (var tag in cache.GetOrCreateAsyncEnumerable( - nameof(GetTags), - Query(ctx).WithCancellation(ct)) - .WithCancellation(ct)) - yield return tag; + const string key = nameof(GetTags); + return await cache.GetOrCreateAsync( + key, + async cancel => + await ctx.Tags + .Where(t => t.FilterListTags.Any()) + .OrderBy(t => t.Id) + .Select(t => new Response + ( + t.Id, + t.Name, + t.Description, + t.FilterListTags + .OrderBy(flt => flt.FilterListId) + .Select(flt => flt.FilterListId) + )) + .TagWith(key) + .ToListAsync(cancel), + cancellationToken: ct); } } diff --git a/services/Directory/FilterLists.Directory.Infrastructure/ConfigurationExtensions.cs b/services/Directory/FilterLists.Directory.Infrastructure/ConfigurationExtensions.cs index 11d8cd391..95b502f16 100644 --- a/services/Directory/FilterLists.Directory.Infrastructure/ConfigurationExtensions.cs +++ b/services/Directory/FilterLists.Directory.Infrastructure/ConfigurationExtensions.cs @@ -25,6 +25,6 @@ public static void AddInfrastructure(this IHostApplicationBuilder builder) StringComparison.OrdinalIgnoreCase))); builder.Services.AddHostedService(); - builder.Services.AddMemoryCache(); + builder.Services.AddHybridCache(); } } \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/FilterLists.Directory.Infrastructure.csproj b/services/Directory/FilterLists.Directory.Infrastructure/FilterLists.Directory.Infrastructure.csproj index 11b3f3692..16cab0f46 100644 --- a/services/Directory/FilterLists.Directory.Infrastructure/FilterLists.Directory.Infrastructure.csproj +++ b/services/Directory/FilterLists.Directory.Infrastructure/FilterLists.Directory.Infrastructure.csproj @@ -6,6 +6,7 @@ + \ No newline at end of file diff --git a/services/Directory/FilterLists.Directory.Infrastructure/MemoryCacheExtension.cs b/services/Directory/FilterLists.Directory.Infrastructure/MemoryCacheExtension.cs deleted file mode 100644 index 4527aff1c..000000000 --- a/services/Directory/FilterLists.Directory.Infrastructure/MemoryCacheExtension.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Runtime.CompilerServices; -using Microsoft.Extensions.Caching.Memory; - -namespace FilterLists.Directory.Infrastructure; - -public static class MemoryCacheExtension -{ - public static async IAsyncEnumerable GetOrCreateAsyncEnumerable( - this IMemoryCache cache, - object key, - ConfiguredCancelableAsyncEnumerable query, - MemoryCacheEntryOptions? options = null) where TResponse : class - { - if (cache.TryGetValue(key, out List? cachedElements)) - { - foreach (var element in cachedElements ?? []) yield return element; - } - else - { - var elementsToCache = new List(); - await foreach (var element in query) - { - elementsToCache.Add(element); - yield return element; - } - - cache.Set(key, elementsToCache, options); - } - } -} \ No newline at end of file