diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index a2d56ec8e2d..f8e8406b72e 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -59,6 +59,7 @@
+
@@ -87,6 +88,7 @@
+
@@ -115,6 +117,7 @@
+
@@ -142,6 +145,7 @@
+
diff --git a/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Expressions/ExpressionHelpers.cs b/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Expressions/ExpressionHelpers.cs
index b12b008e8a7..ab637e81812 100644
--- a/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Expressions/ExpressionHelpers.cs
+++ b/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Expressions/ExpressionHelpers.cs
@@ -15,6 +15,14 @@ internal static class ExpressionHelpers
.GetMethod(nameof(CreateAndConvertParameter), BindingFlags.NonPublic | BindingFlags.Static)!;
private static readonly ConcurrentDictionary> s_cachedConverters = new();
+ private static readonly NullabilityInfoContext s_nullabilityInfoContext = new();
+#if NET9_0_OR_GREATER
+ private static readonly Lock s_nullabilityInfoContextLock = new();
+#else
+ private static readonly object s_nullabilityInfoContextLock = new();
+#endif
+ private static readonly Expression s_false = Expression.Constant(false);
+ private static readonly Expression s_zero = Expression.Constant(0);
///
/// Builds a where expression that can be used to slice a dataset.
@@ -28,6 +36,9 @@ internal static class ExpressionHelpers
///
/// Defines how the dataset is sorted.
///
+ ///
+ /// Defines the null ordering to be used.
+ ///
///
/// The entity type.
///
@@ -43,7 +54,8 @@ internal static class ExpressionHelpers
public static (Expression> WhereExpression, int Offset) BuildWhereExpression(
ReadOnlySpan keys,
Cursor cursor,
- bool forward)
+ bool forward,
+ NullOrdering nullOrdering)
{
if (keys.Length == 0)
{
@@ -58,14 +70,16 @@ public static (Expression> WhereExpression, int Offset) BuildWhere
var cursorExpr = new Expression[cursor.Values.Length];
for (var i = 0; i < cursor.Values.Length; i++)
{
- cursorExpr[i] = CreateParameter(cursor.Values[i], keys[i].Expression.ReturnType);
+ var parameterType = Nullable.GetUnderlyingType(keys[i].Expression.ReturnType)
+ ?? keys[i].Expression.ReturnType;
+
+ cursorExpr[i] = CreateParameter(cursor.Values[i], parameterType);
}
var handled = new List();
Expression? expression = null;
var parameter = Expression.Parameter(typeof(T), "t");
- var zero = Expression.Constant(0);
for (var i = 0; i < keys.Length; i++)
{
@@ -76,25 +90,53 @@ public static (Expression> WhereExpression, int Offset) BuildWhere
for (var j = 0; j < handled.Count; j++)
{
var handledKey = handled[j];
+ var handledKeyIsNullable = IsNullable(handledKey.Expression);
- keyExpr = Expression.Equal(
- Expression.Call(ReplaceParameter(handledKey.Expression, parameter), handledKey.CompareMethod,
- cursorExpr[j]), zero);
+ keyExpr = BuildEqualToKeyExpr(
+ handledKey,
+ parameter,
+ cursor.Values[j],
+ handledKeyIsNullable,
+ cursorExpr[j]);
current = current is null ? keyExpr : Expression.AndAlso(current, keyExpr);
}
+ var keyIsNullable = IsNullable(key.Expression);
+
+ if (keyIsNullable && nullOrdering == NullOrdering.Unspecified)
+ {
+ throw new InvalidOperationException(
+ "The NullOrdering option must be specified in the paging options or "
+ + "arguments when using nullable keys.");
+ }
+
var greaterThan = forward
? key.Direction == CursorKeyDirection.Ascending
: key.Direction == CursorKeyDirection.Descending;
- keyExpr = greaterThan
- ? Expression.GreaterThan(
- Expression.Call(ReplaceParameter(key.Expression, parameter), key.CompareMethod, cursorExpr[i]),
- zero)
- : Expression.LessThan(
- Expression.Call(ReplaceParameter(key.Expression, parameter), key.CompareMethod, cursorExpr[i]),
- zero);
+ if (greaterThan)
+ {
+ keyExpr =
+ BuildGreaterThanKeyExpr(
+ key,
+ parameter,
+ cursor.Values[i],
+ keyIsNullable,
+ nullOrdering,
+ cursorExpr[i]);
+ }
+ else
+ {
+ keyExpr =
+ BuildLessThanKeyExpr(
+ key,
+ parameter,
+ cursor.Values[i],
+ keyIsNullable,
+ nullOrdering,
+ cursorExpr[i]);
+ }
current = current is null ? keyExpr : Expression.AndAlso(current, keyExpr);
expression = expression is null ? current : Expression.OrElse(expression, current);
@@ -104,6 +146,238 @@ public static (Expression> WhereExpression, int Offset) BuildWhere
return (Expression.Lambda>(expression!, parameter), cursor.Offset ?? 0);
}
+ private static Expression BuildEqualToKeyExpr(
+ CursorKey cursorKey,
+ ParameterExpression parameter,
+ object? cursorValue,
+ bool keyIsNullable,
+ Expression cursorExpr)
+ {
+ var keyExpr = ReplaceParameter(cursorKey.Expression, parameter);
+
+ // Access the value of the key if it is a nullable value type.
+ var keyValueExpr = cursorKey.Expression.ReturnType.IsValueType && keyIsNullable
+ ? Expression.Property(keyExpr, "Value")
+ : keyExpr;
+
+ if (keyIsNullable)
+ {
+ // Null constant must be typed to match keyExpr.Type so that expression
+ // construction works for both reference types and Nullable value types.
+ var nullConst = Expression.Constant(null, keyExpr.Type);
+
+ if (cursorValue is null)
+ {
+ // SQL: WHERE key IS NULL.
+ keyExpr = Expression.Equal(keyExpr, nullConst);
+ }
+ else
+ {
+ // SQL: WHERE key IS NOT NULL AND key = cursorValue.
+ keyExpr = Expression.AndAlso(
+ Expression.NotEqual(keyExpr, nullConst),
+ Expression.Equal(
+ Expression.Call(keyValueExpr, cursorKey.CompareMethod, cursorExpr),
+ s_zero));
+ }
+ }
+ else
+ {
+ // SQL: WHERE key = cursorValue.
+ keyExpr = Expression.Equal(
+ Expression.Call(keyExpr, cursorKey.CompareMethod, cursorExpr),
+ s_zero);
+ }
+
+ return keyExpr;
+ }
+
+ private static Expression BuildGreaterThanKeyExpr(
+ CursorKey cursorKey,
+ ParameterExpression parameter,
+ object? cursorValue,
+ bool keyIsNullable,
+ NullOrdering nullOrdering,
+ Expression cursorExpr)
+ {
+ var keyExpr = ReplaceParameter(cursorKey.Expression, parameter);
+
+ // Access the value of the key if it is a nullable value type.
+ var keyValueExpr =
+ cursorKey.Expression.ReturnType.IsValueType && keyIsNullable
+ ? Expression.Property(keyExpr, "Value")
+ : keyExpr;
+
+ if (keyIsNullable)
+ {
+ // Null constant must be typed to match keyExpr.Type so that expression
+ // construction works for both reference types and Nullable value types.
+ var nullConst = Expression.Constant(null, keyExpr.Type);
+
+ if (cursorValue is null)
+ {
+ keyExpr = nullOrdering == NullOrdering.NativeNullsFirst
+ // With nulls first, any non-null value is greater than null.
+ // SQL: WHERE key IS NOT NULL.
+ ? Expression.NotEqual(keyExpr, nullConst)
+ // With nulls last, no value is greater than null.
+ // SQL: WHERE false.
+ : s_false;
+ }
+ else
+ {
+ if (nullOrdering == NullOrdering.NativeNullsFirst)
+ {
+ // SQL: WHERE key > cursorValue.
+ keyExpr = Expression.GreaterThan(
+ Expression.Call(keyValueExpr, cursorKey.CompareMethod, cursorExpr),
+ s_zero);
+ }
+ else
+ {
+ // When nulls are last, null is greater than any non-null value.
+ // SQL: WHERE key IS NULL OR key > cursorValue.
+ keyExpr = Expression.OrElse(
+ Expression.Equal(keyExpr, nullConst),
+ Expression.GreaterThan(
+ Expression.Call(keyValueExpr, cursorKey.CompareMethod, cursorExpr),
+ s_zero));
+ }
+ }
+ }
+ else
+ {
+ // SQL: WHERE key > cursorValue.
+ keyExpr = Expression.GreaterThan(
+ Expression.Call(keyExpr, cursorKey.CompareMethod, cursorExpr),
+ s_zero);
+ }
+
+ return keyExpr;
+ }
+
+ private static Expression BuildLessThanKeyExpr(
+ CursorKey cursorKey,
+ ParameterExpression parameter,
+ object? cursorValue,
+ bool keyIsNullable,
+ NullOrdering nullOrdering,
+ Expression cursorExpr)
+ {
+ var keyExpr = ReplaceParameter(cursorKey.Expression, parameter);
+
+ // Access the value of the key if it is a nullable value type.
+ var keyValueExpr =
+ cursorKey.Expression.ReturnType.IsValueType && keyIsNullable
+ ? Expression.Property(keyExpr, "Value")
+ : keyExpr;
+
+ if (keyIsNullable)
+ {
+ // Null constant must be typed to match keyExpr.Type so that expression
+ // construction works for both reference types and Nullable value types.
+ var nullConst = Expression.Constant(null, keyExpr.Type);
+
+ if (cursorValue is null)
+ {
+ keyExpr = nullOrdering == NullOrdering.NativeNullsFirst
+ // With nulls first, no value is less than null.
+ // SQL: WHERE false.
+ ? s_false
+ // With nulls last, any non-null value is less than null.
+ // SQL: WHERE key IS NOT NULL.
+ : Expression.NotEqual(keyExpr, nullConst);
+ }
+ else
+ {
+ if (nullOrdering == NullOrdering.NativeNullsFirst)
+ {
+ // With nulls first, null is less than any non-null value.
+ // SQL: WHERE key IS NULL OR key < cursorValue.
+ keyExpr = Expression.OrElse(
+ Expression.Equal(keyExpr, nullConst),
+ Expression.LessThan(
+ Expression.Call(keyValueExpr, cursorKey.CompareMethod, cursorExpr),
+ s_zero));
+ }
+ else
+ {
+ // SQL: WHERE key < cursorValue.
+ keyExpr = Expression.LessThan(
+ Expression.Call(keyValueExpr, cursorKey.CompareMethod, cursorExpr),
+ s_zero);
+ }
+ }
+ }
+ else
+ {
+ // SQL: WHERE key < cursorValue.
+ keyExpr = Expression.LessThan(
+ Expression.Call(keyExpr, cursorKey.CompareMethod, cursorExpr),
+ s_zero);
+ }
+
+ return keyExpr;
+ }
+
+ private static bool IsNullable(LambdaExpression expression)
+ {
+ if (expression.ReturnType.IsValueType)
+ {
+ return Nullable.GetUnderlyingType(expression.ReturnType) is not null;
+ }
+
+ var member = expression.Body switch
+ {
+ MemberExpression { Member: PropertyInfo or FieldInfo } m => m.Member,
+ BinaryExpression
+ {
+ NodeType: ExpressionType.Coalesce,
+ Right: MemberExpression { Member: PropertyInfo or FieldInfo } m
+ } => m.Member,
+ _ => null
+ };
+
+ if (member is not null)
+ {
+ var state = member switch
+ {
+ PropertyInfo p => GetNullabilityInfoState(p),
+ FieldInfo f => GetNullabilityInfoState(f),
+ _ => throw new InvalidOperationException()
+ };
+
+ return state switch
+ {
+ NullabilityState.Nullable => true,
+ // Unknown means the assembly was compiled without NRT annotations;
+ // treat as non-nullable (safe default).
+ _ => false
+ };
+ }
+
+ // For computed key expressions (method calls, concatenation, etc.) we cannot inspect
+ // NRT annotations at runtime. Treat as non-nullable — the safe default that avoids
+ // injecting spurious null-handling into the generated WHERE clause.
+ return false;
+ }
+
+ private static NullabilityState GetNullabilityInfoState(PropertyInfo propertyInfo)
+ {
+ lock (s_nullabilityInfoContextLock)
+ {
+ return s_nullabilityInfoContext.Create(propertyInfo).ReadState;
+ }
+ }
+
+ private static NullabilityState GetNullabilityInfoState(FieldInfo fieldInfo)
+ {
+ lock (s_nullabilityInfoContextLock)
+ {
+ return s_nullabilityInfoContext.Create(fieldInfo).ReadState;
+ }
+ }
+
///
/// Build the select expression for a batch paging expression that uses grouping.
///
@@ -200,7 +474,11 @@ public static BatchExpression BuildBatchExpression(
if (arguments.After is not null)
{
cursor = CursorParser.Parse(arguments.After, keys);
- var (whereExpr, cursorOffset) = BuildWhereExpression(keys, cursor, forward: true);
+ var (whereExpr, cursorOffset) = BuildWhereExpression(
+ keys,
+ cursor,
+ forward: true,
+ arguments.NullOrdering);
source = Expression.Call(typeof(Enumerable), "Where", [typeof(TV)], source, whereExpr);
offset = cursorOffset;
@@ -220,7 +498,12 @@ public static BatchExpression BuildBatchExpression(
}
cursor = CursorParser.Parse(arguments.Before, keys);
- var (whereExpr, cursorOffset) = BuildWhereExpression(keys, cursor, forward: false);
+ var (whereExpr, cursorOffset) = BuildWhereExpression(
+ keys,
+ cursor,
+ forward:
+ false,
+ arguments.NullOrdering);
source = Expression.Call(typeof(Enumerable), "Where", [typeof(TV)], source, whereExpr);
offset = cursorOffset;
}
@@ -298,8 +581,8 @@ public static BatchExpression BuildBatchExpression(
var groupType = typeof(Group);
var bindings = new MemberBinding[]
{
- Expression.Bind(groupType.GetProperty(nameof(Group.Key))!, groupKey),
- Expression.Bind(groupType.GetProperty(nameof(Group.Items))!, source)
+ Expression.Bind(groupType.GetProperty(nameof(Group<,>.Key))!, groupKey),
+ Expression.Bind(groupType.GetProperty(nameof(Group<,>.Items))!, source)
};
var createGroup = Expression.MemberInit(Expression.New(groupType), bindings);
@@ -328,9 +611,6 @@ static MethodInfo GetEnumerableMethod(string methodName, Type elementType, Lambd
///
/// Extracts and removes the orderBy and thenBy expressions from the given expression tree.
///
- ///
- ///
- ///
public static OrderRewriterResult ExtractAndRemoveOrder(Expression expression)
{
ArgumentNullException.ThrowIfNull(expression);
@@ -432,15 +712,13 @@ protected override Expression VisitMethodCall(MethodCallExpression node)
&& (node.Method.Name == nameof(Queryable.OrderBy)
|| node.Method.Name == nameof(Queryable.OrderByDescending)
|| node.Method.Name == nameof(Queryable.ThenBy)
- || node.Method.Name == nameof(Queryable.ThenByDescending)))
+ || node.Method.Name == nameof(Queryable.ThenByDescending))
+ && !_insideSelectProjection)
{
- if (!_insideSelectProjection)
- {
- var lambda = (LambdaExpression)StripQuotes(node.Arguments[1]);
- _orderExpressions.Add(lambda);
- _orderMethods.Add(node.Method.Name);
- return Visit(node.Arguments[0]);
- }
+ var lambda = (LambdaExpression)StripQuotes(node.Arguments[1]);
+ _orderExpressions.Add(lambda);
+ _orderMethods.Add(node.Method.Name);
+ return Visit(node.Arguments[0]);
}
return base.VisitMethodCall(node);
diff --git a/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Extensions/PagingQueryableExtensions.cs b/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Extensions/PagingQueryableExtensions.cs
index 61593a89dba..ad08cc0f1a0 100644
--- a/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Extensions/PagingQueryableExtensions.cs
+++ b/src/GreenDonut/src/GreenDonut.Data.EntityFramework/Extensions/PagingQueryableExtensions.cs
@@ -132,7 +132,11 @@ public static async ValueTask> ToPageAsync(
if (arguments.After is not null)
{
cursor = CursorParser.Parse(arguments.After, keys);
- var (whereExpr, cursorOffset) = BuildWhereExpression(keys, cursor, true);
+ var (whereExpr, cursorOffset) = BuildWhereExpression(
+ keys,
+ cursor,
+ true,
+ arguments.NullOrdering);
source = source.Where(whereExpr);
offset = cursorOffset;
@@ -157,7 +161,11 @@ public static async ValueTask> ToPageAsync(
}
cursor = CursorParser.Parse(arguments.Before, keys);
- var (whereExpr, cursorOffset) = BuildWhereExpression(keys, cursor, false);
+ var (whereExpr, cursorOffset) = BuildWhereExpression(
+ keys,
+ cursor,
+ false,
+ arguments.NullOrdering);
source = source.Where(whereExpr);
offset = cursorOffset;
diff --git a/src/GreenDonut/src/GreenDonut.Data.Primitives/NullOrdering.cs b/src/GreenDonut/src/GreenDonut.Data.Primitives/NullOrdering.cs
new file mode 100644
index 00000000000..8a086dea64e
--- /dev/null
+++ b/src/GreenDonut/src/GreenDonut.Data.Primitives/NullOrdering.cs
@@ -0,0 +1,19 @@
+namespace GreenDonut.Data;
+
+public enum NullOrdering
+{
+ /// The null ordering is not specified.
+ Unspecified = 0,
+
+ ///
+ /// The database orders null values first (i.e., null is considered less than
+ /// non-null values).
+ ///
+ NativeNullsFirst = 1,
+
+ ///
+ /// The database orders null values last (i.e., null is considered greater than
+ /// non-null values).
+ ///
+ NativeNullsLast = 2
+}
diff --git a/src/GreenDonut/src/GreenDonut.Data.Primitives/PagingArguments.cs b/src/GreenDonut/src/GreenDonut.Data.Primitives/PagingArguments.cs
index e150e81e6fa..bf3168cfd21 100644
--- a/src/GreenDonut/src/GreenDonut.Data.Primitives/PagingArguments.cs
+++ b/src/GreenDonut/src/GreenDonut.Data.Primitives/PagingArguments.cs
@@ -67,6 +67,9 @@ public PagingArguments(
///
public bool EnableRelativeCursors { get; init; }
+ /// Defines the null ordering to be used.
+ public NullOrdering NullOrdering { get; init; }
+
///
/// Deconstructs the paging arguments into its components.
///
@@ -85,17 +88,22 @@ public PagingArguments(
///
/// Defines if the total count of items in the dataset shall be included in the result.
///
+ ///
+ /// Defines the null ordering to be used.
+ ///
public void Deconstruct(
out int? first,
out string? after,
out int? last,
out string? before,
- out bool includeTotalCount)
+ out bool includeTotalCount,
+ out NullOrdering nullOrdering)
{
first = First;
after = After;
last = Last;
before = Before;
includeTotalCount = IncludeTotalCount;
+ nullOrdering = NullOrdering;
}
}
diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/BoolCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/BoolCursorKeySerializer.cs
index d7dfda0b1a2..f8161ab62eb 100644
--- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/BoolCursorKeySerializer.cs
+++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/BoolCursorKeySerializer.cs
@@ -8,7 +8,7 @@ internal sealed class BoolCursorKeySerializer : ICursorKeySerializer
private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod();
public bool IsSupported(Type type)
- => type == typeof(bool);
+ => type == typeof(bool) || type == typeof(bool?);
public MethodInfo GetCompareToMethod(Type type)
=> s_compareTo;
diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateOnlyCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateOnlyCursorKeySerializer.cs
index 0f60548f71e..6698333a3cd 100644
--- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateOnlyCursorKeySerializer.cs
+++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateOnlyCursorKeySerializer.cs
@@ -12,7 +12,7 @@ internal sealed class DateOnlyCursorKeySerializer : ICursorKeySerializer
private const string DateFormat = "yyyyMMdd";
public bool IsSupported(Type type)
- => type == typeof(DateOnly);
+ => type == typeof(DateOnly) || type == typeof(DateOnly?);
public MethodInfo GetCompareToMethod(Type type)
=> s_compareTo;
diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateTimeCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateTimeCursorKeySerializer.cs
index 96e0b1bf929..775017058c8 100644
--- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateTimeCursorKeySerializer.cs
+++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateTimeCursorKeySerializer.cs
@@ -13,7 +13,7 @@ internal sealed class DateTimeCursorKeySerializer : ICursorKeySerializer
private const string DateTimeFormat = "yyyyMMddHHmmssfffffff";
public bool IsSupported(Type type)
- => type == typeof(DateTime);
+ => type == typeof(DateTime) || type == typeof(DateTime?);
public MethodInfo GetCompareToMethod(Type type)
=> s_compareTo;
diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateTimeOffsetCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateTimeOffsetCursorKeySerializer.cs
index 7f58f5a8611..8b9900067ce 100644
--- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateTimeOffsetCursorKeySerializer.cs
+++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DateTimeOffsetCursorKeySerializer.cs
@@ -14,7 +14,7 @@ internal sealed class DateTimeOffsetCursorKeySerializer : ICursorKeySerializer
private const string OffsetFormat = "hhmm";
public bool IsSupported(Type type)
- => type == typeof(DateTimeOffset);
+ => type == typeof(DateTimeOffset) || type == typeof(DateTimeOffset?);
public MethodInfo GetCompareToMethod(Type type)
=> s_compareTo;
diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DecimalCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DecimalCursorKeySerializer.cs
index 8a6988c30d7..2db8b7358f8 100644
--- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DecimalCursorKeySerializer.cs
+++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DecimalCursorKeySerializer.cs
@@ -8,7 +8,7 @@ internal sealed class DecimalCursorKeySerializer : ICursorKeySerializer
private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod();
public bool IsSupported(Type type)
- => type == typeof(decimal);
+ => type == typeof(decimal) || type == typeof(decimal?);
public MethodInfo GetCompareToMethod(Type type)
=> s_compareTo;
diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DoubleCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DoubleCursorKeySerializer.cs
index f2bbba889b6..822e7ea1481 100644
--- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DoubleCursorKeySerializer.cs
+++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/DoubleCursorKeySerializer.cs
@@ -8,7 +8,7 @@ internal sealed class DoubleCursorKeySerializer : ICursorKeySerializer
private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod();
public bool IsSupported(Type type)
- => type == typeof(double);
+ => type == typeof(double) || type == typeof(double?);
public MethodInfo GetCompareToMethod(Type type)
=> s_compareTo;
diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/FloatCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/FloatCursorKeySerializer.cs
index c55f988d7a6..fd6467e12ea 100644
--- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/FloatCursorKeySerializer.cs
+++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/FloatCursorKeySerializer.cs
@@ -8,7 +8,7 @@ internal sealed class FloatCursorKeySerializer : ICursorKeySerializer
private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod();
public bool IsSupported(Type type)
- => type == typeof(float);
+ => type == typeof(float) || type == typeof(float?);
public MethodInfo GetCompareToMethod(Type type)
=> s_compareTo;
diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/GuidCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/GuidCursorKeySerializer.cs
index 9174ee67b3d..9840c4cdc5a 100644
--- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/GuidCursorKeySerializer.cs
+++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/GuidCursorKeySerializer.cs
@@ -8,7 +8,7 @@ internal sealed class GuidCursorKeySerializer : ICursorKeySerializer
private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod();
public bool IsSupported(Type type)
- => type == typeof(Guid);
+ => type == typeof(Guid) || type == typeof(Guid?);
public MethodInfo GetCompareToMethod(Type type)
=> s_compareTo;
diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/IntCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/IntCursorKeySerializer.cs
index d33eee4b2a2..73bef74b1bd 100644
--- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/IntCursorKeySerializer.cs
+++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/IntCursorKeySerializer.cs
@@ -8,7 +8,7 @@ internal sealed class IntCursorKeySerializer : ICursorKeySerializer
private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod();
public bool IsSupported(Type type)
- => type == typeof(int);
+ => type == typeof(int) || type == typeof(int?);
public MethodInfo GetCompareToMethod(Type type)
=> s_compareTo;
diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/LongCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/LongCursorKeySerializer.cs
index c798c778fd6..260baef755d 100644
--- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/LongCursorKeySerializer.cs
+++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/LongCursorKeySerializer.cs
@@ -8,7 +8,7 @@ internal sealed class LongCursorKeySerializer : ICursorKeySerializer
private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod();
public bool IsSupported(Type type)
- => type == typeof(long);
+ => type == typeof(long) || type == typeof(long?);
public MethodInfo GetCompareToMethod(Type type)
=> s_compareTo;
diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/ShortCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/ShortCursorKeySerializer.cs
index e5e4208de6a..c9b308a146a 100644
--- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/ShortCursorKeySerializer.cs
+++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/ShortCursorKeySerializer.cs
@@ -8,7 +8,7 @@ internal sealed class ShortCursorKeySerializer : ICursorKeySerializer
private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod();
public bool IsSupported(Type type)
- => type == typeof(short);
+ => type == typeof(short) || type == typeof(short?);
public MethodInfo GetCompareToMethod(Type type)
=> s_compareTo;
diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/TimeOnlyCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/TimeOnlyCursorKeySerializer.cs
index b3057c6c234..9779e7a9473 100644
--- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/TimeOnlyCursorKeySerializer.cs
+++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/TimeOnlyCursorKeySerializer.cs
@@ -12,7 +12,7 @@ internal sealed class TimeOnlyCursorKeySerializer : ICursorKeySerializer
private const string TimeFormat = "HHmmssfffffff";
public bool IsSupported(Type type)
- => type == typeof(TimeOnly);
+ => type == typeof(TimeOnly) || type == typeof(TimeOnly?);
public MethodInfo GetCompareToMethod(Type type)
=> s_compareTo;
diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/UIntCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/UIntCursorKeySerializer.cs
index 668874d013e..3a1b461fc9d 100644
--- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/UIntCursorKeySerializer.cs
+++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/UIntCursorKeySerializer.cs
@@ -8,7 +8,7 @@ internal sealed class UIntCursorKeySerializer : ICursorKeySerializer
private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod();
public bool IsSupported(Type type)
- => type == typeof(uint);
+ => type == typeof(uint) || type == typeof(uint?);
public MethodInfo GetCompareToMethod(Type type)
=> s_compareTo;
diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/ULongCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/ULongCursorKeySerializer.cs
index 4dd8f560246..efba41b5b92 100644
--- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/ULongCursorKeySerializer.cs
+++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/ULongCursorKeySerializer.cs
@@ -8,7 +8,7 @@ internal sealed class ULongCursorKeySerializer : ICursorKeySerializer
private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod();
public bool IsSupported(Type type)
- => type == typeof(ulong);
+ => type == typeof(ulong) || type == typeof(ulong?);
public MethodInfo GetCompareToMethod(Type type)
=> s_compareTo;
diff --git a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/UShortCursorKeySerializer.cs b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/UShortCursorKeySerializer.cs
index b7db336fa36..0ab2949b0c3 100644
--- a/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/UShortCursorKeySerializer.cs
+++ b/src/GreenDonut/src/GreenDonut.Data/Cursors/Serializers/UShortCursorKeySerializer.cs
@@ -8,7 +8,7 @@ internal sealed class UShortCursorKeySerializer : ICursorKeySerializer
private static readonly MethodInfo s_compareTo = CompareToResolver.GetCompareToMethod();
public bool IsSupported(Type type)
- => type == typeof(ushort);
+ => type == typeof(ushort) || type == typeof(ushort?);
public MethodInfo GetCompareToMethod(Type type)
=> s_compareTo;
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/GreenDonut.Data.EntityFramework.Tests.csproj b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/GreenDonut.Data.EntityFramework.Tests.csproj
index d4b1212772a..131b0c3a3d7 100644
--- a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/GreenDonut.Data.EntityFramework.Tests.csproj
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/GreenDonut.Data.EntityFramework.Tests.csproj
@@ -13,8 +13,10 @@
+
+
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/PagingHelperPostgreSqlNullableTests.cs b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/PagingHelperPostgreSqlNullableTests.cs
new file mode 100644
index 00000000000..42894406b4b
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/PagingHelperPostgreSqlNullableTests.cs
@@ -0,0 +1,362 @@
+using GreenDonut.Data.TestContext;
+using Squadron;
+using Record = GreenDonut.Data.TestContext.Record;
+
+namespace GreenDonut.Data;
+
+[Collection(PostgresCacheCollectionFixture.DefinitionName)]
+public class PagingHelperPostgreSqlNullableTests(PostgreSqlResource resource)
+{
+ private string CreateConnectionString()
+ => resource.GetConnectionString($"db_{Guid.NewGuid():N}");
+
+ [Fact]
+ public async Task Paging_Nullable_Ascending_Cursor_Value_NonNull()
+ {
+ // Arrange
+ using var interceptor = new CapturePagingQueryInterceptor();
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ // Act
+ var arguments = new PagingArguments(2) { NullOrdering = NullOrdering.NativeNullsLast };
+ await using var context = new NullableTestsContext(Provider.PostgreSql, connectionString);
+ var page1 = await context.Records
+ .OrderBy(t => t.Date)
+ .ThenBy(t => t.Time)
+ .ThenBy(t => t.Id)
+ .ToPageAsync(arguments);
+
+ arguments = arguments with { After = page1.CreateCursor(page1.Last!) };
+ var page2 = await context.Records
+ .OrderBy(t => t.Date)
+ .ThenBy(t => t.Time)
+ .ThenBy(t => t.Id)
+ .ToPageAsync(arguments);
+
+ // Assert
+ var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework);
+ snapshot.Add(page1);
+ snapshot.Add(page2);
+ snapshot.AddSql(interceptor);
+ snapshot.MatchMarkdownSnapshot();
+ }
+
+ [Fact]
+ public async Task Paging_Nullable_Descending_Cursor_Value_NonNull()
+ {
+ // Arrange
+ using var interceptor = new CapturePagingQueryInterceptor();
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ // Act
+ var arguments = new PagingArguments(2) { NullOrdering = NullOrdering.NativeNullsLast };
+ await using var context = new NullableTestsContext(Provider.PostgreSql, connectionString);
+ var page1 = await context.Records
+ .OrderByDescending(t => t.Date)
+ .ThenByDescending(t => t.Time)
+ .ThenByDescending(t => t.Id)
+ .ToPageAsync(arguments);
+
+ arguments = arguments with { After = page1.CreateCursor(page1.Last!) };
+ var page2 = await context.Records
+ .OrderByDescending(t => t.Date)
+ .ThenByDescending(t => t.Time)
+ .ThenByDescending(t => t.Id)
+ .ToPageAsync(arguments);
+
+ // Assert
+ var snapshot = new Snapshot(
+ postFix: TestEnvironment.TargetFramework == "NET10_0"
+ ? TestEnvironment.TargetFramework
+ : null);
+ snapshot.Add(page1);
+ snapshot.Add(page2);
+ snapshot.AddSql(interceptor);
+ snapshot.MatchMarkdownSnapshot();
+ }
+
+ [Fact]
+ public async Task Paging_Nullable_Ascending_Cursor_Value_Null()
+ {
+ // Arrange
+ using var interceptor = new CapturePagingQueryInterceptor();
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ // Act
+ var arguments = new PagingArguments(3) { NullOrdering = NullOrdering.NativeNullsLast };
+ await using var context = new NullableTestsContext(Provider.PostgreSql, connectionString);
+ var page1 = await context.Records
+ .OrderBy(t => t.Date)
+ .ThenBy(t => t.Time)
+ .ThenBy(t => t.Id)
+ .ToPageAsync(arguments);
+
+ arguments = arguments with { After = page1.CreateCursor(page1.Last!) };
+ var page2 = await context.Records
+ .OrderBy(t => t.Date)
+ .ThenBy(t => t.Time)
+ .ThenBy(t => t.Id)
+ .ToPageAsync(arguments);
+
+ // Assert
+ var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework);
+ snapshot.Add(page1);
+ snapshot.Add(page2);
+ snapshot.AddSql(interceptor);
+ snapshot.MatchMarkdownSnapshot();
+ }
+
+ [Fact]
+ public async Task Paging_Nullable_Descending_Cursor_Value_Null()
+ {
+ // Arrange
+ using var interceptor = new CapturePagingQueryInterceptor();
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ // Act
+ var arguments = new PagingArguments(3) { NullOrdering = NullOrdering.NativeNullsLast };
+ await using var context = new NullableTestsContext(Provider.PostgreSql, connectionString);
+ var page1 = await context.Records
+ .OrderByDescending(t => t.Date)
+ .ThenByDescending(t => t.Time)
+ .ThenByDescending(t => t.Id)
+ .ToPageAsync(arguments);
+
+ arguments = arguments with { After = page1.CreateCursor(page1.Last!) };
+ var page2 = await context.Records
+ .OrderByDescending(t => t.Date)
+ .ThenByDescending(t => t.Time)
+ .ThenByDescending(t => t.Id)
+ .ToPageAsync(arguments);
+
+ // Assert
+ var snapshot = new Snapshot(
+ postFix: TestEnvironment.TargetFramework == "NET10_0"
+ ? TestEnvironment.TargetFramework
+ : null);
+ snapshot.Add(page1);
+ snapshot.Add(page2);
+ snapshot.AddSql(interceptor);
+ snapshot.MatchMarkdownSnapshot();
+ }
+
+ [Fact]
+ public async Task Paging_NullableReference_Ascending_Cursor_Value_NonNull()
+ {
+ // Arrange
+ using var interceptor = new CapturePagingQueryInterceptor();
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ // Act
+ var arguments = new PagingArguments(2) { NullOrdering = NullOrdering.NativeNullsLast };
+ await using var context = new NullableTestsContext(Provider.PostgreSql, connectionString);
+ var page1 = await context.Records
+ .OrderBy(t => t.Date)
+ .ThenBy(t => t.String)
+ .ThenBy(t => t.Id)
+ .ToPageAsync(arguments);
+
+ arguments = arguments with { After = page1.CreateCursor(page1.Last!) };
+ var page2 = await context.Records
+ .OrderBy(t => t.Date)
+ .ThenBy(t => t.String)
+ .ThenBy(t => t.Id)
+ .ToPageAsync(arguments);
+
+ // Assert
+ var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework);
+ snapshot.Add(page1);
+ snapshot.Add(page2);
+ snapshot.AddSql(interceptor);
+ snapshot.MatchMarkdownSnapshot();
+ }
+
+ [Fact]
+ public async Task Paging_NullableReference_Descending_Cursor_Value_NonNull()
+ {
+ // Arrange
+ using var interceptor = new CapturePagingQueryInterceptor();
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ // Act
+ var arguments = new PagingArguments(2) { NullOrdering = NullOrdering.NativeNullsLast };
+ await using var context = new NullableTestsContext(Provider.PostgreSql, connectionString);
+ var page1 = await context.Records
+ .OrderByDescending(t => t.Date)
+ .ThenByDescending(t => t.String)
+ .ThenByDescending(t => t.Id)
+ .ToPageAsync(arguments);
+
+ arguments = arguments with { After = page1.CreateCursor(page1.Last!) };
+ var page2 = await context.Records
+ .OrderByDescending(t => t.Date)
+ .ThenByDescending(t => t.String)
+ .ThenByDescending(t => t.Id)
+ .ToPageAsync(arguments);
+
+ // Assert
+ var snapshot = new Snapshot(
+ postFix: TestEnvironment.TargetFramework == "NET10_0"
+ ? TestEnvironment.TargetFramework
+ : null);
+ snapshot.Add(page1);
+ snapshot.Add(page2);
+ snapshot.AddSql(interceptor);
+ snapshot.MatchMarkdownSnapshot();
+ }
+
+ [Fact]
+ public async Task Paging_NullableReference_Ascending_Cursor_Value_Null()
+ {
+ // Arrange
+ using var interceptor = new CapturePagingQueryInterceptor();
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ // Act
+ var arguments = new PagingArguments(3) { NullOrdering = NullOrdering.NativeNullsLast };
+ await using var context = new NullableTestsContext(Provider.PostgreSql, connectionString);
+ var page1 = await context.Records
+ .OrderBy(t => t.Date)
+ .ThenBy(t => t.String)
+ .ThenBy(t => t.Id)
+ .ToPageAsync(arguments);
+
+ arguments = arguments with { After = page1.CreateCursor(page1.Last!) };
+ var page2 = await context.Records
+ .OrderBy(t => t.Date)
+ .ThenBy(t => t.String)
+ .ThenBy(t => t.Id)
+ .ToPageAsync(arguments);
+
+ // Assert
+ var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework);
+ snapshot.Add(page1);
+ snapshot.Add(page2);
+ snapshot.AddSql(interceptor);
+ snapshot.MatchMarkdownSnapshot();
+ }
+
+ [Fact]
+ public async Task Paging_NullableReference_Descending_Cursor_Value_Null()
+ {
+ // Arrange
+ using var interceptor = new CapturePagingQueryInterceptor();
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ // Act
+ var arguments = new PagingArguments(3) { NullOrdering = NullOrdering.NativeNullsLast };
+ await using var context = new NullableTestsContext(Provider.PostgreSql, connectionString);
+ var page1 = await context.Records
+ .OrderByDescending(t => t.Date)
+ .ThenByDescending(t => t.String)
+ .ThenByDescending(t => t.Id)
+ .ToPageAsync(arguments);
+
+ arguments = arguments with { After = page1.CreateCursor(page1.Last!) };
+ var page2 = await context.Records
+ .OrderByDescending(t => t.Date)
+ .ThenByDescending(t => t.String)
+ .ThenByDescending(t => t.Id)
+ .ToPageAsync(arguments);
+
+ // Assert
+ var snapshot = new Snapshot(
+ postFix: TestEnvironment.TargetFramework == "NET10_0"
+ ? TestEnvironment.TargetFramework
+ : null);
+ snapshot.Add(page1);
+ snapshot.Add(page2);
+ snapshot.AddSql(interceptor);
+ snapshot.MatchMarkdownSnapshot();
+ }
+
+ [Fact]
+ public async Task Paging_Nullable_Throws_When_NullOrdering_Not_Specified()
+ {
+ // Arrange
+ using var interceptor = new CapturePagingQueryInterceptor();
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ // Act
+ async Task Act()
+ {
+ var arguments = new PagingArguments(2);
+ await using var context
+ = new NullableTestsContext(Provider.PostgreSql, connectionString);
+ var page1 = await context.Records
+ .OrderBy(t => t.Date)
+ .ThenBy(t => t.Time)
+ .ThenBy(t => t.Id)
+ .ToPageAsync(arguments);
+
+ arguments = arguments with { After = page1.CreateCursor(page1.Last!) };
+ await context.Records
+ .OrderBy(t => t.Date)
+ .ThenBy(t => t.Time)
+ .ThenBy(t => t.Id)
+ .ToPageAsync(arguments);
+ }
+
+ // Assert
+ Assert.Equal(
+ "The NullOrdering option must be specified in the paging options or arguments when "
+ + "using nullable keys.",
+ (await Assert.ThrowsAsync(Act)).Message);
+ }
+
+ private static async Task SeedAsync(string connectionString)
+ {
+ await using var context = new NullableTestsContext(Provider.PostgreSql, connectionString);
+ await context.Database.EnsureCreatedAsync();
+
+ // https://stackoverflow.com/questions/68971695/cursor-pagination-prev-next-with-null-values
+ // ... with the addition of a String property to test nullable reference types.
+ context.Records.AddRange(
+ new Record
+ {
+ Id = Guid.Parse("68a5c7c2-1234-4def-bc01-9f1a23456789"),
+ Date = new DateOnly(2017, 10, 28),
+ Time = new TimeOnly(22, 00, 00),
+ String = "22:00:00"
+ },
+ new Record
+ {
+ Id = Guid.Parse("d3b7e9f1-4567-4abc-a102-8c2b34567890"),
+ Date = new DateOnly(2017, 11, 03),
+ Time = null,
+ String = null
+ },
+ new Record
+ {
+ Id = Guid.Parse("dd8f3a21-89ab-4cde-a203-7d3c45678901"),
+ Date = new DateOnly(2017, 11, 03),
+ Time = new TimeOnly(21, 45, 00),
+ String = "21:45:00"
+ },
+ new Record
+ {
+ Id = Guid.Parse("62ce9d54-2345-4f01-b304-6e4d56789012"),
+ Date = new DateOnly(2017, 11, 04),
+ Time = new TimeOnly(14, 00, 00),
+ String = "14:00:00"
+ },
+ new Record
+ {
+ Id = Guid.Parse("a1d5b763-6789-4f23-c405-5f5e67890123"),
+ Date = new DateOnly(2017, 11, 04),
+ Time = new TimeOnly(19, 40, 00),
+ String = "19:40:00"
+ });
+
+ await context.SaveChangesAsync();
+ }
+}
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/PagingHelperSqlServerNullableTests.cs b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/PagingHelperSqlServerNullableTests.cs
new file mode 100644
index 00000000000..84eaf510b00
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/PagingHelperSqlServerNullableTests.cs
@@ -0,0 +1,315 @@
+using GreenDonut.Data.TestContext;
+using Squadron;
+using Record = GreenDonut.Data.TestContext.Record;
+
+namespace GreenDonut.Data;
+
+[Collection(SqlServerCacheCollectionFixture.DefinitionName)]
+public class PagingHelperSqlServerNullableTests(SqlServerResource resource)
+{
+ private string CreateConnectionString()
+ => resource.CreateConnectionString($"db_{Guid.NewGuid():N}");
+
+ [Fact]
+ public async Task Paging_Nullable_Ascending_Cursor_Value_NonNull()
+ {
+ // Arrange
+ using var interceptor = new CapturePagingQueryInterceptor();
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ // Act
+ var arguments = new PagingArguments(3) { NullOrdering = NullOrdering.NativeNullsFirst };
+ await using var context = new NullableTestsContext(Provider.SqlServer, connectionString);
+ var page1 = await context.Records
+ .OrderBy(t => t.Date)
+ .ThenBy(t => t.Time)
+ .ThenBy(t => t.Id)
+ .ToPageAsync(arguments);
+
+ arguments = arguments with { After = page1.CreateCursor(page1.Last!) };
+ var page2 = await context.Records
+ .OrderBy(t => t.Date)
+ .ThenBy(t => t.Time)
+ .ThenBy(t => t.Id)
+ .ToPageAsync(arguments);
+
+ // Assert
+ var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework);
+ snapshot.Add(page1);
+ snapshot.Add(page2);
+ snapshot.AddSql(interceptor);
+ snapshot.MatchMarkdownSnapshot();
+ }
+
+ [Fact]
+ public async Task Paging_Nullable_Descending_Cursor_Value_NonNull()
+ {
+ // Arrange
+ using var interceptor = new CapturePagingQueryInterceptor();
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ // Act
+ var arguments = new PagingArguments(3) { NullOrdering = NullOrdering.NativeNullsFirst };
+ await using var context = new NullableTestsContext(Provider.SqlServer, connectionString);
+ var page1 = await context.Records
+ .OrderByDescending(t => t.Date)
+ .ThenByDescending(t => t.Time)
+ .ThenByDescending(t => t.Id)
+ .ToPageAsync(arguments);
+
+ arguments = arguments with { After = page1.CreateCursor(page1.Last!) };
+ var page2 = await context.Records
+ .OrderByDescending(t => t.Date)
+ .ThenByDescending(t => t.Time)
+ .ThenByDescending(t => t.Id)
+ .ToPageAsync(arguments);
+
+ // Assert
+ var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework);
+ snapshot.Add(page1);
+ snapshot.Add(page2);
+ snapshot.AddSql(interceptor);
+ snapshot.MatchMarkdownSnapshot();
+ }
+
+ [Fact]
+ public async Task Paging_Nullable_Ascending_Cursor_Value_Null()
+ {
+ // Arrange
+ using var interceptor = new CapturePagingQueryInterceptor();
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ // Act
+ var arguments = new PagingArguments(2) { NullOrdering = NullOrdering.NativeNullsFirst };
+ await using var context = new NullableTestsContext(Provider.SqlServer, connectionString);
+ var page1 = await context.Records
+ .OrderBy(t => t.Date)
+ .ThenBy(t => t.Time)
+ .ThenBy(t => t.Id)
+ .ToPageAsync(arguments);
+
+ arguments = arguments with { After = page1.CreateCursor(page1.Last!) };
+ var page2 = await context.Records
+ .OrderBy(t => t.Date)
+ .ThenBy(t => t.Time)
+ .ThenBy(t => t.Id)
+ .ToPageAsync(arguments);
+
+ // Assert
+ var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework);
+ snapshot.Add(page1);
+ snapshot.Add(page2);
+ snapshot.AddSql(interceptor);
+ snapshot.MatchMarkdownSnapshot();
+ }
+
+ [Fact]
+ public async Task Paging_Nullable_Descending_Cursor_Value_Null()
+ {
+ // Arrange
+ using var interceptor = new CapturePagingQueryInterceptor();
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ // Act
+ var arguments = new PagingArguments(4) { NullOrdering = NullOrdering.NativeNullsFirst };
+ await using var context = new NullableTestsContext(Provider.SqlServer, connectionString);
+ var page1 = await context.Records
+ .OrderByDescending(t => t.Date)
+ .ThenByDescending(t => t.Time)
+ .ThenByDescending(t => t.Id)
+ .ToPageAsync(arguments);
+
+ arguments = arguments with { After = page1.CreateCursor(page1.Last!) };
+ var page2 = await context.Records
+ .OrderByDescending(t => t.Date)
+ .ThenByDescending(t => t.Time)
+ .ThenByDescending(t => t.Id)
+ .ToPageAsync(arguments);
+
+ // Assert
+ var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework);
+ snapshot.Add(page1);
+ snapshot.Add(page2);
+ snapshot.AddSql(interceptor);
+ snapshot.MatchMarkdownSnapshot();
+ }
+
+ [Fact]
+ public async Task Paging_NullableReference_Ascending_Cursor_Value_NonNull()
+ {
+ // Arrange
+ using var interceptor = new CapturePagingQueryInterceptor();
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ // Act
+ var arguments = new PagingArguments(3) { NullOrdering = NullOrdering.NativeNullsFirst };
+ await using var context = new NullableTestsContext(Provider.SqlServer, connectionString);
+ var page1 = await context.Records
+ .OrderBy(t => t.Date)
+ .ThenBy(t => t.String)
+ .ThenBy(t => t.Id)
+ .ToPageAsync(arguments);
+
+ arguments = arguments with { After = page1.CreateCursor(page1.Last!) };
+ var page2 = await context.Records
+ .OrderBy(t => t.Date)
+ .ThenBy(t => t.String)
+ .ThenBy(t => t.Id)
+ .ToPageAsync(arguments);
+
+ // Assert
+ var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework);
+ snapshot.Add(page1);
+ snapshot.Add(page2);
+ snapshot.AddSql(interceptor);
+ snapshot.MatchMarkdownSnapshot();
+ }
+
+ [Fact]
+ public async Task Paging_NullableReference_Descending_Cursor_Value_NonNull()
+ {
+ // Arrange
+ using var interceptor = new CapturePagingQueryInterceptor();
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ // Act
+ var arguments = new PagingArguments(3) { NullOrdering = NullOrdering.NativeNullsFirst };
+ await using var context = new NullableTestsContext(Provider.SqlServer, connectionString);
+ var page1 = await context.Records
+ .OrderByDescending(t => t.Date)
+ .ThenByDescending(t => t.String)
+ .ThenByDescending(t => t.Id)
+ .ToPageAsync(arguments);
+
+ arguments = arguments with { After = page1.CreateCursor(page1.Last!) };
+ var page2 = await context.Records
+ .OrderByDescending(t => t.Date)
+ .ThenByDescending(t => t.String)
+ .ThenByDescending(t => t.Id)
+ .ToPageAsync(arguments);
+
+ // Assert
+ var snapshot = new Snapshot(TestEnvironment.TargetFramework);
+ snapshot.Add(page1);
+ snapshot.Add(page2);
+ snapshot.AddSql(interceptor);
+ snapshot.MatchMarkdownSnapshot();
+ }
+
+ [Fact]
+ public async Task Paging_NullableReference_Ascending_Cursor_Value_Null()
+ {
+ // Arrange
+ using var interceptor = new CapturePagingQueryInterceptor();
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ // Act
+ var arguments = new PagingArguments(2) { NullOrdering = NullOrdering.NativeNullsFirst };
+ await using var context = new NullableTestsContext(Provider.SqlServer, connectionString);
+ var page1 = await context.Records
+ .OrderBy(t => t.Date)
+ .ThenBy(t => t.String)
+ .ThenBy(t => t.Id)
+ .ToPageAsync(arguments);
+
+ arguments = arguments with { After = page1.CreateCursor(page1.Last!) };
+ var page2 = await context.Records
+ .OrderBy(t => t.Date)
+ .ThenBy(t => t.String)
+ .ThenBy(t => t.Id)
+ .ToPageAsync(arguments);
+
+ // Assert
+ var snapshot = new Snapshot(postFix: TestEnvironment.TargetFramework);
+ snapshot.Add(page1);
+ snapshot.Add(page2);
+ snapshot.AddSql(interceptor);
+ snapshot.MatchMarkdownSnapshot();
+ }
+
+ [Fact]
+ public async Task Paging_NullableReference_Descending_Cursor_Value_Null()
+ {
+ // Arrange
+ using var interceptor = new CapturePagingQueryInterceptor();
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ // Act
+ var arguments = new PagingArguments(4) { NullOrdering = NullOrdering.NativeNullsFirst };
+ await using var context = new NullableTestsContext(Provider.SqlServer, connectionString);
+ var page1 = await context.Records
+ .OrderByDescending(t => t.Date)
+ .ThenByDescending(t => t.String)
+ .ThenByDescending(t => t.Id)
+ .ToPageAsync(arguments);
+
+ arguments = arguments with { After = page1.CreateCursor(page1.Last!) };
+ var page2 = await context.Records
+ .OrderByDescending(t => t.Date)
+ .ThenByDescending(t => t.String)
+ .ThenByDescending(t => t.Id)
+ .ToPageAsync(arguments);
+
+ // Assert
+ var snapshot = new Snapshot(TestEnvironment.TargetFramework);
+ snapshot.Add(page1);
+ snapshot.Add(page2);
+ snapshot.AddSql(interceptor);
+ snapshot.MatchMarkdownSnapshot();
+ }
+
+ private static async Task SeedAsync(string connectionString)
+ {
+ await using var context = new NullableTestsContext(Provider.SqlServer, connectionString);
+ await context.Database.EnsureCreatedAsync();
+
+ // https://stackoverflow.com/questions/68971695/cursor-pagination-prev-next-with-null-values
+ // ... with the addition of a String property to test nullable reference types.
+ context.Records.AddRange(
+ new Record
+ {
+ Id = Guid.Parse("68a5c7c2-1234-4def-bc01-9f1a23456789"),
+ Date = new DateOnly(2017, 10, 28),
+ Time = new TimeOnly(22, 00, 00),
+ String = "22:00:00"
+ },
+ new Record
+ {
+ Id = Guid.Parse("d3b7e9f1-4567-4abc-a102-8c2b34567890"),
+ Date = new DateOnly(2017, 11, 03),
+ Time = null,
+ String = null
+ },
+ new Record
+ {
+ Id = Guid.Parse("dd8f3a21-89ab-4cde-a203-7d3c45678901"),
+ Date = new DateOnly(2017, 11, 03),
+ Time = new TimeOnly(21, 45, 00),
+ String = "21:45:00"
+ },
+ new Record
+ {
+ Id = Guid.Parse("62ce9d54-2345-4f01-b304-6e4d56789012"),
+ Date = new DateOnly(2017, 11, 04),
+ Time = new TimeOnly(14, 00, 00),
+ String = "14:00:00"
+ },
+ new Record
+ {
+ Id = Guid.Parse("a1d5b763-6789-4f23-c405-5f5e67890123"),
+ Date = new DateOnly(2017, 11, 04),
+ Time = new TimeOnly(19, 40, 00),
+ String = "19:40:00"
+ });
+
+ await context.SaveChangesAsync();
+ }
+}
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/SqlServerCacheCollectionFixture.cs b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/SqlServerCacheCollectionFixture.cs
new file mode 100644
index 00000000000..53337da749b
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/SqlServerCacheCollectionFixture.cs
@@ -0,0 +1,9 @@
+using Squadron;
+
+namespace GreenDonut.Data;
+
+[CollectionDefinition(DefinitionName)]
+public class SqlServerCacheCollectionFixture : ICollectionFixture
+{
+ internal const string DefinitionName = "SqlServerResource";
+}
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/TestContext/NullableTestsContext.cs b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/TestContext/NullableTestsContext.cs
new file mode 100644
index 00000000000..67c10249666
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/TestContext/NullableTestsContext.cs
@@ -0,0 +1,37 @@
+using Microsoft.EntityFrameworkCore;
+
+namespace GreenDonut.Data.TestContext;
+
+public class NullableTestsContext(Provider provider, string connectionString) : DbContext
+{
+ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
+ {
+ switch (provider)
+ {
+ case Provider.PostgreSql:
+ optionsBuilder.UseNpgsql(connectionString);
+ break;
+ case Provider.SqlServer:
+ optionsBuilder.UseSqlServer(connectionString);
+ break;
+ default:
+ throw new InvalidOperationException();
+ }
+ }
+
+ public DbSet Records { get; set; } = null!;
+}
+
+public class Record
+{
+ public Guid Id { get; set; }
+ public DateOnly? Date { get; set; }
+ public TimeOnly? Time { get; set; }
+ public string? String { get; set; }
+}
+
+public enum Provider
+{
+ PostgreSql,
+ SqlServer
+}
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET10_0.md
index adba1b2b006..d3e440c35d5 100644
--- a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET10_0.md
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET10_0.md
@@ -16,7 +16,7 @@ LIMIT @p
## Expression 0
```text
-[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(6)
+[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) > 0)))).Take(6)
```
## Result 3
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET8_0.md
index 017f339abfd..dcc9055a17d 100644
--- a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET8_0.md
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET8_0.md
@@ -16,7 +16,7 @@ LIMIT @__p_2
## Expression 0
```text
-[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(6)
+[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.Int32]).value) > 0)))).Take(6)
```
## Result 3
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET9_0.md
index 017f339abfd..dcc9055a17d 100644
--- a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET9_0.md
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET9_0.md
@@ -16,7 +16,7 @@ LIMIT @__p_2
## Expression 0
```text
-[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(6)
+[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.Int32]).value) > 0)))).Take(6)
```
## Result 3
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET10_0.md
index 50e11481562..40da3598267 100644
--- a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET10_0.md
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET10_0.md
@@ -16,7 +16,7 @@ LIMIT @p
## Expression 0
```text
-[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) < 0)))).Take(6)
+[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) < 0)))).Take(6)
```
## Result 3
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET8_0.md
index d3dc3984660..ef3c0174609 100644
--- a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET8_0.md
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET8_0.md
@@ -16,7 +16,7 @@ LIMIT @__p_2
## Expression 0
```text
-[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) < 0)))).Take(6)
+[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.Int32]).value) < 0)))).Take(6)
```
## Result 3
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET9_0.md
index d3dc3984660..ef3c0174609 100644
--- a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET9_0.md
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET9_0.md
@@ -16,7 +16,7 @@ LIMIT @__p_2
## Expression 0
```text
-[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) < 0)))).Take(6)
+[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.Int32]).value) < 0)))).Take(6)
```
## Result 3
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET10_0.md
new file mode 100644
index 00000000000..869546d1b7a
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET10_0.md
@@ -0,0 +1,63 @@
+# Paging_NullableReference_Ascending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @p='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date", r."String", r."Id"
+LIMIT @p
+```
+
+## SQL 1
+
+```sql
+-- @value='11/03/2017' (DbType = Date)
+-- @value1='21:45:00'
+-- @value4='dd8f3a21-89ab-4cde-a203-7d3c45678901'
+-- @p='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" IS NULL OR r."Date" > @value OR (r."Date" = @value AND (r."String" IS NULL OR r."String" > @value1)) OR (r."Date" = @value AND r."String" IS NOT NULL AND r."String" = @value1 AND r."Id" > @value4)
+ORDER BY r."Date", r."String", r."Id"
+LIMIT @p
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET8_0.md
new file mode 100644
index 00000000000..0a313f9d9b4
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET8_0.md
@@ -0,0 +1,63 @@
+# Paging_NullableReference_Ascending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @__p_0='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date", r."String", r."Id"
+LIMIT @__p_0
+```
+
+## SQL 1
+
+```sql
+-- @__value_0='11/03/2017' (DbType = Date)
+-- @__value_1='21:45:00'
+-- @__value_2='dd8f3a21-89ab-4cde-a203-7d3c45678901'
+-- @__p_3='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" IS NULL OR r."Date" > @__value_0 OR (r."Date" = @__value_0 AND (r."String" IS NULL OR r."String" > @__value_1)) OR (r."Date" = @__value_0 AND r."String" IS NOT NULL AND r."String" = @__value_1 AND r."Id" > @__value_2)
+ORDER BY r."Date", r."String", r."Id"
+LIMIT @__p_3
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET9_0.md
new file mode 100644
index 00000000000..0a313f9d9b4
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET9_0.md
@@ -0,0 +1,63 @@
+# Paging_NullableReference_Ascending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @__p_0='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date", r."String", r."Id"
+LIMIT @__p_0
+```
+
+## SQL 1
+
+```sql
+-- @__value_0='11/03/2017' (DbType = Date)
+-- @__value_1='21:45:00'
+-- @__value_2='dd8f3a21-89ab-4cde-a203-7d3c45678901'
+-- @__p_3='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" IS NULL OR r."Date" > @__value_0 OR (r."Date" = @__value_0 AND (r."String" IS NULL OR r."String" > @__value_1)) OR (r."Date" = @__value_0 AND r."String" IS NOT NULL AND r."String" = @__value_1 AND r."Id" > @__value_2)
+ORDER BY r."Date", r."String", r."Id"
+LIMIT @__p_3
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET10_0.md
new file mode 100644
index 00000000000..07b41caf032
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET10_0.md
@@ -0,0 +1,68 @@
+# Paging_NullableReference_Ascending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @p='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date", r."String", r."Id"
+LIMIT @p
+```
+
+## SQL 1
+
+```sql
+-- @value='11/03/2017' (DbType = Date)
+-- @value2='d3b7e9f1-4567-4abc-a102-8c2b34567890'
+-- @p='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" IS NULL OR r."Date" > @value OR (r."Date" = @value AND r."String" IS NULL AND r."Id" > @value2)
+ORDER BY r."Date", r."String", r."Id"
+LIMIT @p
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET8_0.md
new file mode 100644
index 00000000000..23643db5da9
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET8_0.md
@@ -0,0 +1,68 @@
+# Paging_NullableReference_Ascending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @__p_0='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date", r."String", r."Id"
+LIMIT @__p_0
+```
+
+## SQL 1
+
+```sql
+-- @__value_0='11/03/2017' (DbType = Date)
+-- @__value_1='d3b7e9f1-4567-4abc-a102-8c2b34567890'
+-- @__p_2='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" IS NULL OR r."Date" > @__value_0 OR (r."Date" = @__value_0 AND r."String" IS NULL AND r."Id" > @__value_1)
+ORDER BY r."Date", r."String", r."Id"
+LIMIT @__p_2
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET9_0.md
new file mode 100644
index 00000000000..23643db5da9
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET9_0.md
@@ -0,0 +1,68 @@
+# Paging_NullableReference_Ascending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @__p_0='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date", r."String", r."Id"
+LIMIT @__p_0
+```
+
+## SQL 1
+
+```sql
+-- @__value_0='11/03/2017' (DbType = Date)
+-- @__value_1='d3b7e9f1-4567-4abc-a102-8c2b34567890'
+-- @__p_2='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" IS NULL OR r."Date" > @__value_0 OR (r."Date" = @__value_0 AND r."String" IS NULL AND r."Id" > @__value_1)
+ORDER BY r."Date", r."String", r."Id"
+LIMIT @__p_2
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull.md
new file mode 100644
index 00000000000..84e4e3e85ba
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull.md
@@ -0,0 +1,63 @@
+# Paging_NullableReference_Descending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @__p_0='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date" DESC, r."String" DESC, r."Id" DESC
+LIMIT @__p_0
+```
+
+## SQL 1
+
+```sql
+-- @__value_0='11/04/2017' (DbType = Date)
+-- @__value_1='14:00:00'
+-- @__value_2='62ce9d54-2345-4f01-b304-6e4d56789012'
+-- @__p_3='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" < @__value_0 OR (r."Date" IS NOT NULL AND r."Date" = @__value_0 AND r."String" < @__value_1) OR (r."Date" IS NOT NULL AND r."Date" = @__value_0 AND r."String" IS NOT NULL AND r."String" = @__value_1 AND r."Id" < @__value_2)
+ORDER BY r."Date" DESC, r."String" DESC, r."Id" DESC
+LIMIT @__p_3
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET10_0.md
new file mode 100644
index 00000000000..507cabd8ac5
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET10_0.md
@@ -0,0 +1,63 @@
+# Paging_NullableReference_Descending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @p='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date" DESC, r."String" DESC, r."Id" DESC
+LIMIT @p
+```
+
+## SQL 1
+
+```sql
+-- @value='11/04/2017' (DbType = Date)
+-- @value1='14:00:00'
+-- @value4='62ce9d54-2345-4f01-b304-6e4d56789012'
+-- @p='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" < @value OR (r."Date" IS NOT NULL AND r."Date" = @value AND r."String" < @value1) OR (r."Date" IS NOT NULL AND r."Date" = @value AND r."String" IS NOT NULL AND r."String" = @value1 AND r."Id" < @value4)
+ORDER BY r."Date" DESC, r."String" DESC, r."Id" DESC
+LIMIT @p
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null.md
new file mode 100644
index 00000000000..5f38117a332
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null.md
@@ -0,0 +1,68 @@
+# Paging_NullableReference_Descending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @__p_0='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date" DESC, r."String" DESC, r."Id" DESC
+LIMIT @__p_0
+```
+
+## SQL 1
+
+```sql
+-- @__value_0='11/03/2017' (DbType = Date)
+-- @__value_1='d3b7e9f1-4567-4abc-a102-8c2b34567890'
+-- @__p_2='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" < @__value_0 OR (r."Date" IS NOT NULL AND r."Date" = @__value_0 AND r."String" IS NOT NULL) OR (r."Date" IS NOT NULL AND r."Date" = @__value_0 AND r."String" IS NULL AND r."Id" < @__value_1)
+ORDER BY r."Date" DESC, r."String" DESC, r."Id" DESC
+LIMIT @__p_2
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET10_0.md
new file mode 100644
index 00000000000..4de1c82032c
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET10_0.md
@@ -0,0 +1,68 @@
+# Paging_NullableReference_Descending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @p='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date" DESC, r."String" DESC, r."Id" DESC
+LIMIT @p
+```
+
+## SQL 1
+
+```sql
+-- @value='11/03/2017' (DbType = Date)
+-- @value2='d3b7e9f1-4567-4abc-a102-8c2b34567890'
+-- @p='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" < @value OR (r."Date" IS NOT NULL AND r."Date" = @value AND r."String" IS NOT NULL) OR (r."Date" IS NOT NULL AND r."Date" = @value AND r."String" IS NULL AND r."Id" < @value2)
+ORDER BY r."Date" DESC, r."String" DESC, r."Id" DESC
+LIMIT @p
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET10_0.md
new file mode 100644
index 00000000000..0c5877dbe0a
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET10_0.md
@@ -0,0 +1,63 @@
+# Paging_Nullable_Ascending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @p='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date", r."Time", r."Id"
+LIMIT @p
+```
+
+## SQL 1
+
+```sql
+-- @value='11/03/2017' (DbType = Date)
+-- @value1='21:45' (DbType = Time)
+-- @value4='dd8f3a21-89ab-4cde-a203-7d3c45678901'
+-- @p='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" IS NULL OR r."Date" > @value OR (r."Date" = @value AND (r."Time" IS NULL OR r."Time" > @value1)) OR (r."Date" = @value AND r."Time" IS NOT NULL AND r."Time" = @value1 AND r."Id" > @value4)
+ORDER BY r."Date", r."Time", r."Id"
+LIMIT @p
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET8_0.md
new file mode 100644
index 00000000000..f13bac10ef0
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET8_0.md
@@ -0,0 +1,63 @@
+# Paging_Nullable_Ascending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @__p_0='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date", r."Time", r."Id"
+LIMIT @__p_0
+```
+
+## SQL 1
+
+```sql
+-- @__value_0='11/03/2017' (DbType = Date)
+-- @__value_1='21:45' (DbType = Time)
+-- @__value_2='dd8f3a21-89ab-4cde-a203-7d3c45678901'
+-- @__p_3='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" IS NULL OR r."Date" > @__value_0 OR (r."Date" = @__value_0 AND (r."Time" IS NULL OR r."Time" > @__value_1)) OR (r."Date" = @__value_0 AND r."Time" IS NOT NULL AND r."Time" = @__value_1 AND r."Id" > @__value_2)
+ORDER BY r."Date", r."Time", r."Id"
+LIMIT @__p_3
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET9_0.md
new file mode 100644
index 00000000000..f13bac10ef0
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET9_0.md
@@ -0,0 +1,63 @@
+# Paging_Nullable_Ascending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @__p_0='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date", r."Time", r."Id"
+LIMIT @__p_0
+```
+
+## SQL 1
+
+```sql
+-- @__value_0='11/03/2017' (DbType = Date)
+-- @__value_1='21:45' (DbType = Time)
+-- @__value_2='dd8f3a21-89ab-4cde-a203-7d3c45678901'
+-- @__p_3='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" IS NULL OR r."Date" > @__value_0 OR (r."Date" = @__value_0 AND (r."Time" IS NULL OR r."Time" > @__value_1)) OR (r."Date" = @__value_0 AND r."Time" IS NOT NULL AND r."Time" = @__value_1 AND r."Id" > @__value_2)
+ORDER BY r."Date", r."Time", r."Id"
+LIMIT @__p_3
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET10_0.md
new file mode 100644
index 00000000000..9494ad16ea2
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET10_0.md
@@ -0,0 +1,68 @@
+# Paging_Nullable_Ascending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @p='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date", r."Time", r."Id"
+LIMIT @p
+```
+
+## SQL 1
+
+```sql
+-- @value='11/03/2017' (DbType = Date)
+-- @value2='d3b7e9f1-4567-4abc-a102-8c2b34567890'
+-- @p='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" IS NULL OR r."Date" > @value OR (r."Date" = @value AND r."Time" IS NULL AND r."Id" > @value2)
+ORDER BY r."Date", r."Time", r."Id"
+LIMIT @p
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET8_0.md
new file mode 100644
index 00000000000..ab8d617e957
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET8_0.md
@@ -0,0 +1,68 @@
+# Paging_Nullable_Ascending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @__p_0='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date", r."Time", r."Id"
+LIMIT @__p_0
+```
+
+## SQL 1
+
+```sql
+-- @__value_0='11/03/2017' (DbType = Date)
+-- @__value_1='d3b7e9f1-4567-4abc-a102-8c2b34567890'
+-- @__p_2='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" IS NULL OR r."Date" > @__value_0 OR (r."Date" = @__value_0 AND r."Time" IS NULL AND r."Id" > @__value_1)
+ORDER BY r."Date", r."Time", r."Id"
+LIMIT @__p_2
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET9_0.md
new file mode 100644
index 00000000000..ab8d617e957
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET9_0.md
@@ -0,0 +1,68 @@
+# Paging_Nullable_Ascending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @__p_0='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date", r."Time", r."Id"
+LIMIT @__p_0
+```
+
+## SQL 1
+
+```sql
+-- @__value_0='11/03/2017' (DbType = Date)
+-- @__value_1='d3b7e9f1-4567-4abc-a102-8c2b34567890'
+-- @__p_2='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" IS NULL OR r."Date" > @__value_0 OR (r."Date" = @__value_0 AND r."Time" IS NULL AND r."Id" > @__value_1)
+ORDER BY r."Date", r."Time", r."Id"
+LIMIT @__p_2
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull.md
new file mode 100644
index 00000000000..4ce9d101b10
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull.md
@@ -0,0 +1,63 @@
+# Paging_Nullable_Descending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @__p_0='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date" DESC, r."Time" DESC, r."Id" DESC
+LIMIT @__p_0
+```
+
+## SQL 1
+
+```sql
+-- @__value_0='11/04/2017' (DbType = Date)
+-- @__value_1='14:00' (DbType = Time)
+-- @__value_2='62ce9d54-2345-4f01-b304-6e4d56789012'
+-- @__p_3='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" < @__value_0 OR (r."Date" IS NOT NULL AND r."Date" = @__value_0 AND r."Time" < @__value_1) OR (r."Date" IS NOT NULL AND r."Date" = @__value_0 AND r."Time" IS NOT NULL AND r."Time" = @__value_1 AND r."Id" < @__value_2)
+ORDER BY r."Date" DESC, r."Time" DESC, r."Id" DESC
+LIMIT @__p_3
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET10_0.md
new file mode 100644
index 00000000000..56a5251f432
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET10_0.md
@@ -0,0 +1,63 @@
+# Paging_Nullable_Descending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @p='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date" DESC, r."Time" DESC, r."Id" DESC
+LIMIT @p
+```
+
+## SQL 1
+
+```sql
+-- @value='11/04/2017' (DbType = Date)
+-- @value1='14:00' (DbType = Time)
+-- @value4='62ce9d54-2345-4f01-b304-6e4d56789012'
+-- @p='3'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" < @value OR (r."Date" IS NOT NULL AND r."Date" = @value AND r."Time" < @value1) OR (r."Date" IS NOT NULL AND r."Date" = @value AND r."Time" IS NOT NULL AND r."Time" = @value1 AND r."Id" < @value4)
+ORDER BY r."Date" DESC, r."Time" DESC, r."Id" DESC
+LIMIT @p
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_Null.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_Null.md
new file mode 100644
index 00000000000..bcdaa85a7b6
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_Null.md
@@ -0,0 +1,68 @@
+# Paging_Nullable_Descending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @__p_0='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date" DESC, r."Time" DESC, r."Id" DESC
+LIMIT @__p_0
+```
+
+## SQL 1
+
+```sql
+-- @__value_0='11/03/2017' (DbType = Date)
+-- @__value_1='d3b7e9f1-4567-4abc-a102-8c2b34567890'
+-- @__p_2='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" < @__value_0 OR (r."Date" IS NOT NULL AND r."Date" = @__value_0 AND r."Time" IS NOT NULL) OR (r."Date" IS NOT NULL AND r."Date" = @__value_0 AND r."Time" IS NULL AND r."Id" < @__value_1)
+ORDER BY r."Date" DESC, r."Time" DESC, r."Id" DESC
+LIMIT @__p_2
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET10_0.md
new file mode 100644
index 00000000000..1abf3942fda
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperPostgreSqlNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET10_0.md
@@ -0,0 +1,68 @@
+# Paging_Nullable_Descending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+-- @p='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+ORDER BY r."Date" DESC, r."Time" DESC, r."Id" DESC
+LIMIT @p
+```
+
+## SQL 1
+
+```sql
+-- @value='11/03/2017' (DbType = Date)
+-- @value2='d3b7e9f1-4567-4abc-a102-8c2b34567890'
+-- @p='4'
+SELECT r."Id", r."Date", r."String", r."Time"
+FROM "Records" AS r
+WHERE r."Date" < @value OR (r."Date" IS NOT NULL AND r."Date" = @value AND r."Time" IS NOT NULL) OR (r."Date" IS NOT NULL AND r."Date" = @value AND r."Time" IS NULL AND r."Id" < @value2)
+ORDER BY r."Date" DESC, r."Time" DESC, r."Id" DESC
+LIMIT @p
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET10_0.md
new file mode 100644
index 00000000000..dddf0508c08
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET10_0.md
@@ -0,0 +1,69 @@
+# Paging_NullableReference_Ascending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @p int = 4;
+
+SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date], [r].[String], [r].[Id]
+```
+
+## SQL 1
+
+```sql
+DECLARE @p int = 4;
+DECLARE @value date = '2017-11-03';
+DECLARE @value1 nvarchar(4000) = N'21:45:00';
+DECLARE @value4 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901';
+
+SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] > @value OR ([r].[Date] IS NOT NULL AND [r].[Date] = @value AND [r].[String] > @value1) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @value AND [r].[String] IS NOT NULL AND [r].[String] = @value1 AND [r].[Id] > @value4)
+ORDER BY [r].[Date], [r].[String], [r].[Id]
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET8_0.md
new file mode 100644
index 00000000000..003c5eadfe2
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET8_0.md
@@ -0,0 +1,69 @@
+# Paging_NullableReference_Ascending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @__p_0 int = 4;
+
+SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date], [r].[String], [r].[Id]
+```
+
+## SQL 1
+
+```sql
+DECLARE @__p_3 int = 4;
+DECLARE @__value_0 date = '2017-11-03';
+DECLARE @__value_1 nvarchar(4000) = N'21:45:00';
+DECLARE @__value_2 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901';
+
+SELECT TOP(@__p_3) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] > @__value_0 OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[String] > @__value_1) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[String] IS NOT NULL AND [r].[String] = @__value_1 AND [r].[Id] > @__value_2)
+ORDER BY [r].[Date], [r].[String], [r].[Id]
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET9_0.md
new file mode 100644
index 00000000000..003c5eadfe2
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_NonNull_NET9_0.md
@@ -0,0 +1,69 @@
+# Paging_NullableReference_Ascending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @__p_0 int = 4;
+
+SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date], [r].[String], [r].[Id]
+```
+
+## SQL 1
+
+```sql
+DECLARE @__p_3 int = 4;
+DECLARE @__value_0 date = '2017-11-03';
+DECLARE @__value_1 nvarchar(4000) = N'21:45:00';
+DECLARE @__value_2 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901';
+
+SELECT TOP(@__p_3) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] > @__value_0 OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[String] > @__value_1) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[String] IS NOT NULL AND [r].[String] = @__value_1 AND [r].[Id] > @__value_2)
+ORDER BY [r].[Date], [r].[String], [r].[Id]
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET10_0.md
new file mode 100644
index 00000000000..501b0ea2b36
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET10_0.md
@@ -0,0 +1,62 @@
+# Paging_NullableReference_Ascending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @p int = 3;
+
+SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date], [r].[String], [r].[Id]
+```
+
+## SQL 1
+
+```sql
+DECLARE @p int = 3;
+DECLARE @value date = '2017-11-03';
+DECLARE @value2 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890';
+
+SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] > @value OR ([r].[Date] IS NOT NULL AND [r].[Date] = @value AND [r].[String] IS NOT NULL) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @value AND [r].[String] IS NULL AND [r].[Id] > @value2)
+ORDER BY [r].[Date], [r].[String], [r].[Id]
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET8_0.md
new file mode 100644
index 00000000000..5c5e2be2206
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET8_0.md
@@ -0,0 +1,62 @@
+# Paging_NullableReference_Ascending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @__p_0 int = 3;
+
+SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date], [r].[String], [r].[Id]
+```
+
+## SQL 1
+
+```sql
+DECLARE @__p_2 int = 3;
+DECLARE @__value_0 date = '2017-11-03';
+DECLARE @__value_1 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890';
+
+SELECT TOP(@__p_2) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] > @__value_0 OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[String] IS NOT NULL) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[String] IS NULL AND [r].[Id] > @__value_1)
+ORDER BY [r].[Date], [r].[String], [r].[Id]
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET9_0.md
new file mode 100644
index 00000000000..5c5e2be2206
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Ascending_Cursor_Value_Null_NET9_0.md
@@ -0,0 +1,62 @@
+# Paging_NullableReference_Ascending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @__p_0 int = 3;
+
+SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date], [r].[String], [r].[Id]
+```
+
+## SQL 1
+
+```sql
+DECLARE @__p_2 int = 3;
+DECLARE @__value_0 date = '2017-11-03';
+DECLARE @__value_1 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890';
+
+SELECT TOP(@__p_2) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] > @__value_0 OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[String] IS NOT NULL) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[String] IS NULL AND [r].[Id] > @__value_1)
+ORDER BY [r].[Date], [r].[String], [r].[Id]
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET10_0.md
new file mode 100644
index 00000000000..21bc2f28e37
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET10_0.md
@@ -0,0 +1,69 @@
+# Paging_NullableReference_Descending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @p int = 4;
+
+SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC
+```
+
+## SQL 1
+
+```sql
+DECLARE @p int = 4;
+DECLARE @value date = '2017-11-03';
+DECLARE @value1 nvarchar(4000) = N'21:45:00';
+DECLARE @value4 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901';
+
+SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] IS NULL OR [r].[Date] < @value OR ([r].[Date] = @value AND ([r].[String] IS NULL OR [r].[String] < @value1)) OR ([r].[Date] = @value AND [r].[String] IS NOT NULL AND [r].[String] = @value1 AND [r].[Id] < @value4)
+ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET8_0.md
new file mode 100644
index 00000000000..cecfe34715f
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET8_0.md
@@ -0,0 +1,69 @@
+# Paging_NullableReference_Descending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @__p_0 int = 4;
+
+SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC
+```
+
+## SQL 1
+
+```sql
+DECLARE @__p_3 int = 4;
+DECLARE @__value_0 date = '2017-11-03';
+DECLARE @__value_1 nvarchar(4000) = N'21:45:00';
+DECLARE @__value_2 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901';
+
+SELECT TOP(@__p_3) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] IS NULL OR [r].[Date] < @__value_0 OR ([r].[Date] = @__value_0 AND ([r].[String] IS NULL OR [r].[String] < @__value_1)) OR ([r].[Date] = @__value_0 AND [r].[String] IS NOT NULL AND [r].[String] = @__value_1 AND [r].[Id] < @__value_2)
+ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET9_0.md
new file mode 100644
index 00000000000..cecfe34715f
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_NonNull_NET9_0.md
@@ -0,0 +1,69 @@
+# Paging_NullableReference_Descending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @__p_0 int = 4;
+
+SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC
+```
+
+## SQL 1
+
+```sql
+DECLARE @__p_3 int = 4;
+DECLARE @__value_0 date = '2017-11-03';
+DECLARE @__value_1 nvarchar(4000) = N'21:45:00';
+DECLARE @__value_2 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901';
+
+SELECT TOP(@__p_3) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] IS NULL OR [r].[Date] < @__value_0 OR ([r].[Date] = @__value_0 AND ([r].[String] IS NULL OR [r].[String] < @__value_1)) OR ([r].[Date] = @__value_0 AND [r].[String] IS NOT NULL AND [r].[String] = @__value_1 AND [r].[Id] < @__value_2)
+ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET10_0.md
new file mode 100644
index 00000000000..f3652d06de0
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET10_0.md
@@ -0,0 +1,68 @@
+# Paging_NullableReference_Descending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @p int = 5;
+
+SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC
+```
+
+## SQL 1
+
+```sql
+DECLARE @p int = 5;
+DECLARE @value date = '2017-11-03';
+DECLARE @value2 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890';
+
+SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] IS NULL OR [r].[Date] < @value OR ([r].[Date] = @value AND [r].[String] IS NULL AND [r].[Id] < @value2)
+ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET8_0.md
new file mode 100644
index 00000000000..13c61301192
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET8_0.md
@@ -0,0 +1,68 @@
+# Paging_NullableReference_Descending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @__p_0 int = 5;
+
+SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC
+```
+
+## SQL 1
+
+```sql
+DECLARE @__p_2 int = 5;
+DECLARE @__value_0 date = '2017-11-03';
+DECLARE @__value_1 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890';
+
+SELECT TOP(@__p_2) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] IS NULL OR [r].[Date] < @__value_0 OR ([r].[Date] = @__value_0 AND [r].[String] IS NULL AND [r].[Id] < @__value_1)
+ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET9_0.md
new file mode 100644
index 00000000000..13c61301192
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_NullableReference_Descending_Cursor_Value_Null_NET9_0.md
@@ -0,0 +1,68 @@
+# Paging_NullableReference_Descending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @__p_0 int = 5;
+
+SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC
+```
+
+## SQL 1
+
+```sql
+DECLARE @__p_2 int = 5;
+DECLARE @__value_0 date = '2017-11-03';
+DECLARE @__value_1 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890';
+
+SELECT TOP(@__p_2) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] IS NULL OR [r].[Date] < @__value_0 OR ([r].[Date] = @__value_0 AND [r].[String] IS NULL AND [r].[Id] < @__value_1)
+ORDER BY [r].[Date] DESC, [r].[String] DESC, [r].[Id] DESC
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET10_0.md
new file mode 100644
index 00000000000..e29721a2b54
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET10_0.md
@@ -0,0 +1,69 @@
+# Paging_Nullable_Ascending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @p int = 4;
+
+SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date], [r].[Time], [r].[Id]
+```
+
+## SQL 1
+
+```sql
+DECLARE @p int = 4;
+DECLARE @value date = '2017-11-03';
+DECLARE @value1 time = '21:45:00';
+DECLARE @value4 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901';
+
+SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] > @value OR ([r].[Date] IS NOT NULL AND [r].[Date] = @value AND [r].[Time] > @value1) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @value AND [r].[Time] IS NOT NULL AND [r].[Time] = @value1 AND [r].[Id] > @value4)
+ORDER BY [r].[Date], [r].[Time], [r].[Id]
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET8_0.md
new file mode 100644
index 00000000000..3bfcdb86fa0
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET8_0.md
@@ -0,0 +1,69 @@
+# Paging_Nullable_Ascending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @__p_0 int = 4;
+
+SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date], [r].[Time], [r].[Id]
+```
+
+## SQL 1
+
+```sql
+DECLARE @__p_3 int = 4;
+DECLARE @__value_0 date = '2017-11-03';
+DECLARE @__value_1 time = '21:45:00';
+DECLARE @__value_2 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901';
+
+SELECT TOP(@__p_3) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] > @__value_0 OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[Time] > @__value_1) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[Time] IS NOT NULL AND [r].[Time] = @__value_1 AND [r].[Id] > @__value_2)
+ORDER BY [r].[Date], [r].[Time], [r].[Id]
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET9_0.md
new file mode 100644
index 00000000000..3bfcdb86fa0
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_NonNull_NET9_0.md
@@ -0,0 +1,69 @@
+# Paging_Nullable_Ascending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @__p_0 int = 4;
+
+SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date], [r].[Time], [r].[Id]
+```
+
+## SQL 1
+
+```sql
+DECLARE @__p_3 int = 4;
+DECLARE @__value_0 date = '2017-11-03';
+DECLARE @__value_1 time = '21:45:00';
+DECLARE @__value_2 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901';
+
+SELECT TOP(@__p_3) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] > @__value_0 OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[Time] > @__value_1) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[Time] IS NOT NULL AND [r].[Time] = @__value_1 AND [r].[Id] > @__value_2)
+ORDER BY [r].[Date], [r].[Time], [r].[Id]
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET10_0.md
new file mode 100644
index 00000000000..f1b99c1fb85
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET10_0.md
@@ -0,0 +1,62 @@
+# Paging_Nullable_Ascending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @p int = 3;
+
+SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date], [r].[Time], [r].[Id]
+```
+
+## SQL 1
+
+```sql
+DECLARE @p int = 3;
+DECLARE @value date = '2017-11-03';
+DECLARE @value2 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890';
+
+SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] > @value OR ([r].[Date] IS NOT NULL AND [r].[Date] = @value AND [r].[Time] IS NOT NULL) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @value AND [r].[Time] IS NULL AND [r].[Id] > @value2)
+ORDER BY [r].[Date], [r].[Time], [r].[Id]
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET8_0.md
new file mode 100644
index 00000000000..ae57828835d
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET8_0.md
@@ -0,0 +1,62 @@
+# Paging_Nullable_Ascending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @__p_0 int = 3;
+
+SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date], [r].[Time], [r].[Id]
+```
+
+## SQL 1
+
+```sql
+DECLARE @__p_2 int = 3;
+DECLARE @__value_0 date = '2017-11-03';
+DECLARE @__value_1 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890';
+
+SELECT TOP(@__p_2) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] > @__value_0 OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[Time] IS NOT NULL) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[Time] IS NULL AND [r].[Id] > @__value_1)
+ORDER BY [r].[Date], [r].[Time], [r].[Id]
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET9_0.md
new file mode 100644
index 00000000000..ae57828835d
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Ascending_Cursor_Value_Null_NET9_0.md
@@ -0,0 +1,62 @@
+# Paging_Nullable_Ascending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @__p_0 int = 3;
+
+SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date], [r].[Time], [r].[Id]
+```
+
+## SQL 1
+
+```sql
+DECLARE @__p_2 int = 3;
+DECLARE @__value_0 date = '2017-11-03';
+DECLARE @__value_1 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890';
+
+SELECT TOP(@__p_2) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] > @__value_0 OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[Time] IS NOT NULL) OR ([r].[Date] IS NOT NULL AND [r].[Date] = @__value_0 AND [r].[Time] IS NULL AND [r].[Id] > @__value_1)
+ORDER BY [r].[Date], [r].[Time], [r].[Id]
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET10_0.md
new file mode 100644
index 00000000000..b7f456b498b
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET10_0.md
@@ -0,0 +1,69 @@
+# Paging_Nullable_Descending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @p int = 4;
+
+SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC
+```
+
+## SQL 1
+
+```sql
+DECLARE @p int = 4;
+DECLARE @value date = '2017-11-03';
+DECLARE @value1 time = '21:45:00';
+DECLARE @value4 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901';
+
+SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] IS NULL OR [r].[Date] < @value OR ([r].[Date] = @value AND ([r].[Time] IS NULL OR [r].[Time] < @value1)) OR ([r].[Date] = @value AND [r].[Time] IS NOT NULL AND [r].[Time] = @value1 AND [r].[Id] < @value4)
+ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET8_0.md
new file mode 100644
index 00000000000..edbffb5d9d9
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET8_0.md
@@ -0,0 +1,69 @@
+# Paging_Nullable_Descending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @__p_0 int = 4;
+
+SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC
+```
+
+## SQL 1
+
+```sql
+DECLARE @__p_3 int = 4;
+DECLARE @__value_0 date = '2017-11-03';
+DECLARE @__value_1 time = '21:45:00';
+DECLARE @__value_2 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901';
+
+SELECT TOP(@__p_3) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] IS NULL OR [r].[Date] < @__value_0 OR ([r].[Date] = @__value_0 AND ([r].[Time] IS NULL OR [r].[Time] < @__value_1)) OR ([r].[Date] = @__value_0 AND [r].[Time] IS NOT NULL AND [r].[Time] = @__value_1 AND [r].[Id] < @__value_2)
+ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET9_0.md
new file mode 100644
index 00000000000..edbffb5d9d9
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_NonNull_NET9_0.md
@@ -0,0 +1,69 @@
+# Paging_Nullable_Descending_Cursor_Value_NonNull
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ },
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @__p_0 int = 4;
+
+SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC
+```
+
+## SQL 1
+
+```sql
+DECLARE @__p_3 int = 4;
+DECLARE @__value_0 date = '2017-11-03';
+DECLARE @__value_1 time = '21:45:00';
+DECLARE @__value_2 uniqueIdentifier = 'dd8f3a21-89ab-4cde-a203-7d3c45678901';
+
+SELECT TOP(@__p_3) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] IS NULL OR [r].[Date] < @__value_0 OR ([r].[Date] = @__value_0 AND ([r].[Time] IS NULL OR [r].[Time] < @__value_1)) OR ([r].[Date] = @__value_0 AND [r].[Time] IS NOT NULL AND [r].[Time] = @__value_1 AND [r].[Id] < @__value_2)
+ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET10_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET10_0.md
new file mode 100644
index 00000000000..10c56ce09fd
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET10_0.md
@@ -0,0 +1,68 @@
+# Paging_Nullable_Descending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @p int = 5;
+
+SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC
+```
+
+## SQL 1
+
+```sql
+DECLARE @p int = 5;
+DECLARE @value date = '2017-11-03';
+DECLARE @value2 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890';
+
+SELECT TOP(@p) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] IS NULL OR [r].[Date] < @value OR ([r].[Date] = @value AND [r].[Time] IS NULL AND [r].[Id] < @value2)
+ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET8_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET8_0.md
new file mode 100644
index 00000000000..5329d5e817c
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET8_0.md
@@ -0,0 +1,68 @@
+# Paging_Nullable_Descending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @__p_0 int = 5;
+
+SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC
+```
+
+## SQL 1
+
+```sql
+DECLARE @__p_2 int = 5;
+DECLARE @__value_0 date = '2017-11-03';
+DECLARE @__value_1 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890';
+
+SELECT TOP(@__p_2) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] IS NULL OR [r].[Date] < @__value_0 OR ([r].[Date] = @__value_0 AND [r].[Time] IS NULL AND [r].[Id] < @__value_1)
+ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC
+```
diff --git a/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET9_0.md b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET9_0.md
new file mode 100644
index 00000000000..5329d5e817c
--- /dev/null
+++ b/src/GreenDonut/test/GreenDonut.Data.EntityFramework.Tests/__snapshots__/PagingHelperSqlServerNullableTests.Paging_Nullable_Descending_Cursor_Value_Null_NET9_0.md
@@ -0,0 +1,68 @@
+# Paging_Nullable_Descending_Cursor_Value_Null
+
+## Result 1
+
+```json
+[
+ {
+ "Id": "a1d5b763-6789-4f23-c405-5f5e67890123",
+ "Date": "2017-11-04",
+ "Time": "19:40:00",
+ "String": "19:40:00"
+ },
+ {
+ "Id": "62ce9d54-2345-4f01-b304-6e4d56789012",
+ "Date": "2017-11-04",
+ "Time": "14:00:00",
+ "String": "14:00:00"
+ },
+ {
+ "Id": "dd8f3a21-89ab-4cde-a203-7d3c45678901",
+ "Date": "2017-11-03",
+ "Time": "21:45:00",
+ "String": "21:45:00"
+ },
+ {
+ "Id": "d3b7e9f1-4567-4abc-a102-8c2b34567890",
+ "Date": "2017-11-03",
+ "Time": null,
+ "String": null
+ }
+]
+```
+
+## Result 2
+
+```json
+[
+ {
+ "Id": "68a5c7c2-1234-4def-bc01-9f1a23456789",
+ "Date": "2017-10-28",
+ "Time": "22:00:00",
+ "String": "22:00:00"
+ }
+]
+```
+
+## SQL 0
+
+```sql
+DECLARE @__p_0 int = 5;
+
+SELECT TOP(@__p_0) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC
+```
+
+## SQL 1
+
+```sql
+DECLARE @__p_2 int = 5;
+DECLARE @__value_0 date = '2017-11-03';
+DECLARE @__value_1 uniqueIdentifier = 'd3b7e9f1-4567-4abc-a102-8c2b34567890';
+
+SELECT TOP(@__p_2) [r].[Id], [r].[Date], [r].[String], [r].[Time]
+FROM [Records] AS [r]
+WHERE [r].[Date] IS NULL OR [r].[Date] < @__value_0 OR ([r].[Date] = @__value_0 AND [r].[Time] IS NULL AND [r].[Id] < @__value_1)
+ORDER BY [r].[Date] DESC, [r].[Time] DESC, [r].[Id] DESC
+```
diff --git a/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs
index 8472f49bce2..70cae7fb7c9 100644
--- a/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs
+++ b/src/HotChocolate/Core/src/Types.Analyzers/FileBuilders/TypeFileBuilderBase.cs
@@ -1292,9 +1292,12 @@ private void WriteResolverArguments(Resolver resolver, IMethodSymbol resolverMet
using (Writer.IncreaseIndent())
{
Writer.WriteIndentedLine(
- "EnableRelativeCursors = args{0}_flags.HasFlag(global::{1}.RelativeCursor)",
+ "EnableRelativeCursors = args{0}_flags.HasFlag(global::{1}.RelativeCursor),",
i,
WellKnownTypes.ConnectionFlags);
+ Writer.WriteIndentedLine(
+ "NullOrdering = args{0}_options.NullOrdering",
+ i);
}
Writer.WriteIndentedLine("};");
diff --git a/src/HotChocolate/Core/src/Types/HotChocolate.Types.csproj b/src/HotChocolate/Core/src/Types/HotChocolate.Types.csproj
index 40c34203c94..b397fe324cb 100644
--- a/src/HotChocolate/Core/src/Types/HotChocolate.Types.csproj
+++ b/src/HotChocolate/Core/src/Types/HotChocolate.Types.csproj
@@ -59,6 +59,7 @@
+
diff --git a/src/HotChocolate/Core/src/Types/Types/Pagination/PagingOptions.cs b/src/HotChocolate/Core/src/Types/Types/Pagination/PagingOptions.cs
index d6143ba8aa2..951afb464ad 100644
--- a/src/HotChocolate/Core/src/Types/Types/Pagination/PagingOptions.cs
+++ b/src/HotChocolate/Core/src/Types/Types/Pagination/PagingOptions.cs
@@ -1,4 +1,5 @@
using System.Collections.Immutable;
+using GreenDonut.Data;
namespace HotChocolate.Types.Pagination;
@@ -64,6 +65,9 @@ public class PagingOptions
///
public bool? EnableRelativeCursors { get; set; }
+ /// Defines the null ordering to be used.
+ public NullOrdering NullOrdering { get; set; }
+
///
/// Gets or sets the fields that represent relative cursors.
///
@@ -103,6 +107,10 @@ internal void Merge(PagingOptions other)
ProviderName ??= other.ProviderName;
IncludeNodesField ??= other.IncludeNodesField;
EnableRelativeCursors ??= other.EnableRelativeCursors;
+ if (NullOrdering == NullOrdering.Unspecified)
+ {
+ NullOrdering = other.NullOrdering;
+ }
RelativeCursorFields = RelativeCursorFields.Union(other.RelativeCursorFields);
PageInfoFields = PageInfoFields.Union(other.PageInfoFields);
}
@@ -123,6 +131,7 @@ internal PagingOptions Copy()
ProviderName = ProviderName,
IncludeNodesField = IncludeNodesField,
EnableRelativeCursors = EnableRelativeCursors,
+ NullOrdering = NullOrdering,
RelativeCursorFields = RelativeCursorFields,
PageInfoFields = PageInfoFields
};
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/IntegrationTests.cs b/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/IntegrationTests.cs
new file mode 100644
index 00000000000..af23b5725d8
--- /dev/null
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/IntegrationTests.cs
@@ -0,0 +1,40 @@
+using GreenDonut.Data;
+using HotChocolate.Execution;
+using HotChocolate.Tests;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace HotChocolate.Types;
+
+public class IntegrationTests
+{
+ [Fact]
+ public async Task Schema_Snapshot()
+ {
+ await new ServiceCollection()
+ .AddGraphQLServer()
+ .AddIntegrationTestTypes()
+ .AddPagingArguments()
+ .BuildSchemaAsync()
+ .MatchSnapshotAsync();
+ }
+
+ [Fact]
+ public async Task Maps_NullOrdering_From_PagingOptions_To_PagingArguments()
+ {
+ // arrange
+ var executor = await new ServiceCollection()
+ .AddGraphQLServer()
+ .AddIntegrationTestTypes()
+ .AddPagingArguments()
+ .ModifyPagingOptions(o => o.NullOrdering = NullOrdering.NativeNullsFirst)
+ .BuildRequestExecutorAsync();
+
+ // act
+ var result = await executor.ExecuteAsync("{ ints { nodes } }");
+ var operationResult = result.ExpectOperationResult();
+
+ // assert
+ Assert.Empty(operationResult.Errors);
+ Assert.Equal(NullOrdering.NativeNullsFirst, Query.PagingArguments.NullOrdering);
+ }
+}
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/InterfaceTests.cs b/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/InterfaceTests.cs
index 582518b51ae..3ae7c2431a4 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/InterfaceTests.cs
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/InterfaceTests.cs
@@ -1,21 +1,10 @@
using HotChocolate.Execution;
-using HotChocolate.Tests;
using Microsoft.Extensions.DependencyInjection;
namespace HotChocolate.Types;
public class InterfaceTests
{
- [Fact]
- public async Task Schema_Snapshot()
- {
- await new ServiceCollection()
- .AddGraphQLServer()
- .AddIntegrationTestTypes()
- .BuildSchemaAsync()
- .MatchSnapshotAsync();
- }
-
[Fact]
public async Task Ensure_Interface_Resolvers_Are_ParallelExecutable()
{
@@ -23,6 +12,7 @@ public async Task Ensure_Interface_Resolvers_Are_ParallelExecutable()
await new ServiceCollection()
.AddGraphQLServer()
.AddIntegrationTestTypes()
+ .AddPagingArguments()
.BuildSchemaAsync();
Assert.True(
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/Product.cs b/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/Product.cs
index 14e174ce973..129e5cd8750 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/Product.cs
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/Product.cs
@@ -1,5 +1,6 @@
using System.Reflection;
using System.Text.Json;
+using GreenDonut.Data;
using HotChocolate.Features;
using HotChocolate.Language;
using HotChocolate.Text.Json;
@@ -43,6 +44,9 @@ public static string NullableArgumentWithExplicitType(
[QueryType]
public static partial class Query
{
+ [GraphQLIgnore]
+ public static PagingArguments PagingArguments { get; private set; }
+
///
/// Gets the product.
///
@@ -50,6 +54,13 @@ public static partial class Query
public static Product GetProduct()
=> new Book { Id = "1", Title = "GraphQL in Action" };
+ [UsePaging]
+ public static IEnumerable GetInts(PagingArguments pagingArguments)
+ {
+ PagingArguments = pagingArguments;
+ return [];
+ }
+
[UsePaging]
[RewriteAfterToVersion]
public static IQueryable GetProducts([GraphQLType>] long after)
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/__snapshots__/InterfaceTests.Schema_Snapshot.snap b/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/__snapshots__/IntegrationTests.Schema_Snapshot.snap
similarity index 84%
rename from src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/__snapshots__/InterfaceTests.Schema_Snapshot.snap
rename to src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/__snapshots__/IntegrationTests.Schema_Snapshot.snap
index 8b3cccf4882..ee5e95bd61a 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/__snapshots__/InterfaceTests.Schema_Snapshot.snap
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Integration.Tests/__snapshots__/IntegrationTests.Schema_Snapshot.snap
@@ -18,6 +18,24 @@ type Book implements Product {
kind: String!
}
+"A connection to a list of items."
+type IntsConnection {
+ "Information to aid in pagination."
+ pageInfo: PageInfo!
+ "A list of edges."
+ edges: [IntsEdge!]
+ "A flattened list of the nodes."
+ nodes: [Int!]
+}
+
+"An edge in a connection."
+type IntsEdge {
+ "A cursor for use in pagination."
+ cursor: String!
+ "The item at the end of the edge."
+ node: Int!
+}
+
type Issue8057Entity implements Node {
id: ID!
}
@@ -61,6 +79,7 @@ type Query {
The only product.
"""
product: Product!
+ ints("Returns the first _n_ elements from the list." first: Int "Returns the elements in the list that come after the specified cursor." after: String "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): IntsConnection @listSize(assumedSize: 50, slicingArguments: [ "first", "last" ], slicingArgumentDefaultValue: 10, sizedFields: [ "edges", "nodes" ], requireOneSlicingArgument: false)
products("Returns the elements in the list that come after the specified cursor." after: Version2 "Returns the first _n_ elements from the list." first: Int "Returns the last _n_ elements from the list." last: Int "Returns the elements in the list that come before the specified cursor." before: String): ProductsConnection @listSize(assumedSize: 50, slicingArguments: [ "first", "last" ], slicingArgumentDefaultValue: 10, sizedFields: [ "edges", "nodes" ], requireOneSlicingArgument: false) @cost(weight: "10")
argumentWithExplicitType(arg: Version2): String!
nullableArgumentWithExplicitType(arg: Version2): String!
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_ConnectionFlags.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_ConnectionFlags.md
index 7e207f817e2..cd1f98ffec3 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_ConnectionFlags.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_ConnectionFlags.md
@@ -400,7 +400,8 @@ namespace TestNamespace.Types.Root
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = global::HotChocolate.Types.Pagination.ConnectionFlagsHelper.GetConnectionFlags(context);
var args2 = context.RequestAborted;
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_ConnectionT_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_ConnectionT_MatchesSnapshot.md
index 85a8a33ddb5..4a75957736d 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_ConnectionT_MatchesSnapshot.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_ConnectionT_MatchesSnapshot.md
@@ -101,7 +101,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.BookPage.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_MatchesSnapshot.md
index 9aed6db3349..555b1dec77b 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_MatchesSnapshot.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_MatchesSnapshot.md
@@ -369,7 +369,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_No_Duplicates_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_No_Duplicates_MatchesSnapshot.md
index 85cbace87f1..14ff4747d76 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_No_Duplicates_MatchesSnapshot.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_No_Duplicates_MatchesSnapshot.md
@@ -363,7 +363,8 @@ namespace TestNamespace.Types.Nodes
args1_before,
args1_includeTotalCount)
{
- EnableRelativeCursors = args1_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args1_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args1_options.NullOrdering
};
var args2 = context.RequestAborted;
var result = await global::TestNamespace.Types.Nodes.AuthorNode.GetAuthorsAsync(args0, args1, args2);
@@ -500,7 +501,8 @@ namespace TestNamespace.Types.Root
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1);
@@ -543,7 +545,8 @@ namespace TestNamespace.Types.Root
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthors2Async(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_UseConnection_ConnectionName_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_UseConnection_ConnectionName_MatchesSnapshot.md
index 4c658412e44..da094afa7e9 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_UseConnection_ConnectionName_MatchesSnapshot.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_UseConnection_ConnectionName_MatchesSnapshot.md
@@ -120,7 +120,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_UseConnection_IncludeTotalCount_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_UseConnection_IncludeTotalCount_MatchesSnapshot.md
index 455ad32b079..60e86f0ebec 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_UseConnection_IncludeTotalCount_MatchesSnapshot.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_CustomConnection_UseConnection_IncludeTotalCount_MatchesSnapshot.md
@@ -387,7 +387,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_GenericCustomConnection_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_GenericCustomConnection_MatchesSnapshot.md
index 01357211e5f..2dc7ffebf7c 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_GenericCustomConnection_MatchesSnapshot.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_GenericCustomConnection_MatchesSnapshot.md
@@ -370,7 +370,8 @@ namespace TestNamespace.Types.Nodes
args1_before,
args1_includeTotalCount)
{
- EnableRelativeCursors = args1_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args1_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args1_options.NullOrdering
};
var args2 = context.RequestAborted;
var result = await global::TestNamespace.Types.Nodes.AuthorNode.GetAuthorsAsync(args0, args1, args2);
@@ -509,7 +510,8 @@ namespace TestNamespace.Types.Root
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1);
@@ -552,7 +554,8 @@ namespace TestNamespace.Types.Root
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthors2Async(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_GenericCustomConnection_WithConnectionName_MatchesSnapshot.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_GenericCustomConnection_WithConnectionName_MatchesSnapshot.md
index b03e2071a4e..8cea2bebc1f 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_GenericCustomConnection_WithConnectionName_MatchesSnapshot.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_GenericCustomConnection_WithConnectionName_MatchesSnapshot.md
@@ -370,7 +370,8 @@ namespace TestNamespace.Types.Nodes
args1_before,
args1_includeTotalCount)
{
- EnableRelativeCursors = args1_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args1_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args1_options.NullOrdering
};
var args2 = context.RequestAborted;
var result = await global::TestNamespace.Types.Nodes.AuthorNode.GetAuthorsAsync(args0, args1, args2);
@@ -527,7 +528,8 @@ namespace TestNamespace.Types.Root
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1);
@@ -570,7 +572,8 @@ namespace TestNamespace.Types.Root
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthors2Async(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Inherit_PageEdge.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Inherit_PageEdge.md
index 4924720173e..0bfcd520a41 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Inherit_PageEdge.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Inherit_PageEdge.md
@@ -400,7 +400,8 @@ namespace TestNamespace.Types.Root
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Reuse_PageEdge.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Reuse_PageEdge.md
index 7f5cfdeb60d..2efc930c4a2 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Reuse_PageEdge.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Reuse_PageEdge.md
@@ -371,7 +371,8 @@ namespace TestNamespace.Types.Root
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Reuse_PageEdge_Generic.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Reuse_PageEdge_Generic.md
index 5a6c5889c8d..f3563e68e02 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Reuse_PageEdge_Generic.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_ConnectionBase_Reuse_PageEdge_Generic.md
@@ -378,7 +378,8 @@ namespace TestNamespace.Types.Root
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_PageConnection.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_PageConnection.md
index 6cd7beca197..4a617e9553f 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_PageConnection.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.GenerateSource_Inherit_From_PageConnection.md
@@ -406,7 +406,8 @@ namespace TestNamespace.Types.Root
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Class.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Class.md
index 14dacdc1f2d..75d7fd07ea9 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Class.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Class.md
@@ -371,7 +371,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Class_Scoped.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Class_Scoped.md
index ac5c2eb6c73..83a368eddef 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Class_Scoped.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Class_Scoped.md
@@ -373,7 +373,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Field.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Field.md
index 86a6d102186..664dd0620dd 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Field.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Connection_Field.md
@@ -370,7 +370,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Class.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Class.md
index 18dd6ec42c6..7a809ed967c 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Class.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Class.md
@@ -371,7 +371,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Class_Scoped.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Class_Scoped.md
index 030b10e605d..39ce816d419 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Class_Scoped.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Class_Scoped.md
@@ -371,7 +371,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Field.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Field.md
index 396050729e1..b435569087a 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Field.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_Edge_Field.md
@@ -370,7 +370,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_PageConnection.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_PageConnection.md
index 340e7dfe2cf..b5fb8b04e72 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_PageConnection.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_PageConnection.md
@@ -410,7 +410,8 @@ namespace TestNamespace.Types.Root
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_PageConnection_Scoped.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_PageConnection_Scoped.md
index 7007a6b2030..5a64df96469 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_PageConnection_Scoped.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Inaccessible_On_PageConnection_Scoped.md
@@ -413,7 +413,8 @@ namespace TestNamespace.Types.Root
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Class.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Class.md
index 6d6cfd514c3..2bc49bebe0d 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Class.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Class.md
@@ -366,7 +366,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Class_Scoped.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Class_Scoped.md
index ad975f570d6..fcda35edae1 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Class_Scoped.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Class_Scoped.md
@@ -373,7 +373,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Field.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Field.md
index 07a9914b255..119b37ed851 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Field.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Connection_Field.md
@@ -370,7 +370,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Class.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Class.md
index c281ad185a1..8ad2e03d79d 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Class.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Class.md
@@ -366,7 +366,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Class_Scoped.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Class_Scoped.md
index e41c109c83e..2c1aea0bf70 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Class_Scoped.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Class_Scoped.md
@@ -371,7 +371,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Field.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Field.md
index c154ec5918a..4502f046821 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Field.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_Edge_Field.md
@@ -370,7 +370,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_PageConnection.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_PageConnection.md
index 0a4c876600e..e4bd624515a 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_PageConnection.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_PageConnection.md
@@ -400,7 +400,8 @@ namespace TestNamespace.Types.Root
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_PageConnection_Scoped.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_PageConnection_Scoped.md
index b2ba274ce67..e42d279063d 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_PageConnection_Scoped.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/PagingTests.Shareable_On_PageConnection_Scoped.md
@@ -413,7 +413,8 @@ namespace TestNamespace.Types.Root
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = context.RequestAborted;
var result = await global::TestNamespace.Types.Root.AuthorQueries.GetAuthorsAsync(args0, args1);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.CorrectGenericTypeMatch_NoError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.CorrectGenericTypeMatch_NoError.md
index 946e88f0048..c89059f3681 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.CorrectGenericTypeMatch_NoError.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.CorrectGenericTypeMatch_NoError.md
@@ -440,7 +440,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1_selection = context.Selection;
var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.CorrectGenericTypeMatch_WithConnection_NoError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.CorrectGenericTypeMatch_WithConnection_NoError.md
index 130dccd6bdb..70c97406cde 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.CorrectGenericTypeMatch_WithConnection_NoError.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.CorrectGenericTypeMatch_WithConnection_NoError.md
@@ -157,7 +157,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1_selection = context.Selection;
var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.NoQueryContextParameter_NoError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.NoQueryContextParameter_NoError.md
index 81a6c7a8bc8..650a5a06f33 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.NoQueryContextParameter_NoError.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.NoQueryContextParameter_NoError.md
@@ -440,7 +440,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1 = _binding_GetProductsAsync_productService.Execute(context);
var args2 = context.RequestAborted;
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithConnection_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithConnection_RaisesError.md
index 56453c60c8e..64352dda65e 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithConnection_RaisesError.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithConnection_RaisesError.md
@@ -157,7 +157,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1_selection = context.Selection;
var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithInterfaceType_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithInterfaceType_RaisesError.md
index 23c5c1f70d8..2330e8f5d7c 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithInterfaceType_RaisesError.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithInterfaceType_RaisesError.md
@@ -157,7 +157,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1_selection = context.Selection;
var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithMutationType_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithMutationType_RaisesError.md
index 5b19b90aea1..98bcd87eb85 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithMutationType_RaisesError.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithMutationType_RaisesError.md
@@ -173,7 +173,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1_selection = context.Selection;
var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithObjectType_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithObjectType_RaisesError.md
index 5cd1ecc24e8..8f65f343169 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithObjectType_RaisesError.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithObjectType_RaisesError.md
@@ -440,7 +440,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1_selection = context.Selection;
var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithQueryType_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithQueryType_RaisesError.md
index 6b3a96cc7b0..ac21c2147c6 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithQueryType_RaisesError.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithQueryType_RaisesError.md
@@ -452,7 +452,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1_selection = context.Selection;
var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithSubscriptionType_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithSubscriptionType_RaisesError.md
index 8d9d459fd36..11ebaf345e0 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithSubscriptionType_RaisesError.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextConnectionAnalyzerTests.TypeMismatch_WithSubscriptionType_RaisesError.md
@@ -452,7 +452,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1_selection = context.Selection;
var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_MultipleAttributes_OnlyUseProjectionFlagged.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_MultipleAttributes_OnlyUseProjectionFlagged.md
index e5c7057aca5..53baad9f8b4 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_MultipleAttributes_OnlyUseProjectionFlagged.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_MultipleAttributes_OnlyUseProjectionFlagged.md
@@ -179,7 +179,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1_selection = context.Selection;
var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithUseProjection_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithUseProjection_RaisesError.md
index 6d8e5613468..451c712ff03 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithUseProjection_RaisesError.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithUseProjection_RaisesError.md
@@ -179,7 +179,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1_selection = context.Selection;
var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithUseProjections_RaisesError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithUseProjections_RaisesError.md
index 73389a78b8e..dacd6bf8e85 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithUseProjections_RaisesError.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithUseProjections_RaisesError.md
@@ -178,7 +178,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1_selection = context.Selection;
var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context);
diff --git a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithoutUseProjection_NoError.md b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithoutUseProjection_NoError.md
index b418b28ebc8..6b6b3f00931 100644
--- a/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithoutUseProjection_NoError.md
+++ b/src/HotChocolate/Core/test/Types.Analyzers.Tests/__snapshots__/QueryContextProjectionAnalyzerTests.QueryContext_WithoutUseProjection_NoError.md
@@ -178,7 +178,8 @@ namespace TestNamespace
args0_before,
args0_includeTotalCount)
{
- EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor)
+ EnableRelativeCursors = args0_flags.HasFlag(global::HotChocolate.Types.Pagination.ConnectionFlags.RelativeCursor),
+ NullOrdering = args0_options.NullOrdering
};
var args1_selection = context.Selection;
var args1_filter = global::HotChocolate.Data.Filters.FilterContextResolverContextExtensions.GetFilterContext(context);
diff --git a/src/HotChocolate/Data/src/Data/PagingArgumentsParameterExpressionBuilder.cs b/src/HotChocolate/Data/src/Data/PagingArgumentsParameterExpressionBuilder.cs
index a9ad284273b..a24b73911ec 100644
--- a/src/HotChocolate/Data/src/Data/PagingArgumentsParameterExpressionBuilder.cs
+++ b/src/HotChocolate/Data/src/Data/PagingArgumentsParameterExpressionBuilder.cs
@@ -28,6 +28,7 @@ public T Execute(IResolverContext context)
private static PagingArguments MapArguments(IResolverContext context)
{
+ var pagingOptions = PagingHelper.GetPagingOptions(context.Schema, context.Selection.Field);
var pagingArguments = context.GetLocalState(WellKnownContextData.PagingArguments);
var includeTotalCount = IncludeTotalCount(context.Selection);
@@ -36,11 +37,20 @@ private static PagingArguments MapArguments(IResolverContext context)
includeTotalCount = context.IsSelected("totalCount");
}
- return MapArguments(pagingArguments, includeTotalCount);
+ return MapArguments(pagingArguments, includeTotalCount, pagingOptions.NullOrdering);
}
- private static PagingArguments MapArguments(CursorPagingArguments arguments, bool includeTotalCount)
- => new(arguments.First, arguments.After, arguments.Last, arguments.Before, includeTotalCount);
+ private static PagingArguments MapArguments(
+ CursorPagingArguments arguments,
+ bool includeTotalCount,
+ NullOrdering nullOrdering)
+ => new(
+ arguments.First,
+ arguments.After,
+ arguments.Last,
+ arguments.Before,
+ includeTotalCount)
+ { NullOrdering = nullOrdering };
private static bool IncludeTotalCount(Selection selection)
=> selection.Field.Features.Get()?.IncludeTotalCount is true;
diff --git a/src/HotChocolate/Data/src/EntityFramework/Pagination/EfQueryableCursorPagingHandler.cs b/src/HotChocolate/Data/src/EntityFramework/Pagination/EfQueryableCursorPagingHandler.cs
index e6a1e24ed4f..cc58b291e9c 100644
--- a/src/HotChocolate/Data/src/EntityFramework/Pagination/EfQueryableCursorPagingHandler.cs
+++ b/src/HotChocolate/Data/src/EntityFramework/Pagination/EfQueryableCursorPagingHandler.cs
@@ -1,12 +1,13 @@
using System.Collections.Immutable;
+using System.Reflection;
+using GreenDonut.Data;
using GreenDonut.Data.Cursors;
using GreenDonut.Data.Expressions;
using HotChocolate.Resolvers;
using HotChocolate.Types.Pagination;
using HotChocolate.Types.Pagination.Utilities;
-#if DEBUG
using Microsoft.EntityFrameworkCore;
-#endif
+using Microsoft.EntityFrameworkCore.Infrastructure;
using static HotChocolate.Data.Properties.EntityFrameworkResources;
namespace HotChocolate.Data.Pagination;
@@ -14,6 +15,8 @@ namespace HotChocolate.Data.Pagination;
internal sealed class EfQueryableCursorPagingHandler(PagingOptions options)
: CursorPagingHandler(options)
{
+ private readonly NullOrdering _nullOrdering = options.NullOrdering;
+
protected override ValueTask SliceAsync(
IResolverContext context,
object source,
@@ -26,6 +29,7 @@ private async ValueTask SliceAsync(
CursorPagingArguments arguments)
{
var query = executable.Source;
+ var nullOrdering = ResolveNullOrdering(context, query, executable.IsInMemory, _nullOrdering);
var keys = ParseDataSetKeys(query);
var forward = arguments.Last is null;
var requestedCount = int.MaxValue;
@@ -48,14 +52,24 @@ private async ValueTask SliceAsync(
if (arguments.After is not null)
{
var cursor = CursorParser.Parse(arguments.After, keys);
- var (whereExpr, _) = ExpressionHelpers.BuildWhereExpression(keys, cursor, true);
+ var (whereExpr, _) =
+ ExpressionHelpers.BuildWhereExpression(
+ keys,
+ cursor,
+ true,
+ nullOrdering);
query = query.Where(whereExpr);
}
if (arguments.Before is not null)
{
var cursor = CursorParser.Parse(arguments.Before, keys);
- var (whereExpr, _) = ExpressionHelpers.BuildWhereExpression(keys, cursor, false);
+ var (whereExpr, _) =
+ ExpressionHelpers.BuildWhereExpression(
+ keys,
+ cursor,
+ false,
+ nullOrdering);
query = query.Where(whereExpr);
}
@@ -218,4 +232,98 @@ private static CursorKey[] ParseDataSetKeys(IQueryable source)
parser.Visit(source.Expression);
return parser.Keys.ToArray();
}
+
+ private static NullOrdering ResolveNullOrdering(
+ IResolverContext context,
+ IQueryable query,
+ bool isInMemory,
+ NullOrdering configured)
+ {
+ if (configured is not NullOrdering.Unspecified)
+ {
+ return configured;
+ }
+
+ // LINQ-to-Objects sorts null values first in ascending order.
+ if (isInMemory)
+ {
+ return NullOrdering.NativeNullsFirst;
+ }
+
+ var providerName = TryGetProviderName(query)
+ ?? TryGetProviderName(context, query.Provider);
+
+ if (providerName is not null)
+ {
+ if (providerName.Contains("Npgsql", StringComparison.OrdinalIgnoreCase))
+ {
+ return NullOrdering.NativeNullsLast;
+ }
+
+ if (providerName.Contains("SqlServer", StringComparison.OrdinalIgnoreCase)
+ || providerName.Contains("Sqlite", StringComparison.OrdinalIgnoreCase)
+ || providerName.Contains("MySql", StringComparison.OrdinalIgnoreCase))
+ {
+ return NullOrdering.NativeNullsFirst;
+ }
+
+ // When the provider is not in our supported list we return Unspecified so that
+ // BuildWhereExpression throws a clear error if nullable cursor keys are present.
+ return NullOrdering.Unspecified;
+ }
+
+ // The provider could not be identified. Return Unspecified so that
+ // BuildWhereExpression throws a clear error if nullable cursor keys are
+ // encountered, prompting the user to set PagingOptions.NullOrdering explicitly.
+ return NullOrdering.Unspecified;
+ }
+
+ private static string? TryGetProviderName(IQueryable query)
+ {
+ if (TryGetProviderName(query as IInfrastructure) is { } providerName)
+ {
+ return providerName;
+ }
+
+ if (TryGetProviderName(query.Provider as IInfrastructure) is { } providerNameFromProvider)
+ {
+ return providerNameFromProvider;
+ }
+
+ return null;
+ }
+
+ private static string? TryGetProviderName(IInfrastructure? infrastructure)
+ => infrastructure?
+ .Instance
+ .GetService(typeof(ICurrentDbContext))
+ is ICurrentDbContext currentDbContext
+ ? currentDbContext.Context.Database.ProviderName
+ : null;
+
+ private static string? TryGetProviderName(IResolverContext context, IQueryProvider provider)
+ {
+ if (TryGetDbContextType(provider) is { } dbContextType
+ && context.Services.GetService(dbContextType) is DbContext dbContext)
+ {
+ return dbContext.Database.ProviderName;
+ }
+
+ return null;
+ }
+
+ private static Type? TryGetDbContextType(IQueryProvider provider)
+ {
+ const BindingFlags flags = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
+
+ if (TryGetFieldValue(provider, "_queryCompiler", flags) is not object queryCompiler)
+ {
+ return null;
+ }
+
+ return TryGetFieldValue(queryCompiler, "_contextType", flags) as Type;
+ }
+
+ private static object? TryGetFieldValue(object instance, string fieldName, BindingFlags flags)
+ => instance.GetType().GetField(fieldName, flags)?.GetValue(instance);
}
diff --git a/src/HotChocolate/Data/test/Data.EntityFramework.Pagination.Tests/IntegrationTests.cs b/src/HotChocolate/Data/test/Data.EntityFramework.Pagination.Tests/IntegrationTests.cs
index d9a2c1900ef..b3576ee3f5e 100644
--- a/src/HotChocolate/Data/test/Data.EntityFramework.Pagination.Tests/IntegrationTests.cs
+++ b/src/HotChocolate/Data/test/Data.EntityFramework.Pagination.Tests/IntegrationTests.cs
@@ -1,5 +1,7 @@
using System.Reflection;
using System.Runtime.CompilerServices;
+using System.Text.Json;
+using GreenDonut.Data;
using HotChocolate.Data.Sorting;
using HotChocolate.Data.TestContext;
using HotChocolate.Execution;
@@ -413,6 +415,135 @@ public async Task Paging_Fetch_Last_2_Items_Between()
: null);
}
+ [Fact]
+ public async Task Paging_Next_2_With_Nullable_Key_And_Configured_NullOrdering()
+ {
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ var executor = await new ServiceCollection()
+ .AddScoped(_ => new CatalogContext(connectionString))
+ .AddGraphQLServer()
+ .AddQueryType()
+ .AddSorting()
+ .AddDbContextCursorPagingProvider()
+ .ModifyPagingOptions(o => o.NullOrdering = NullOrdering.NativeNullsLast)
+ .BuildRequestExecutorAsync();
+
+ var firstResult = await executor.ExecuteAsync(
+ """
+ {
+ brandsNullable(first: 2) {
+ pageInfo {
+ endCursor
+ }
+ }
+ }
+ """);
+
+ var firstOperationResult = firstResult.ExpectOperationResult();
+ var firstGraphQLError = firstOperationResult.Errors?.FirstOrDefault();
+ var firstError = firstGraphQLError?.Exception?.ToString();
+ var firstExtensions = firstGraphQLError?.Extensions is null
+ ? null
+ : JsonSerializer.Serialize(firstGraphQLError.Extensions);
+ Assert.True(
+ firstOperationResult.Errors is null or { Count: 0 },
+ $"{firstError}\n{firstExtensions}\n{firstResult.ToJson()}");
+
+ using var firstDocument = JsonDocument.Parse(firstResult.ToJson());
+ var afterCursor = firstDocument.RootElement
+ .GetProperty("data")
+ .GetProperty("brandsNullable")
+ .GetProperty("pageInfo")
+ .GetProperty("endCursor")
+ .GetString();
+
+ Assert.False(string.IsNullOrEmpty(afterCursor));
+
+ var secondResult = await executor.ExecuteAsync(
+ $$"""
+ {
+ brandsNullable(first: 2, after: "{{afterCursor}}") {
+ nodes {
+ name
+ }
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ }
+ }
+ }
+ """);
+
+ var secondOperationResult = secondResult.ExpectOperationResult();
+ var secondGraphQLError = secondOperationResult.Errors?.FirstOrDefault();
+ var secondError = secondGraphQLError?.Exception?.ToString();
+ var secondExtensions = secondGraphQLError?.Extensions is null
+ ? null
+ : JsonSerializer.Serialize(secondGraphQLError.Extensions);
+ Assert.True(
+ secondOperationResult.Errors is null or { Count: 0 },
+ $"{secondError}\n{secondExtensions}\n{secondResult.ToJson()}");
+ }
+
+ [Fact]
+ public async Task Paging_Next_2_With_Nullable_Key_And_Inferred_NullOrdering()
+ {
+ var connectionString = CreateConnectionString();
+ await SeedAsync(connectionString);
+
+ var executor = await new ServiceCollection()
+ .AddScoped(_ => new CatalogContext(connectionString))
+ .AddGraphQLServer()
+ .AddQueryType()
+ .AddSorting()
+ .AddDbContextCursorPagingProvider()
+ .BuildRequestExecutorAsync();
+
+ var firstResult = await executor.ExecuteAsync(
+ """
+ {
+ brandsNullable(first: 2) {
+ pageInfo {
+ endCursor
+ }
+ }
+ }
+ """);
+
+ var firstOperationResult = firstResult.ExpectOperationResult();
+ Assert.True(firstOperationResult.Errors is null or { Count: 0 }, firstResult.ToJson());
+
+ using var firstDocument = JsonDocument.Parse(firstResult.ToJson());
+ var afterCursor = firstDocument.RootElement
+ .GetProperty("data")
+ .GetProperty("brandsNullable")
+ .GetProperty("pageInfo")
+ .GetProperty("endCursor")
+ .GetString();
+
+ Assert.False(string.IsNullOrEmpty(afterCursor));
+
+ var secondResult = await executor.ExecuteAsync(
+ $$"""
+ {
+ brandsNullable(first: 2, after: "{{afterCursor}}") {
+ nodes {
+ name
+ }
+ pageInfo {
+ hasNextPage
+ hasPreviousPage
+ }
+ }
+ }
+ """);
+
+ var secondOperationResult = secondResult.ExpectOperationResult();
+ Assert.True(secondOperationResult.Errors is null or { Count: 0 }, secondResult.ToJson());
+ }
+
public class Query
{
[UsePaging]
@@ -453,6 +584,13 @@ public IQueryable GetProducts(CatalogContext context, ISortingContext s
return context.Products;
}
+
+ [UsePaging]
+ public IQueryable GetBrandsNullable(CatalogContext context)
+ => context.Brands
+ .OrderBy(t => t.Name)
+ .ThenBy(t => t.AlwaysNull)
+ .ThenBy(t => t.Id);
}
[ExtendObjectType("ProductConnection")]
diff --git a/src/HotChocolate/Data/test/Data.Tests/Pagination/PagingArgumentsParameterExpressionBuilderTests.cs b/src/HotChocolate/Data/test/Data.Tests/Pagination/PagingArgumentsParameterExpressionBuilderTests.cs
new file mode 100644
index 00000000000..fdbba155672
--- /dev/null
+++ b/src/HotChocolate/Data/test/Data.Tests/Pagination/PagingArgumentsParameterExpressionBuilderTests.cs
@@ -0,0 +1,42 @@
+using GreenDonut.Data;
+using HotChocolate.Execution;
+using HotChocolate.Types;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace HotChocolate.Data.Pagination;
+
+public class PagingArgumentsParameterExpressionBuilderTests
+{
+ [Fact]
+ public async Task Maps_NullOrdering_From_PagingOptions_To_PagingArguments()
+ {
+ // arrange
+ var executor = await new ServiceCollection()
+ .AddGraphQL()
+ .AddQueryType()
+ .AddPagingArguments()
+ .ModifyPagingOptions(o => o.NullOrdering = NullOrdering.NativeNullsFirst)
+ .BuildRequestExecutorAsync();
+
+ // act
+ var result = await executor.ExecuteAsync("{ ints { nodes } }");
+ var operationResult = result.ExpectOperationResult();
+
+ // assert
+ Assert.Empty(operationResult.Errors);
+ Assert.Equal(NullOrdering.NativeNullsFirst, Query.PagingArguments.NullOrdering);
+ }
+
+ public class Query
+ {
+ public static PagingArguments PagingArguments { get; private set; }
+
+ [UsePaging]
+ public IEnumerable GetInts(PagingArguments pagingArguments)
+ {
+ PagingArguments = pagingArguments;
+
+ return [];
+ }
+ }
+}
diff --git a/src/HotChocolate/Data/test/Data.Tests/PagingHelperIntegrationTests.cs b/src/HotChocolate/Data/test/Data.Tests/PagingHelperIntegrationTests.cs
index fafd08dd6e9..1bc5ad329e7 100644
--- a/src/HotChocolate/Data/test/Data.Tests/PagingHelperIntegrationTests.cs
+++ b/src/HotChocolate/Data/test/Data.Tests/PagingHelperIntegrationTests.cs
@@ -233,6 +233,7 @@ public async Task GetDefaultPage_With_Nullable_SecondPage()
.AddGraphQL()
.AddQueryType()
.AddPagingArguments()
+ .ModifyPagingOptions(o => o.NullOrdering = NullOrdering.NativeNullsLast)
.ExecuteRequestAsync(
OperationRequestBuilder.New()
.SetDocument(
@@ -345,6 +346,7 @@ public async Task GetDefaultPage_With_Nullable_Fallback_SecondPage()
.AddGraphQL()
.AddQueryType()
.AddPagingArguments()
+ .ModifyPagingOptions(o => o.NullOrdering = NullOrdering.NativeNullsLast)
.ExecuteRequestAsync(
OperationRequestBuilder.New()
.SetDocument(
diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Deep_SecondPage.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Deep_SecondPage.md
index 9b2e6a0a1ea..5f918abe82a 100644
--- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Deep_SecondPage.md
+++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Deep_SecondPage.md
@@ -16,7 +16,7 @@ LIMIT @__p_2
## Expression 0
```text
-[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(x => x.BrandDetails.Country.Name).ThenBy(t => t.Id).Where(t => ((t.BrandDetails.Country.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.BrandDetails.Country.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(3)
+[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(x => x.BrandDetails.Country.Name).ThenBy(t => t.Id).Where(t => ((t.BrandDetails.Country.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) > 0) OrElse ((t.BrandDetails.Country.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.Int32]).value) > 0)))).Take(3)
```
## Result
diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Deep_SecondPage_NET10_0.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Deep_SecondPage_NET10_0.md
index 55781a45184..856690862c0 100644
--- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Deep_SecondPage_NET10_0.md
+++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Deep_SecondPage_NET10_0.md
@@ -16,7 +16,7 @@ LIMIT @p
## Expression 0
```text
-[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(x => x.BrandDetails.Country.Name).ThenBy(t => t.Id).Where(t => ((t.BrandDetails.Country.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.BrandDetails.Country.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(3)
+[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(x => x.BrandDetails.Country.Name).ThenBy(t => t.Id).Where(t => ((t.BrandDetails.Country.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) > 0) OrElse ((t.BrandDetails.Country.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) > 0)))).Take(3)
```
## Result
diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_Fallback_SecondPage.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_Fallback_SecondPage.md
index b8092b48bec..8b67613feef 100644
--- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_Fallback_SecondPage.md
+++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_Fallback_SecondPage.md
@@ -16,7 +16,7 @@ LIMIT @__p_2
## Expression 0
```text
-[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => (t.DisplayName ?? t.Name)).ThenBy(t => t.Id).Where(t => (((t.DisplayName ?? t.Name).CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse (((t.DisplayName ?? t.Name).CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(3)
+[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => (t.DisplayName ?? t.Name)).ThenBy(t => t.Id).Where(t => (((t.DisplayName ?? t.Name).CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) > 0) OrElse (((t.DisplayName ?? t.Name).CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) > 0)))).Take(3)
```
## Result
diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_Fallback_SecondPage_NET10_0.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_Fallback_SecondPage_NET10_0.md
index 8340bf5fe79..839e1510dd1 100644
--- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_Fallback_SecondPage_NET10_0.md
+++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_Fallback_SecondPage_NET10_0.md
@@ -16,7 +16,7 @@ LIMIT @p
## Expression 0
```text
-[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => (t.DisplayName ?? t.Name)).ThenBy(t => t.Id).Where(t => (((t.DisplayName ?? t.Name).CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse (((t.DisplayName ?? t.Name).CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(3)
+[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => (t.DisplayName ?? t.Name)).ThenBy(t => t.Id).Where(t => (((t.DisplayName ?? t.Name).CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) > 0) OrElse (((t.DisplayName ?? t.Name).CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) > 0)))).Take(3)
```
## Result
diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_SecondPage.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_SecondPage.md
index a74b0183df3..6eee21c65e6 100644
--- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_SecondPage.md
+++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_SecondPage.md
@@ -4,19 +4,19 @@
```sql
-- @__value_0='Brand10'
--- @__value_2='11'
--- @__p_3='3'
+-- @__value_1='11'
+-- @__p_2='3'
SELECT b."Id", b."AlwaysNull", b."DisplayName", b."Name", b."BrandDetails_Country_Name"
FROM "Brands" AS b
-WHERE b."Name" > @__value_0 OR (b."Name" = @__value_0 AND b."AlwaysNull" > NULL) OR (b."Name" = @__value_0 AND b."AlwaysNull" IS NULL AND b."Id" > @__value_2)
+WHERE b."Name" > @__value_0 OR (b."Name" = @__value_0 AND b."AlwaysNull" IS NULL AND b."Id" > @__value_1)
ORDER BY b."Name", b."AlwaysNull", b."Id"
-LIMIT @__p_3
+LIMIT @__p_2
```
## Expression 0
```text
-[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(x => x.AlwaysNull).ThenBy(t => t.Id).Where(t => (((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.AlwaysNull.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0))) OrElse (((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.AlwaysNull.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0)) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(3)
+[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(x => x.AlwaysNull).ThenBy(t => t.Id).Where(t => (((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso False)) OrElse (((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.AlwaysNull == null)) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) > 0)))).Take(3)
```
## Result
diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_SecondPage_NET10_0.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_SecondPage_NET10_0.md
index 4387aababf2..c57e326fa8e 100644
--- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_SecondPage_NET10_0.md
+++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetDefaultPage_With_Nullable_SecondPage_NET10_0.md
@@ -4,11 +4,11 @@
```sql
-- @value='Brand10'
--- @value4='11'
+-- @value2='11'
-- @p='3'
SELECT b."Id", b."AlwaysNull", b."DisplayName", b."Name", b."BrandDetails_Country_Name"
FROM "Brands" AS b
-WHERE b."Name" > @value OR (b."Name" = @value AND b."AlwaysNull" > NULL) OR (b."Name" = @value AND b."AlwaysNull" IS NULL AND b."Id" > @value4)
+WHERE b."Name" > @value OR (b."Name" = @value AND b."AlwaysNull" IS NULL AND b."Id" > @value2)
ORDER BY b."Name", b."AlwaysNull", b."Id"
LIMIT @p
```
@@ -16,7 +16,7 @@ LIMIT @p
## Expression 0
```text
-[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(x => x.AlwaysNull).ThenBy(t => t.Id).Where(t => (((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.AlwaysNull.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0))) OrElse (((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.AlwaysNull.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0)) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(3)
+[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(x => x.AlwaysNull).ThenBy(t => t.Id).Where(t => (((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso False)) OrElse (((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.AlwaysNull == null)) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) > 0)))).Take(3)
```
## Result
diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetSecondPage_With_2_Items.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetSecondPage_With_2_Items.md
index 916311626ca..6bef425e8f7 100644
--- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetSecondPage_With_2_Items.md
+++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetSecondPage_With_2_Items.md
@@ -16,7 +16,7 @@ LIMIT @__p_2
## Expression 0
```text
-[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(3)
+[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.Int32]).value) > 0)))).Take(3)
```
## Result
diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetSecondPage_With_2_Items_NET10_0.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetSecondPage_With_2_Items_NET10_0.md
index 80b865ca900..14d7b63f828 100644
--- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetSecondPage_With_2_Items_NET10_0.md
+++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.GetSecondPage_With_2_Items_NET10_0.md
@@ -16,7 +16,7 @@ LIMIT @p
## Expression 0
```text
-[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(3)
+[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) > 0)))).Take(3)
```
## Result
diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13.md
index 017f339abfd..dcc9055a17d 100644
--- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13.md
+++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13.md
@@ -16,7 +16,7 @@ LIMIT @__p_2
## Expression 0
```text
-[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(6)
+[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.Int32]).value) > 0)))).Take(6)
```
## Result 3
diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET10_0.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET10_0.md
index adba1b2b006..d3e440c35d5 100644
--- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET10_0.md
+++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_After_Id_13_NET10_0.md
@@ -16,7 +16,7 @@ LIMIT @p
## Expression 0
```text
-[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) > 0)))).Take(6)
+[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderBy(t => t.Name).ThenBy(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) > 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) > 0)))).Take(6)
```
## Result 3
diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96.md
index d3dc3984660..ef3c0174609 100644
--- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96.md
+++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96.md
@@ -16,7 +16,7 @@ LIMIT @__p_2
## Expression 0
```text
-[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) < 0)))).Take(6)
+[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass14_0`1[System.Int32]).value) < 0)))).Take(6)
```
## Result 3
diff --git a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET10_0.md b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET10_0.md
index 50e11481562..40da3598267 100644
--- a/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET10_0.md
+++ b/src/HotChocolate/Data/test/Data.Tests/__snapshots__/PagingHelperIntegrationTests.Paging_First_5_Before_Id_96_NET10_0.md
@@ -16,7 +16,7 @@ LIMIT @p
## Expression 0
```text
-[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass6_0`1[System.Int32]).value) < 0)))).Take(6)
+[Microsoft.EntityFrameworkCore.Query.EntityQueryRootExpression].OrderByDescending(t => t.Name).ThenByDescending(t => t.Id).Where(t => ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) < 0) OrElse ((t.Name.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.String]).value) == 0) AndAlso (t.Id.CompareTo(value(GreenDonut.Data.Expressions.ExpressionHelpers+<>c__DisplayClass16_0`1[System.Int32]).value) < 0)))).Take(6)
```
## Result 3
diff --git a/website/src/docs/hotchocolate/v15/fetching-data/pagination.md b/website/src/docs/hotchocolate/v15/fetching-data/pagination.md
index 30266fb6036..68540d973d1 100644
--- a/website/src/docs/hotchocolate/v15/fetching-data/pagination.md
+++ b/website/src/docs/hotchocolate/v15/fetching-data/pagination.md
@@ -820,15 +820,18 @@ If no paging providers have been registered, a default paging provider capable o
The following options can be configured.
-| Property | Default | Description |
-| ------------------------------ | ------- | ----------------------------------------------------------------------------------- |
-| `MaxPageSize` | `50` | Maximum number of items a client can request via `first`, `last` or `take`. |
-| `DefaultPageSize` | `10` | The default number of items, if a client does not specify`first`, `last` or `take`. |
-| `IncludeTotalCount` | `false` | Add a `totalCount` field for clients to request the total number of items. |
-| `AllowBackwardPagination` | `true` | Include `before` and `last` arguments on the _Connection_. |
-| `RequirePagingBoundaries` | `false` | Clients need to specify either `first`, `last` or `take`. |
-| `InferConnectionNameFromField` | `true` | Infer the name of the _Connection_ from the field name rather than its type. |
-| `ProviderName` | `null` | The name of the pagination provider to use. |
+| Property | Default | Description |
+| ------------------------------ | ------------- | ------------------------------------------------------------------------------------------------ |
+| `MaxPageSize` | `50` | Maximum number of items a client can request via `first`, `last` or `take`. |
+| `DefaultPageSize` | `10` | The default number of items, if a client does not specify`first`, `last` or `take`. |
+| `IncludeTotalCount` | `false` | Add a `totalCount` field for clients to request the total number of items. |
+| `AllowBackwardPagination` | `true` | Include `before` and `last` arguments on the _Connection_. |
+| `RequirePagingBoundaries` | `false` | Clients need to specify either `first`, `last` or `take`. |
+| `InferConnectionNameFromField` | `true` | Infer the name of the _Connection_ from the field name rather than its type. |
+| `ProviderName` | `null` | The name of the pagination provider to use. |
+| `NullOrdering` | `Unspecified` | The database null sort order for nullable cursor keys (`NativeNullsFirst` or `NativeNullsLast`). |
+
+When paging over nullable cursor keys, configure `NullOrdering` to match your database's native null sorting behavior.
# Pagination defaults
@@ -837,7 +840,11 @@ If we want to enforce consistent pagination defaults throughout our app, we can
```csharp
builder.Services
.AddGraphQLServer()
- .ModifyPagingOptions(opt => opt.MaxPageSize = 100);
+ .ModifyPagingOptions(opt =>
+ {
+ opt.MaxPageSize = 100;
+ opt.NullOrdering = NullOrdering.NativeNullsLast;
+ });
```
[Learn more about possible PagingOptions](#pagingoptions)
diff --git a/website/src/docs/hotchocolate/v16/fetching-data/pagination.md b/website/src/docs/hotchocolate/v16/fetching-data/pagination.md
index 1a684b46434..bd9a77916cd 100644
--- a/website/src/docs/hotchocolate/v16/fetching-data/pagination.md
+++ b/website/src/docs/hotchocolate/v16/fetching-data/pagination.md
@@ -820,15 +820,36 @@ If no paging providers have been registered, a default paging provider capable o
The following options can be configured.
-| Property | Default | Description |
-| ------------------------------ | ------- | ----------------------------------------------------------------------------------- |
-| `MaxPageSize` | `50` | Maximum number of items a client can request via `first`, `last` or `take`. |
-| `DefaultPageSize` | `10` | The default number of items, if a client does not specify`first`, `last` or `take`. |
-| `IncludeTotalCount` | `false` | Add a `totalCount` field for clients to request the total number of items. |
-| `AllowBackwardPagination` | `true` | Include `before` and `last` arguments on the _Connection_. |
-| `RequirePagingBoundaries` | `false` | Clients need to specify either `first`, `last` or `take`. |
-| `InferConnectionNameFromField` | `true` | Infer the name of the _Connection_ from the field name rather than its type. |
-| `ProviderName` | `null` | The name of the pagination provider to use. |
+| Property | Default | Description |
+| ------------------------------ | ------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `MaxPageSize` | `50` | Maximum number of items a client can request via `first`, `last` or `take`. |
+| `DefaultPageSize` | `10` | The default number of items, if a client does not specify`first`, `last` or `take`. |
+| `IncludeTotalCount` | `false` | Add a `totalCount` field for clients to request the total number of items. |
+| `AllowBackwardPagination` | `true` | Include `before` and `last` arguments on the _Connection_. |
+| `RequirePagingBoundaries` | `false` | Clients need to specify either `first`, `last` or `take`. |
+| `InferConnectionNameFromField` | `true` | Infer the name of the _Connection_ from the field name rather than its type. |
+| `ProviderName` | `null` | The name of the pagination provider to use. |
+| `NullOrdering` | `Unspecified` | Controls how `null` values are ordered relative to non-null values when a nullable field is used as a cursor key. See [Nullable cursor keys](#nullable-cursor-keys) below. |
+
+# Nullable cursor keys
+
+When a cursor key field can be `null`, you must tell Hot Chocolate how the database orders `null` values so that cursor-based pagination produces correct results across pages.
+
+Set `NullOrdering` on `PagingOptions` to match your database's native behavior:
+
+| Value | When to use |
+| ------------------ | --------------------------------------------------------------------------------------------------- |
+| `Unspecified` | Default. The EF Core paging handler auto-detects the ordering for known providers (see note below). |
+| `NativeNullsFirst` | `null` is ordered **before** all non-null values (e.g. SQL Server, SQLite, in-memory LINQ). |
+| `NativeNullsLast` | `null` is ordered **after** all non-null values (e.g. PostgreSQL default behavior). |
+
+```csharp
+builder.Services
+ .AddGraphQLServer()
+ .ModifyPagingOptions(opt => opt.NullOrdering = NullOrdering.NativeNullsLast);
+```
+
+> **Auto-detection:** When `NullOrdering` is `Unspecified` and the EF Core paging handler is used, the correct ordering is detected automatically for all supported providers: PostgreSQL (`NativeNullsLast`), and SQL Server, SQLite, and in-memory (`NativeNullsFirst`). For unrecognized providers, an error is thrown when nullable cursor keys are present — set `PagingOptions.NullOrdering` explicitly to resolve it.
# Pagination defaults