-
-
Notifications
You must be signed in to change notification settings - Fork 12
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issue with Dynamic Sort column In KeySet for Value Type #49
Comments
Hello. Thanks for providing a repro. So, this is related to how column configuration is dependent on the generic type inferred inside I don't have a solution to make your particular method work (might be resolvable if I look into the insides if I can resolve the type in a better more reliable way), but right now I don't recommend calling methods that coerce column types in [Theory]
[InlineData("Id")]
[InlineData("String")]
[InlineData("Guid")]
[InlineData("IsDone")]
[InlineData("Created")]
[InlineData("CreatedNullable")]
public async Task Ascending_HasPreviousAsync_Buggy(string sortColumn)
{
var keysetContext = DbContext.MainModels.KeysetPaginate(
b => ConfigureAscendingForColumn(b, sortColumn));
var items = await keysetContext.Query
.Take(20)
.ToListAsync();
keysetContext.EnsureCorrectOrder(items);
var dtos = items.Select(x => new
{
x.Id,
x.String,
x.Guid,
x.IsDone,
x.Created,
x.CreatedNullable
}).ToList();
// exception is thrown when this line executes
await keysetContext.HasPreviousAsync(dtos);
}
// Just to illustrate.
private void ConfigureAscendingForColumn(KeysetPaginationBuilder<MainModel> b, string sortColumn)
{
switch (sortColumn)
{
case "Id":
b.Ascending(x => x.Id);
break;
case "String":
b.Ascending(x => x.String);
break;
case "Guid":
b.Ascending(x => x.Guid);
break;
case "IsDone":
b.Ascending(x => x.IsDone);
break;
case "Created":
b.Ascending(x => x.Created);
break;
case "CreatedNullable":
b.Ascending(x => x.CreatedNullable);
break;
default:
throw new NotImplementedException($"Unsupported {nameof(sortColumn)} {sortColumn}");
}
} I do something similar in the samples (but on an even higher level). A bit more code maybe, but works. If you were to prebuild the keyset definitions (which I recommend), then you'll have to do the branching on a higher level anyway. |
@mrahhal thanks for the response. meanwhile I discovered something that might be of help but unfortunately I am not well versed in C# expressions, maybe you might have an idea. so if we modify the constructor of public KeysetColumn(bool isDescending, Expression<Func<T, TColumn>> expression)
//: base(isDescending, expression)
: base(isDescending, ProcessExpression(expression))
{}
private static Expression<Func<T, TColumn>> ProcessExpression(Expression<Func<T, TColumn>> expression)
{
var body = expression.Body;
if (body is UnaryExpression unaryExpression && unaryExpression.NodeType == ExpressionType.Convert)
{
// Check if it's a unary expression with conversion
if (unaryExpression.Operand is MemberExpression operand)
{
// at this point, the operand is a MemberExpression that has the true type of TColumn
// The problem now is how to create a lambda expression 'Expression<Func<T, TColumn>>' for 'operand' without the compiler forcing us to explicitly
// define an Expression.Convert(operand, typeof(object))
throw new NotImplementedException();
}
}
// If it doesn't match the expected format, return the original expression
return expression;
} Please see the comments in the code above. |
The problem is that after v1.3.0 I support any kind of expression, so I'm hesitant to have this kind of special casing processing specific expressions, as even if it works this would work in certain cases but not others. Forcing no coersion is better than this arbitrary behavior. In any case, I'm going to investigate having better more reliable type inference at some point, but for now the alternative works just fine without major changes. |
Thanks for your time @mrahhal very much appreciated. |
No worries. I'm gonna keep this open for now in case I look into it later. |
First of all, I would like to thank you for this very useful package that abstracts away the complexity of Keyset Pagination.
So to the issue I seem to have encountered, I have a requirement to use a dynamic sort column in keyset.
in cases where the sort column is a
reference
type likestring
everything works Ok, but when the sort column is avalue
type likeint
,DateTime
, orGuid
, it fails with either of the following exceptions depending on the combination used.System.InvalidOperationException : The binary operator LessThan is not defined for the types 'System.Object' and 'System.Object'.
or
System.InvalidOperationException : The binary operator GreaterThan is not defined for the types 'System.Object' and 'System.Object'.
a simple reproduction can be done by adding the code below to KeysetPaginationTest.cs and running the newly added test methods (
Ascending_HasPreviousAsync_Buggy
,Descending_HasPreviousAsync_Buggy
,Ascending_HasNextAsync_Buggy
,Descending_HasNextAsync_Buggy
)from my little investigation, it seems that in order to coerce an expression returning a value type into
Func<TEntity,object>
the compiler needs to insert aConvert(expr, typeof(object))
, aUnaryExpression
.For strings and other reference types, there is no need to box, so a "straight" member expression is returned.
The text was updated successfully, but these errors were encountered: