No puedo explicar por qué usar un Int32
no funciona pero usando un string
. ¿No son ambos tipos EDM "primitivos" y no implementan ambos IComparable
? No entiendo el comportamiento diferente.
De todos modos, parece que es necesario pasar todas las expresiones de la colección con el tipo concreto para que se ordene para evitar la conversión de tipo fallido. En otras palabras, no es un IComparable
, pero en lugar de una int
, una string
, un DateTime
, etc.
tuve éxito para lograr esto en la línea de la idea en esta respuesta: How to check for the presence of an OrderBy in a ObjectQuery<T> expression tree
Definir una interfaz que hace no depende del tipo para ordenar por, pero solo el tipo de entidad. (El siguiente ejemplo se generaliza a entidades arbitrarias. Si sólo desea que por User
eliminar el parámetro genérico y reemplazar TEntity
en los queryables por User
.)
public interface IOrderByExpression<TEntity> where TEntity : class
{
IOrderedQueryable<TEntity> ApplyOrderBy(IQueryable<TEntity> query);
IOrderedQueryable<TEntity> ApplyThenBy(IOrderedQueryable<TEntity> query);
}
Definir una implementación de la interfaz que ahora toma el tipo de ordenar por como segundo parámetro genérico:
public class OrderByExpression<TEntity, TOrderBy> : IOrderByExpression<TEntity>
where TEntity : class
{
private Expression<Func<TEntity, TOrderBy>> _expression;
private bool _descending;
public OrderByExpression(Expression<Func<TEntity, TOrderBy>> expression,
bool descending = false)
{
_expression = expression;
_descending = descending;
}
public IOrderedQueryable<TEntity> ApplyOrderBy(
IQueryable<TEntity> query)
{
if (_descending)
return query.OrderByDescending(_expression);
else
return query.OrderBy(_expression);
}
public IOrderedQueryable<TEntity> ApplyThenBy(
IOrderedQueryable<TEntity> query)
{
if (_descending)
return query.ThenByDescending(_expression);
else
return query.ThenBy(_expression);
}
}
Entonces ApplyOrderBy
se vería así:
public IQueryable<TEntity> ApplyOrderBy<TEntity>(IQueryable<TEntity> query,
params IOrderByExpression<TEntity>[] orderByExpressions)
where TEntity : class
{
if (orderByExpressions == null)
return query;
IOrderedQueryable<TEntity> output = null;
foreach (var orderByExpression in orderByExpressions)
{
if (output == null)
output = orderByExpression.ApplyOrderBy(query);
else
output = orderByExpression.ApplyThenBy(output);
}
return output ?? query;
}
Y puede ser utilizado de la siguiente manera:
var query = context.Users ... ;
var queryWithOrderBy = ApplyOrderBy(query,
new OrderByExpression<User, string>(u => u.UserName), // a string, asc
new OrderByExpression<User, int>(u => u.UserId, true)); // an int, desc
var result = queryWithOrderBy.ToList(); // didn't throw an exception for me
La necesidad de especificar los parámetros de tipo genérico explícitamente en los casos OrderByExpression
no es agradable, pero no pude encontrar una manera para que el compilador infiere los tipos.(Yo estaba esperando que fuera, porque el compilador infiere el User
como TEntity
de query
para el método ApplyOrderBy
, lo que esperaba que conoce la TEntity
de OrderByExpression
(igual User
también). Así que el parámetro lambda u
debe ser conocido como un User
y luego el compilador podría derivar del tipo de UserName
como string
y desde UserId
como int
. Pero esta teoría es aparentemente mal. el compilador se queja y quiere tener los tipos genéricos de forma explícita.)
1, Gran respuesta, funciona perfectamente y es exactamente lo que esperaba :) –
He estado luchando con esto, y probablemente me rendiré , pero reconozco que debería ser posible usar Expression>, luego cuando llamas a ApplyOrderBy inspecciona la expresión Lambda, busca el tipo del parámetro y luego construye una nueva expresión Lambda correctamente tipada. Aunque parece demasiado trabajo duro. –
Excelente respuesta, funcionó para mí 'fuera de la caja' –