From 045ce709aa4bbf03d1769f43d816c002f6a7ebbd Mon Sep 17 00:00:00 2001 From: Laura Hausmann Date: Fri, 31 Jan 2025 16:56:11 +0100 Subject: [PATCH] [backend] Switch to Iceshrimp.EntityFrameworkCore.Extensions --- .../Controllers/Web/AdminController.cs | 1 + .../Core/Extensions/ExpressionExtensions.cs | 51 --------- .../Core/Extensions/QueryableExtensions.cs | 107 +----------------- .../Core/Extensions/QueryableFtsExtensions.cs | 1 + .../Core/Queues/BackgroundTaskQueue.cs | 1 + .../Services/StorageMaintenanceService.cs | 1 + .../Core/Services/UserService.cs | 1 + .../Core/Tasks/MediaCleanupTask.cs | 1 + .../Core/Tasks/TimelineHeuristicTask.cs | 1 + Iceshrimp.Backend/Iceshrimp.Backend.csproj | 1 + 10 files changed, 9 insertions(+), 157 deletions(-) delete mode 100644 Iceshrimp.Backend/Core/Extensions/ExpressionExtensions.cs diff --git a/Iceshrimp.Backend/Controllers/Web/AdminController.cs b/Iceshrimp.Backend/Controllers/Web/AdminController.cs index be6fc90d..b0498a8e 100644 --- a/Iceshrimp.Backend/Controllers/Web/AdminController.cs +++ b/Iceshrimp.Backend/Controllers/Web/AdminController.cs @@ -14,6 +14,7 @@ using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Services; using Iceshrimp.Backend.Core.Tasks; +using Iceshrimp.EntityFrameworkCore.Extensions; using Iceshrimp.Shared.Configuration; using Iceshrimp.Shared.Schemas.Web; using Microsoft.AspNetCore.Mvc; diff --git a/Iceshrimp.Backend/Core/Extensions/ExpressionExtensions.cs b/Iceshrimp.Backend/Core/Extensions/ExpressionExtensions.cs deleted file mode 100644 index d78c484a..00000000 --- a/Iceshrimp.Backend/Core/Extensions/ExpressionExtensions.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Linq.Expressions; - -namespace Iceshrimp.Backend.Core.Extensions; - -public static class ExpressionExtensions -{ - public static Expression> True() => f => true; - public static Expression> False() => f => false; - - public static Expression> Or( - this Expression> expr1, Expression> expr2 - ) - { - var invokedExpr = Expression.Invoke(expr2, expr1.Parameters); - return Expression.Lambda>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters); - } - - public static Expression> And( - this Expression> expr1, Expression> expr2 - ) - { - var invokedExpr = Expression.Invoke(expr2, expr1.Parameters); - return Expression.Lambda>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters); - } - - public static Expression> Compose( - this Expression> first, - Expression> second - ) - { - var param = Expression.Parameter(typeof(TFirstParam), "param"); - - var newFirst = first.Body.Replace(first.Parameters[0], param); - var newSecond = second.Body.Replace(second.Parameters[0], newFirst); - - return Expression.Lambda>(newSecond, param); - } - - private static Expression Replace(this Expression expression, Expression searchEx, Expression replaceEx) - { - return new ReplaceVisitor(searchEx, replaceEx).Visit(expression) ?? throw new NullReferenceException(); - } - - private class ReplaceVisitor(Expression from, Expression to) : ExpressionVisitor - { - public override Expression? Visit(Expression? node) - { - return node == from ? to : base.Visit(node); - } - } -} \ No newline at end of file diff --git a/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs b/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs index 3a251739..99c07443 100644 --- a/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/QueryableExtensions.cs @@ -9,6 +9,7 @@ using Iceshrimp.Backend.Controllers.Shared.Schemas; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Middleware; +using Iceshrimp.EntityFrameworkCore.Extensions; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; @@ -16,112 +17,6 @@ namespace Iceshrimp.Backend.Core.Extensions; public static class QueryableExtensions { - /// - /// This helper method allows consumers to obtain the performance & memory footprint benefits of chunked DB transactions, - /// while not requiring them to work with chunks instead of a regular enumerator. - /// - /// - /// Make sure to call .OrderBy() on the query, otherwise the results will be unpredictable. - /// Furthermore, this method is unsuitable for cases where the consumer removes elements from the original collection. - /// - /// - /// The result set as an IAsyncEnumerable. Makes one DB roundtrip at the start of each chunk. - /// Successive items in the chunk are yielded instantaneously. - /// - [SuppressMessage("ReSharper", "InconsistentNaming")] - public static async IAsyncEnumerable AsChunkedAsyncEnumerable(this IQueryable query, int chunkSize) - { - var offset = 0; - while (true) - { - var res = await query.Skip(offset).Take(chunkSize).ToArrayAsync(); - if (res.Length == 0) break; - foreach (var item in res) yield return item; - if (res.Length < chunkSize) break; - offset += chunkSize; - } - } - - /// - /// - /// This overload requires you to pass a predicate to the identifier. - /// .OrderBy() is appended to the query. - /// Set the parameter to append things to the query after pagination, for cases where query translation would fail otherwise. - /// - [SuppressMessage("ReSharper", "InconsistentNaming")] - public static async IAsyncEnumerable AsChunkedAsyncEnumerable( - this IQueryable query, int chunkSize, Expression> idPredicate, - Func, IQueryable>? hook = null - ) - { - var pred = idPredicate.Compile(); - query = query.OrderBy(idPredicate); - - string? last = null; - while (true) - { - // ReSharper disable once AccessToModifiedClosure - var final = last is not null ? query.Where(idPredicate.Compose(p => p.IsGreaterThan(last))) : query; - if (hook != null) - final = hook(final); - var res = await final.Take(chunkSize).ToArrayAsync(); - if (res.Length == 0) break; - foreach (var item in res) yield return item; - if (res.Length < chunkSize) break; - last = pred.Invoke(res.Last()); - } - } - - /// - [SuppressMessage("ReSharper", "InconsistentNaming")] - public static async IAsyncEnumerable AsChunkedAsyncEnumerable( - this IQueryable query, int chunkSize, Expression> idPredicate, - Func, IQueryable>? hook = null - ) - { - var pred = idPredicate.Compile(); - query = query.OrderBy(idPredicate); - - Guid? last = null; - while (true) - { - // ReSharper disable once AccessToModifiedClosure - var final = last is not null ? query.Where(idPredicate.Compose(p => p > last)) : query; - if (hook != null) - final = hook(final); - var res = await final.Take(chunkSize).ToArrayAsync(); - if (res.Length == 0) break; - foreach (var item in res) yield return item; - if (res.Length < chunkSize) break; - last = pred.Invoke(res.Last()); - } - } - - /// - [SuppressMessage("ReSharper", "InconsistentNaming")] - public static async IAsyncEnumerable AsChunkedAsyncEnumerable( - this IQueryable query, int chunkSize, Expression> idPredicate, - Func, IQueryable>? hook = null - ) - { - var pred = idPredicate.Compile(); - query = query.OrderBy(idPredicate); - - int? last = null; - while (true) - { - // ReSharper disable once AccessToModifiedClosure - var final = last is not null ? query.Where(idPredicate.Compose(p => p > last)) : query; - if (hook != null) - final = hook(final); - var res = await final.Take(chunkSize).ToArrayAsync(); - if (res.Length == 0) break; - foreach (var item in res) yield return item; - if (res.Length < chunkSize) break; - last = pred.Invoke(res.Last()); - } - } - public static IQueryable Paginate( this IQueryable query, MastodonPaginationQuery pq, diff --git a/Iceshrimp.Backend/Core/Extensions/QueryableFtsExtensions.cs b/Iceshrimp.Backend/Core/Extensions/QueryableFtsExtensions.cs index 151d780e..abd63c5a 100644 --- a/Iceshrimp.Backend/Core/Extensions/QueryableFtsExtensions.cs +++ b/Iceshrimp.Backend/Core/Extensions/QueryableFtsExtensions.cs @@ -5,6 +5,7 @@ using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Helpers; +using Iceshrimp.EntityFrameworkCore.Extensions; using Iceshrimp.Parsing; using Microsoft.EntityFrameworkCore; diff --git a/Iceshrimp.Backend/Core/Queues/BackgroundTaskQueue.cs b/Iceshrimp.Backend/Core/Queues/BackgroundTaskQueue.cs index 5ed08319..671c632e 100644 --- a/Iceshrimp.Backend/Core/Queues/BackgroundTaskQueue.cs +++ b/Iceshrimp.Backend/Core/Queues/BackgroundTaskQueue.cs @@ -5,6 +5,7 @@ using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Extensions; using Iceshrimp.Backend.Core.Services; +using Iceshrimp.EntityFrameworkCore.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; using J = System.Text.Json.Serialization.JsonPropertyNameAttribute; diff --git a/Iceshrimp.Backend/Core/Services/StorageMaintenanceService.cs b/Iceshrimp.Backend/Core/Services/StorageMaintenanceService.cs index a874f76e..9da66364 100644 --- a/Iceshrimp.Backend/Core/Services/StorageMaintenanceService.cs +++ b/Iceshrimp.Backend/Core/Services/StorageMaintenanceService.cs @@ -4,6 +4,7 @@ using Iceshrimp.Backend.Core.Configuration; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Database.Tables; using Iceshrimp.Backend.Core.Extensions; +using Iceshrimp.EntityFrameworkCore.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; diff --git a/Iceshrimp.Backend/Core/Services/UserService.cs b/Iceshrimp.Backend/Core/Services/UserService.cs index 9469f3a4..f550ddab 100644 --- a/Iceshrimp.Backend/Core/Services/UserService.cs +++ b/Iceshrimp.Backend/Core/Services/UserService.cs @@ -14,6 +14,7 @@ using Iceshrimp.Backend.Core.Helpers; using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion; using Iceshrimp.Backend.Core.Middleware; using Iceshrimp.Backend.Core.Queues; +using Iceshrimp.EntityFrameworkCore.Extensions; using Iceshrimp.MfmSharp; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; diff --git a/Iceshrimp.Backend/Core/Tasks/MediaCleanupTask.cs b/Iceshrimp.Backend/Core/Tasks/MediaCleanupTask.cs index d6475b33..fddc3aaf 100644 --- a/Iceshrimp.Backend/Core/Tasks/MediaCleanupTask.cs +++ b/Iceshrimp.Backend/Core/Tasks/MediaCleanupTask.cs @@ -4,6 +4,7 @@ using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Extensions; using Iceshrimp.Backend.Core.Queues; using Iceshrimp.Backend.Core.Services; +using Iceshrimp.EntityFrameworkCore.Extensions; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Options; diff --git a/Iceshrimp.Backend/Core/Tasks/TimelineHeuristicTask.cs b/Iceshrimp.Backend/Core/Tasks/TimelineHeuristicTask.cs index a790bdec..8c66adfe 100644 --- a/Iceshrimp.Backend/Core/Tasks/TimelineHeuristicTask.cs +++ b/Iceshrimp.Backend/Core/Tasks/TimelineHeuristicTask.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using Iceshrimp.Backend.Core.Database; using Iceshrimp.Backend.Core.Extensions; using Iceshrimp.Backend.Core.Services; +using Iceshrimp.EntityFrameworkCore.Extensions; namespace Iceshrimp.Backend.Core.Tasks; diff --git a/Iceshrimp.Backend/Iceshrimp.Backend.csproj b/Iceshrimp.Backend/Iceshrimp.Backend.csproj index 1c71c52a..d4b4847c 100644 --- a/Iceshrimp.Backend/Iceshrimp.Backend.csproj +++ b/Iceshrimp.Backend/Iceshrimp.Backend.csproj @@ -26,6 +26,7 @@ +