Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@ All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/).

## [1.2.0] - 2026-03-20

Optimization:
- Skip querying the database for the total count when the count can be determined from the number of elements returned from the first page.

## [1.1.1] - 2026-01-02

Fixes:
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<PackageProjectUrl>https://github.com/Jameak/CursorPagination</PackageProjectUrl>
<PackageTags>cursor paginate pagination keyset offset skip source generator queryable</PackageTags>
<PackageDescription>An easy-to-use efficient KeySet- and Offset-pagination implementation for IQueryable with included opaque Cursor support.</PackageDescription>
<VersionPrefix>1.1.1</VersionPrefix>
<VersionPrefix>1.2.0</VersionPrefix>
<VersionSuffix></VersionSuffix>
<PackageVersion Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)</PackageVersion>
<PackageVersion Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix)</PackageVersion>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using Microsoft.AspNetCore.Mvc;

namespace Jameak.CursorPagination.Sample.Controllers;

[ApiController]
[Route("api/[controller]/[action]")]
public class PaginatedDataController : ControllerBase
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#pragma warning disable RS2008 // Analyzer release tracking

namespace Jameak.CursorPagination.SourceGenerator;

internal static class DiagnosticHelper
{
private const string UsageCategory = "Usage";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Microsoft.CodeAnalysis.Operations;

namespace Jameak.CursorPagination.SourceGenerator;

internal static class ExtensionMethods
{
private static readonly SymbolDisplayFormat s_fullyQualifiedWithNullRefModifier =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using static Jameak.CursorPagination.SourceGenerator.HelperMethods;

namespace Jameak.CursorPagination.SourceGenerator.Extractors;

internal abstract partial class BaseExtractor
{
private sealed class PaginationPropertyExtractionHelper
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using static Jameak.CursorPagination.SourceGenerator.HelperMethods;

namespace Jameak.CursorPagination.SourceGenerator.Extractors;

internal abstract partial class BaseExtractor
{
internal abstract bool TryHandle(GeneratorAttributeSyntaxContext context, INamedTypeSymbol generatorClassSymbol, out BaseExtractedData? extractedData);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using static Jameak.CursorPagination.SourceGenerator.HelperMethods;

namespace Jameak.CursorPagination.SourceGenerator.Extractors;

internal sealed class KeySetExtractor : BaseExtractor
{
internal override bool TryHandle(GeneratorAttributeSyntaxContext context, INamedTypeSymbol generatorClassSymbol, out BaseExtractedData? extractedData)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using static Jameak.CursorPagination.SourceGenerator.HelperMethods;

namespace Jameak.CursorPagination.SourceGenerator.Extractors;

internal sealed class OffsetExtractor : BaseExtractor
{
internal override bool TryHandle(GeneratorAttributeSyntaxContext context, INamedTypeSymbol generatorClassSymbol, out BaseExtractedData? extractedData)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Microsoft.CodeAnalysis.CSharp.Syntax;

namespace Jameak.CursorPagination.SourceGenerator;

internal static class HelperMethods
{

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using static Jameak.CursorPagination.SourceGenerator.HelperMethods;

namespace Jameak.CursorPagination.SourceGenerator;

internal static class KeySetPaginationClassBuilder
{
private const string PrivateHelperClassName = "PrivateHelper";
Expand Down Expand Up @@ -411,8 +412,8 @@
};

return orEqual
? (isGreaterThan ? ">=" : "<=")

Check warning on line 415 in src/Jameak.CursorPagination.SourceGenerator/KeySetPaginationClassBuilder.cs

View workflow job for this annotation

GitHub Actions / sonarqube-scan

Extract this nested ternary operation into an independent statement.
: (isGreaterThan ? ">" : "<");

Check warning on line 416 in src/Jameak.CursorPagination.SourceGenerator/KeySetPaginationClassBuilder.cs

View workflow job for this annotation

GitHub Actions / sonarqube-scan

Extract this nested ternary operation into an independent statement.
}

static string CreateComparisonString(PropertyConfiguration property, string operation)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using static Jameak.CursorPagination.SourceGenerator.HelperMethods;

namespace Jameak.CursorPagination.SourceGenerator;

internal static class OffsetPaginationClassBuilder
{
private const string PrivateHelperClassName = "PrivateHelper";
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace Jameak.CursorPagination.SourceGenerator;

internal enum PaginationKind
{
KeySet,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Jameak.CursorPagination.SourceGenerator.Helpers;

namespace Jameak.CursorPagination.SourceGenerator.Poco;

internal record BaseExtractedData
{
public string Name { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Jameak.CursorPagination.SourceGenerator.Helpers;

namespace Jameak.CursorPagination.SourceGenerator.Poco;

internal record ExtractedKeySetData : BaseExtractedData
{
public string? Namespace { get; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Jameak.CursorPagination.SourceGenerator.Helpers;

namespace Jameak.CursorPagination.SourceGenerator.Poco;

internal record ExtractedOffsetData : BaseExtractedData
{
public string? Namespace { get; }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace Jameak.CursorPagination.SourceGenerator;

internal static class TrackingNames
{
public const string InitialOffsetExtraction = nameof(InitialOffsetExtraction);
Expand Down
8 changes: 8 additions & 0 deletions src/Jameak.CursorPagination/InternalPaginatorHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Jameak.CursorPagination.Page;

namespace Jameak.CursorPagination;

internal static class InternalPaginatorHelper
{
internal sealed record EmptyNextPageState(
Expand Down Expand Up @@ -99,6 +100,13 @@ internal static bool ShouldComputeTotalCount(bool hasAlreadyComputedCount, Compu
};
}

internal static bool CanSetTotalCountFromCurrentPageCount<T, TCursor>(List<T> pageElements, TCursor? cursor, int pageSize) where TCursor : class
{
var isFirstPage = cursor == null;
var pageElementsFewerThanRequested = pageElements.Count < pageSize;
return isFirstPage && pageElementsFewerThanRequested;
}

internal static void ThrowIfEnumNotDefined<T>(T argument, [CallerArgumentExpression(nameof(argument))] string? paramName = null) where T : struct, Enum
{
if (!Enum.IsDefined(argument))
Expand Down
24 changes: 14 additions & 10 deletions src/Jameak.CursorPagination/KeySetPaginator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,19 @@ private static PageResult<T, TCursor> InternalApplyPagination<T, TCursor>(
InternalPaginatorHelper.ThrowIfEnumNotDefined(computeNextPage);
InternalPaginatorHelper.ThrowIfEnumNotDefined(computeTotalCount);

if (InternalPaginatorHelper.ShouldComputeTotalCount(totalCount.HasValue, computeTotalCount))
{
totalCount = queryable.Count();
}

var paginatedQueryable = strategy.ApplyPagination(queryable, pageSize, computeNextPage != ComputeNextPage.Never, paginationDirection, afterCursor);
var materialized = paginatedQueryable.ToList();

strategy.PostProcessMaterializedResultInPlace(materialized, pageSize, computeNextPage != ComputeNextPage.Never, paginationDirection, out var hasNextPage);
var (previousCursorElement, nextCursorElement) = InternalPaginatorHelper.GetCursorElements(materialized, paginationDirection);

if (InternalPaginatorHelper.ShouldComputeTotalCount(totalCount.HasValue, computeTotalCount))
{
totalCount = InternalPaginatorHelper.CanSetTotalCountFromCurrentPageCount(materialized, afterCursor, pageSize)
? materialized.Count
: queryable.Count();
}

NextPage<T, TCursor> NextPageGenerator(TCursor nextCursor)
{
return () => InternalApplyPagination(
Expand Down Expand Up @@ -196,17 +198,19 @@ private static async Task<PageResultAsync<T, TCursor>> InternalApplyPaginationAs
InternalPaginatorHelper.ThrowIfEnumNotDefined(computeNextPage);
InternalPaginatorHelper.ThrowIfEnumNotDefined(computeTotalCount);

if (InternalPaginatorHelper.ShouldComputeTotalCount(totalCount.HasValue, computeTotalCount))
{
totalCount = await asyncCountFunc(queryable, cancellationToken);
}

var paginatedQueryable = strategy.ApplyPagination(queryable, pageSize, computeNextPage != ComputeNextPage.Never, paginationDirection, afterCursor);
var materialized = await asyncMaterializationFunc(paginatedQueryable, cancellationToken);

strategy.PostProcessMaterializedResultInPlace(materialized, pageSize, computeNextPage != ComputeNextPage.Never, paginationDirection, out var hasNextPage);
var (previousCursorElement, nextCursorElement) = InternalPaginatorHelper.GetCursorElements(materialized, paginationDirection);

if (InternalPaginatorHelper.ShouldComputeTotalCount(totalCount.HasValue, computeTotalCount))
{
totalCount = InternalPaginatorHelper.CanSetTotalCountFromCurrentPageCount(materialized, afterCursor, pageSize)
? materialized.Count
: await asyncCountFunc(queryable, cancellationToken);
}

NextPageAsync<T, TCursor> NextPageAsyncGenerator(TCursor nextCursor)
{
return (cancellationToken) => InternalApplyPaginationAsync(
Expand Down
24 changes: 14 additions & 10 deletions src/Jameak.CursorPagination/OffsetPaginator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,20 @@ private static PageResult<T, OffsetCursor> InternalApplyPagination<T>(
InternalPaginatorHelper.ThrowIfEnumNotDefined(computeNextPage);
InternalPaginatorHelper.ThrowIfEnumNotDefined(computeTotalCount);

if (InternalPaginatorHelper.ShouldComputeTotalCount(totalCount.HasValue, computeTotalCount))
{
totalCount = queryable.Count();
}

var paginatedQueryable = strategy.ApplyPagination(queryable, pageSize, computeNextPage != ComputeNextPage.Never, paginationDirection, afterCursor);
var materialized = paginatedQueryable.ToList();

var postProcessed = strategy.PostProcessMaterializedResult(materialized, pageSize, computeNextPage != ComputeNextPage.Never, paginationDirection, afterCursor, out var hasNextPage)
.ToList();
var (_, nextCursorElement) = InternalPaginatorHelper.GetCursorElements(postProcessed, paginationDirection);

if (InternalPaginatorHelper.ShouldComputeTotalCount(totalCount.HasValue, computeTotalCount))
{
totalCount = InternalPaginatorHelper.CanSetTotalCountFromCurrentPageCount(postProcessed, afterCursor, pageSize)
? postProcessed.Count
: queryable.Count();
}

NextPage<T, OffsetCursor> NextPageGenerator(OffsetCursor nextCursor)
{
return () => InternalApplyPagination(
Expand Down Expand Up @@ -216,11 +218,6 @@ private static async Task<PageResultAsync<T, OffsetCursor>> InternalApplyPaginat
InternalPaginatorHelper.ThrowIfEnumNotDefined(computeNextPage);
InternalPaginatorHelper.ThrowIfEnumNotDefined(computeTotalCount);

if (InternalPaginatorHelper.ShouldComputeTotalCount(totalCount.HasValue, computeTotalCount))
{
totalCount = await asyncCountFunc(queryable, cancellationToken);
}

var paginatedQueryable = strategy.ApplyPagination(queryable, pageSize, computeNextPage != ComputeNextPage.Never, paginationDirection, afterCursor);
var materialized = await asyncMaterializationFunc(paginatedQueryable, cancellationToken);

Expand All @@ -229,6 +226,13 @@ private static async Task<PageResultAsync<T, OffsetCursor>> InternalApplyPaginat

var (_, nextCursorElement) = InternalPaginatorHelper.GetCursorElements(postProcessed, paginationDirection);

if (InternalPaginatorHelper.ShouldComputeTotalCount(totalCount.HasValue, computeTotalCount))
{
totalCount = InternalPaginatorHelper.CanSetTotalCountFromCurrentPageCount(postProcessed, afterCursor, pageSize)
? postProcessed.Count
: await asyncCountFunc(queryable, cancellationToken);
}

NextPageAsync<T, OffsetCursor> NextPageAsyncGenerator(OffsetCursor nextCursor)
{
return (cancellationToken) => InternalApplyPaginationAsync(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace Jameak.CursorPagination.SourceGenerator.IntegrationTests.InputClasses;

internal record SimplePropertyPoco
{
public required int IntProp { get; set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Jameak.CursorPagination.SourceGenerator.IntegrationTests.PartialStrategies;

namespace Jameak.CursorPagination.SourceGenerator.IntegrationTests;

public class KeySetCursorSerializationTests
{
[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Jameak.CursorPagination.SourceGenerator.IntegrationTests.PartialStrategies;

namespace Jameak.CursorPagination.SourceGenerator.IntegrationTests;

public class KeySetPaginationTests
{
private static void AssertStrategyOrdersCorrectly<TCursor>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Jameak.CursorPagination.Abstractions.OffsetPagination;

namespace Jameak.CursorPagination.SourceGenerator.IntegrationTests;

public class OffsetCursorSerializationTests
{
[Fact]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Jameak.CursorPagination.SourceGenerator.IntegrationTests.PartialStrategies;

namespace Jameak.CursorPagination.SourceGenerator.IntegrationTests;

public class OffsetPaginationTests
{
private static void AssertStrategyOrdersCorrectly(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using Jameak.CursorPagination.SourceGenerator.IntegrationTests.InputClasses;

namespace Jameak.CursorPagination.SourceGenerator.IntegrationTests;

internal class TestHelper
{
public static IEnumerable<SimplePropertyPoco> InfiniteGenerator()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace Jameak.CursorPagination.SourceGenerator.Tests;

public class ErrorTests
{
private const string ValidPaginatedType = """
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Jameak.CursorPagination.SourceGenerator.Analyzers;

namespace Jameak.CursorPagination.SourceGenerator.Tests;

public class FullNameOfDiagnosticAnalyzerTests
{
[Fact]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace Jameak.CursorPagination.SourceGenerator.Tests;

public class GeneralTests
{
[Fact]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace Jameak.CursorPagination.SourceGenerator.Tests;

public class InternalUsageAnalyzerTests
{
[Fact]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace Jameak.CursorPagination.SourceGenerator.Tests;

public class KeySetSpecificTests
{
private static readonly string s_paginatedType = """
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Runtime.CompilerServices;

namespace Jameak.CursorPagination.SourceGenerator.Tests;

internal static class ModuleInitializer
{
[ModuleInitializer]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace Jameak.CursorPagination.SourceGenerator.Tests;

public class NestedPropertyTests
{
[Fact]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace Jameak.CursorPagination.SourceGenerator.Tests;

public class NullableTests
{
[Fact]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace Jameak.CursorPagination.SourceGenerator.Tests;

public class OffsetSpecificTests
{
private static readonly string s_paginatedType = """
Expand Down
1 change: 1 addition & 0 deletions test/Jameak.CursorPagination.Tests/AsyncDelegates.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
namespace Jameak.CursorPagination.Tests;

internal class AsyncDelegates
{
public static Task<List<T>> SyncToList<T>(IQueryable<T> queryable)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.EntityFrameworkCore;

namespace Jameak.CursorPagination.Tests.DbClasses;

public sealed class DatabaseFixture : IDisposable
{
private readonly SqliteConnection _connection;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using Microsoft.EntityFrameworkCore;

namespace Jameak.CursorPagination.Tests.DbClasses;

public class TestDbContext : DbContext
{
private readonly List<string> _logMessages = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.EntityFrameworkCore;

namespace Jameak.CursorPagination.Tests.InputClasses;

[PrimaryKey(nameof(PrimaryKeyInt))]
[Index(nameof(ComputedIntProp))]
public record NullablePropertyWithDbComputedColumnPoco
Expand Down
Loading
Loading