[backend] Switch to Iceshrimp.EntityFrameworkCore.Extensions
This commit is contained in:
parent
db04e6dadf
commit
045ce709aa
10 changed files with 9 additions and 157 deletions
|
@ -14,6 +14,7 @@ using Iceshrimp.Backend.Core.Helpers;
|
||||||
using Iceshrimp.Backend.Core.Middleware;
|
using Iceshrimp.Backend.Core.Middleware;
|
||||||
using Iceshrimp.Backend.Core.Services;
|
using Iceshrimp.Backend.Core.Services;
|
||||||
using Iceshrimp.Backend.Core.Tasks;
|
using Iceshrimp.Backend.Core.Tasks;
|
||||||
|
using Iceshrimp.EntityFrameworkCore.Extensions;
|
||||||
using Iceshrimp.Shared.Configuration;
|
using Iceshrimp.Shared.Configuration;
|
||||||
using Iceshrimp.Shared.Schemas.Web;
|
using Iceshrimp.Shared.Schemas.Web;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
|
|
@ -1,51 +0,0 @@
|
||||||
using System.Linq.Expressions;
|
|
||||||
|
|
||||||
namespace Iceshrimp.Backend.Core.Extensions;
|
|
||||||
|
|
||||||
public static class ExpressionExtensions
|
|
||||||
{
|
|
||||||
public static Expression<Func<T, bool>> True<T>() => f => true;
|
|
||||||
public static Expression<Func<T, bool>> False<T>() => f => false;
|
|
||||||
|
|
||||||
public static Expression<Func<T, bool>> Or<T>(
|
|
||||||
this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2
|
|
||||||
)
|
|
||||||
{
|
|
||||||
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters);
|
|
||||||
return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Expression<Func<T, bool>> And<T>(
|
|
||||||
this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2
|
|
||||||
)
|
|
||||||
{
|
|
||||||
var invokedExpr = Expression.Invoke(expr2, expr1.Parameters);
|
|
||||||
return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, invokedExpr), expr1.Parameters);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Expression<Func<TFirstParam, TResult>> Compose<TFirstParam, TIntermediate, TResult>(
|
|
||||||
this Expression<Func<TFirstParam, TIntermediate>> first,
|
|
||||||
Expression<Func<TIntermediate, TResult>> 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<Func<TFirstParam, TResult>>(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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -9,6 +9,7 @@ using Iceshrimp.Backend.Controllers.Shared.Schemas;
|
||||||
using Iceshrimp.Backend.Core.Database;
|
using Iceshrimp.Backend.Core.Database;
|
||||||
using Iceshrimp.Backend.Core.Database.Tables;
|
using Iceshrimp.Backend.Core.Database.Tables;
|
||||||
using Iceshrimp.Backend.Core.Middleware;
|
using Iceshrimp.Backend.Core.Middleware;
|
||||||
|
using Iceshrimp.EntityFrameworkCore.Extensions;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
@ -16,112 +17,6 @@ namespace Iceshrimp.Backend.Core.Extensions;
|
||||||
|
|
||||||
public static class QueryableExtensions
|
public static class QueryableExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// 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.
|
|
||||||
/// </remarks>
|
|
||||||
/// <returns>
|
|
||||||
/// The result set as an IAsyncEnumerable. Makes one DB roundtrip at the start of each chunk.
|
|
||||||
/// Successive items in the chunk are yielded instantaneously.
|
|
||||||
/// </returns>
|
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
|
||||||
public static async IAsyncEnumerable<T> AsChunkedAsyncEnumerable<T>(this IQueryable<T> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="AsChunkedAsyncEnumerable{T}(System.Linq.IQueryable{T},int)" select="summary|returns"/>
|
|
||||||
/// <remarks>
|
|
||||||
/// This overload requires you to pass a predicate to the identifier.
|
|
||||||
/// .OrderBy(<paramref name="idPredicate"/>) is appended to the query.
|
|
||||||
/// Set the <paramref name="hook"/> parameter to append things to the query after pagination, for cases where query translation would fail otherwise.
|
|
||||||
/// </remarks>
|
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
|
||||||
public static async IAsyncEnumerable<TResult> AsChunkedAsyncEnumerable<TResult>(
|
|
||||||
this IQueryable<TResult> query, int chunkSize, Expression<Func<TResult, string>> idPredicate,
|
|
||||||
Func<IQueryable<TResult>, IQueryable<TResult>>? 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="AsChunkedAsyncEnumerable{TResult}(System.Linq.IQueryable{TResult},int,System.Linq.Expressions.Expression{System.Func{TResult,string}},System.Func{System.Linq.IQueryable{TResult},System.Linq.IQueryable{TResult}}?)"/>
|
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
|
||||||
public static async IAsyncEnumerable<TResult> AsChunkedAsyncEnumerable<TResult>(
|
|
||||||
this IQueryable<TResult> query, int chunkSize, Expression<Func<TResult, Guid>> idPredicate,
|
|
||||||
Func<IQueryable<TResult>, IQueryable<TResult>>? 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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc cref="AsChunkedAsyncEnumerable{TResult}(System.Linq.IQueryable{TResult},int,System.Linq.Expressions.Expression{System.Func{TResult,string}},System.Func{System.Linq.IQueryable{TResult},System.Linq.IQueryable{TResult}}?)"/>
|
|
||||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
|
||||||
public static async IAsyncEnumerable<TResult> AsChunkedAsyncEnumerable<TResult>(
|
|
||||||
this IQueryable<TResult> query, int chunkSize, Expression<Func<TResult, int>> idPredicate,
|
|
||||||
Func<IQueryable<TResult>, IQueryable<TResult>>? 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<T> Paginate<T>(
|
public static IQueryable<T> Paginate<T>(
|
||||||
this IQueryable<T> query,
|
this IQueryable<T> query,
|
||||||
MastodonPaginationQuery pq,
|
MastodonPaginationQuery pq,
|
||||||
|
|
|
@ -5,6 +5,7 @@ using Iceshrimp.Backend.Core.Configuration;
|
||||||
using Iceshrimp.Backend.Core.Database;
|
using Iceshrimp.Backend.Core.Database;
|
||||||
using Iceshrimp.Backend.Core.Database.Tables;
|
using Iceshrimp.Backend.Core.Database.Tables;
|
||||||
using Iceshrimp.Backend.Core.Helpers;
|
using Iceshrimp.Backend.Core.Helpers;
|
||||||
|
using Iceshrimp.EntityFrameworkCore.Extensions;
|
||||||
using Iceshrimp.Parsing;
|
using Iceshrimp.Parsing;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@ using Iceshrimp.Backend.Core.Database;
|
||||||
using Iceshrimp.Backend.Core.Database.Tables;
|
using Iceshrimp.Backend.Core.Database.Tables;
|
||||||
using Iceshrimp.Backend.Core.Extensions;
|
using Iceshrimp.Backend.Core.Extensions;
|
||||||
using Iceshrimp.Backend.Core.Services;
|
using Iceshrimp.Backend.Core.Services;
|
||||||
|
using Iceshrimp.EntityFrameworkCore.Extensions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
|
using J = System.Text.Json.Serialization.JsonPropertyNameAttribute;
|
||||||
|
|
|
@ -4,6 +4,7 @@ using Iceshrimp.Backend.Core.Configuration;
|
||||||
using Iceshrimp.Backend.Core.Database;
|
using Iceshrimp.Backend.Core.Database;
|
||||||
using Iceshrimp.Backend.Core.Database.Tables;
|
using Iceshrimp.Backend.Core.Database.Tables;
|
||||||
using Iceshrimp.Backend.Core.Extensions;
|
using Iceshrimp.Backend.Core.Extensions;
|
||||||
|
using Iceshrimp.EntityFrameworkCore.Extensions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,7 @@ using Iceshrimp.Backend.Core.Helpers;
|
||||||
using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
|
using Iceshrimp.Backend.Core.Helpers.LibMfm.Conversion;
|
||||||
using Iceshrimp.Backend.Core.Middleware;
|
using Iceshrimp.Backend.Core.Middleware;
|
||||||
using Iceshrimp.Backend.Core.Queues;
|
using Iceshrimp.Backend.Core.Queues;
|
||||||
|
using Iceshrimp.EntityFrameworkCore.Extensions;
|
||||||
using Iceshrimp.MfmSharp;
|
using Iceshrimp.MfmSharp;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
|
@ -4,6 +4,7 @@ using Iceshrimp.Backend.Core.Database;
|
||||||
using Iceshrimp.Backend.Core.Extensions;
|
using Iceshrimp.Backend.Core.Extensions;
|
||||||
using Iceshrimp.Backend.Core.Queues;
|
using Iceshrimp.Backend.Core.Queues;
|
||||||
using Iceshrimp.Backend.Core.Services;
|
using Iceshrimp.Backend.Core.Services;
|
||||||
|
using Iceshrimp.EntityFrameworkCore.Extensions;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||||
using Iceshrimp.Backend.Core.Database;
|
using Iceshrimp.Backend.Core.Database;
|
||||||
using Iceshrimp.Backend.Core.Extensions;
|
using Iceshrimp.Backend.Core.Extensions;
|
||||||
using Iceshrimp.Backend.Core.Services;
|
using Iceshrimp.Backend.Core.Services;
|
||||||
|
using Iceshrimp.EntityFrameworkCore.Extensions;
|
||||||
|
|
||||||
namespace Iceshrimp.Backend.Core.Tasks;
|
namespace Iceshrimp.Backend.Core.Tasks;
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
<PackageReference Include="EntityFrameworkCore.Projectables" Version="4.0.0" />
|
<PackageReference Include="EntityFrameworkCore.Projectables" Version="4.0.0" />
|
||||||
<PackageReference Include="FlexLabs.EntityFrameworkCore.Upsert" Version="8.1.2" />
|
<PackageReference Include="FlexLabs.EntityFrameworkCore.Upsert" Version="8.1.2" />
|
||||||
<PackageReference Include="Iceshrimp.Assets.Fonts" Version="1.0.0" />
|
<PackageReference Include="Iceshrimp.Assets.Fonts" Version="1.0.0" />
|
||||||
|
<PackageReference Include="Iceshrimp.EntityFrameworkCore.Extensions" Version="1.0.0" />
|
||||||
<PackageReference Include="Iceshrimp.ObjectStorage.S3" Version="0.34.3" />
|
<PackageReference Include="Iceshrimp.ObjectStorage.S3" Version="0.34.3" />
|
||||||
<PackageReference Include="Isopoh.Cryptography.Argon2" Version="2.0.0" />
|
<PackageReference Include="Isopoh.Cryptography.Argon2" Version="2.0.0" />
|
||||||
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0" />
|
<PackageReference Include="JetBrains.Annotations" Version="2024.3.0" />
|
||||||
|
|
Loading…
Add table
Reference in a new issue