mirror of
https://github.com/collinbarrett/FilterLists.git
synced 2026-03-11 09:04:27 +00:00
refactor: swap MemoryCache for HybridCache
This commit is contained in:
parent
836ce6751b
commit
6132173c40
13 changed files with 285 additions and 343 deletions
|
|
@ -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<List<GetLanguages.Response>>()
|
||||
.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<List<GetLicenses.Response>>()
|
||||
.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<List<GetLists.Response>>()
|
||||
.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<Results<Ok<Response>, 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<Results<Ok<GetListDetails.Response>, 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<GetListDetails.Response>()
|
||||
.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<List<GetMaintainers.Response>>()
|
||||
.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<List<GetSoftware.Response>>()
|
||||
.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<List<GetSyntaxes.Response>>()
|
||||
.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<List<GetTags.Response>>()
|
||||
.WithOpenApi(operation => new OpenApiOperation(operation)
|
||||
{
|
||||
Tags = [TagsTag],
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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<QueryDbContext, IAsyncEnumerable<Response>> 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<List<Response>>;
|
||||
|
||||
public sealed record Request : IStreamRequest<Response>;
|
||||
|
||||
private sealed class Handler(QueryDbContext ctx, IMemoryCache cache) : IStreamRequestHandler<Request, Response>
|
||||
private sealed class Handler(QueryDbContext ctx, HybridCache cache) : IRequestHandler<Request, List<Response>>
|
||||
{
|
||||
public async IAsyncEnumerable<Response> Handle(Request request, [EnumeratorCancellation] CancellationToken ct)
|
||||
public async Task<List<Response>> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<QueryDbContext, IAsyncEnumerable<Response>> 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<List<Response>>;
|
||||
|
||||
public sealed record Request : IStreamRequest<Response>;
|
||||
|
||||
private sealed class Handler(QueryDbContext ctx, IMemoryCache cache) : IStreamRequestHandler<Request, Response>
|
||||
private sealed class Handler(QueryDbContext ctx, HybridCache cache) : IRequestHandler<Request, List<Response>>
|
||||
{
|
||||
public async IAsyncEnumerable<Response> Handle(Request request, [EnumeratorCancellation] CancellationToken ct)
|
||||
public async Task<List<Response>> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<QueryDbContext, int, Task<Response?>> 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<Response?>;
|
||||
|
||||
private sealed class Handler(QueryDbContext ctx, IMemoryCache cache) : IRequestHandler<Request, Response?>
|
||||
private sealed class Handler(QueryDbContext ctx, HybridCache cache) : IRequestHandler<Request, Response?>
|
||||
{
|
||||
public Task<Response?> Handle(Request request, CancellationToken _)
|
||||
public async Task<Response?> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<QueryDbContext, IAsyncEnumerable<Response>> 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<List<Response>>;
|
||||
|
||||
public sealed record Request : IStreamRequest<Response>;
|
||||
|
||||
private sealed class Handler(QueryDbContext ctx, IMemoryCache cache) : IStreamRequestHandler<Request, Response>
|
||||
private sealed class Handler(QueryDbContext ctx, HybridCache cache) : IRequestHandler<Request, List<Response>>
|
||||
{
|
||||
public async IAsyncEnumerable<Response> Handle(Request request, [EnumeratorCancellation] CancellationToken ct)
|
||||
public async Task<List<Response>> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<QueryDbContext, IAsyncEnumerable<Response>> 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<List<Response>>;
|
||||
|
||||
public sealed record Request : IStreamRequest<Response>;
|
||||
|
||||
private sealed class Handler(QueryDbContext ctx, IMemoryCache cache) : IStreamRequestHandler<Request, Response>
|
||||
private sealed class Handler(QueryDbContext ctx, HybridCache cache) : IRequestHandler<Request, List<Response>>
|
||||
{
|
||||
public async IAsyncEnumerable<Response> Handle(Request request, [EnumeratorCancellation] CancellationToken ct)
|
||||
public async Task<List<Response>> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<QueryDbContext, IAsyncEnumerable<Response>> 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<List<Response>>;
|
||||
|
||||
public sealed record Request : IStreamRequest<Response>;
|
||||
|
||||
private sealed class Handler(QueryDbContext ctx, IMemoryCache cache) : IStreamRequestHandler<Request, Response>
|
||||
private sealed class Handler(QueryDbContext ctx, HybridCache cache) : IRequestHandler<Request, List<Response>>
|
||||
{
|
||||
public async IAsyncEnumerable<Response> Handle(Request request, [EnumeratorCancellation] CancellationToken ct)
|
||||
public async Task<List<Response>> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<QueryDbContext, IAsyncEnumerable<Response>> 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<List<Response>>;
|
||||
|
||||
public sealed record Request : IStreamRequest<Response>;
|
||||
|
||||
private sealed class Handler(QueryDbContext ctx, IMemoryCache cache) : IStreamRequestHandler<Request, Response>
|
||||
private sealed class Handler(QueryDbContext ctx, HybridCache cache) : IRequestHandler<Request, List<Response>>
|
||||
{
|
||||
public async IAsyncEnumerable<Response> Handle(Request request, [EnumeratorCancellation] CancellationToken ct)
|
||||
public async Task<List<Response>> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<QueryDbContext, IAsyncEnumerable<Response>> 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<List<Response>>;
|
||||
|
||||
public sealed record Request : IStreamRequest<Response>;
|
||||
|
||||
private sealed class Handler(QueryDbContext ctx, IMemoryCache cache) : IStreamRequestHandler<Request, Response>
|
||||
private sealed class Handler(QueryDbContext ctx, HybridCache cache) : IRequestHandler<Request, List<Response>>
|
||||
{
|
||||
public async IAsyncEnumerable<Response> Handle(Request request, [EnumeratorCancellation] CancellationToken ct)
|
||||
public async Task<List<Response>> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -25,6 +25,6 @@ public static void AddInfrastructure(this IHostApplicationBuilder builder)
|
|||
StringComparison.OrdinalIgnoreCase)));
|
||||
|
||||
builder.Services.AddHostedService<MigrationService>();
|
||||
builder.Services.AddMemoryCache();
|
||||
builder.Services.AddHybridCache();
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Aspire.Microsoft.EntityFrameworkCore.SqlServer" Version="8.2.2"/>
|
||||
<PackageReference Include="Microsoft.Extensions.Caching.Hybrid" Version="9.5.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -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<TResponse> GetOrCreateAsyncEnumerable<TResponse>(
|
||||
this IMemoryCache cache,
|
||||
object key,
|
||||
ConfiguredCancelableAsyncEnumerable<TResponse> query,
|
||||
MemoryCacheEntryOptions? options = null) where TResponse : class
|
||||
{
|
||||
if (cache.TryGetValue(key, out List<TResponse>? cachedElements))
|
||||
{
|
||||
foreach (var element in cachedElements ?? []) yield return element;
|
||||
}
|
||||
else
|
||||
{
|
||||
var elementsToCache = new List<TResponse>();
|
||||
await foreach (var element in query)
|
||||
{
|
||||
elementsToCache.Add(element);
|
||||
yield return element;
|
||||
}
|
||||
|
||||
cache.Set(key, elementsToCache, options);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue