Skip to content

[Feature] Extend OrderBy functionality #73

@joacar

Description

@joacar

Hi,

Haven't used this package (yet) but it seem to have a lot of functionality that I might use as my project evolves. It also lacks the only functionality I currently need :) Since the reflection calls, expression tree parsing is probably more efficient than my code I thought to create a PR with a new feature.

To the point: I use EF Core (currently in a MVC application) with an extension OrderBy that takes a string Property [ASC | DESC] | ["," ...] (abusing BNF ;). However I need to be able to order by more complex expressions such as p => p.A.Cost + p.B.Cost + ... etc where, in this example, Cost might be nullable. So I also need to sort null values last, if desired.

So I've created a class Orderable

public class Orderable
{
	internal Orderable(string expression)
	{
		var parts = expression.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
		if (parts.Length == 0 || parts.Length > 2)
		{
			throw new ArgumentException("Expression must be of form 'TKey [ASC | DESC]'", nameof(expression));
		}
		Property = parts[0];
		if (parts.Length == 2)
		{
			Descending = "DESC".Equals(parts[1], StringComparison.OrdinalIgnoreCase);
		}
	}

	public Orderable(Expression expression, Type expressionType, bool descending = false, bool nullLast = true)
	{
		Expression = expression;
		ExpressionType = expressionType;
		Descending = descending;
		NullLast = nullLast;
	}

	public static Orderable Create<TModel, TKey>(Expression<Func<TModel, TKey>> expression, bool descending = false, bool nullLast = true)
	{
		return new Orderable(expression, expression.ReturnType, descending, nullLast);
	}

	public Expression Expression { get; }

	public Type ExpressionType { get; set; }

	public string Parameter { get; set; }

	public bool NullLast { get; }

	public string Property { get; }

	public bool Descending { get; }
}

Extension method

public static IQueryable<T> SortBy<T>(this IQueryable<T> source, params Ordering[] orderings) where T : class
{
	// Argument checks ommitted
	string methodName = "";
	IQueryable<T> query = source;
	foreach (var ordering in orderings)
	{
		if (string.IsNullOrEmpty(methodName))
		{
			methodName = ordering.Descending ? "OrderByDescending" : "OrderBy";
		}
		else
		{
			methodName = ordering.Descending ? "ThenByDescending" : "ThenBy";
		}
		ParameterExpression parameter = Expression.Parameter(source.ElementType, string.Empty);
		LambdaExpression lambda;
		Expression propertyExpression;
		Type propertyType;
		if (string.IsNullOrEmpty(ordering.Property))
		{
			// Build directly from Expression
			propertyType = ordering.ExpressionType;
			lambda = (LambdaExpression)ordering.Expression;
			if (ordering.NullLast)
			{
				// TODO: Check type is nullable
				try
				{
					var op = lambda.Body;
					var equal = Expression.Equal(op, Expression.Constant(null));
					var condition = Expression.Condition(equal, Expression.Constant(1), Expression.Constant(0));
					Expression mce = Expression.Call(
						typeof(Queryable),
			   methodName,
			   new[] { source.ElementType, typeof(int) },
			   query.Expression,
				Expression.Quote(condition));
					query = (IQueryable<T>)query.Provider.CreateQuery(mce);

				}
				catch (Exception)
				{
				}
			}
		}
		else
		{
			lambda = CreateLambdaExpression(parameter, ordering, out propertyExpression);
			propertyType = propertyExpression.Type;
		}

		Expression methodCallExpression = Expression.Call(
			   typeof(Queryable),
			   methodName,
			   new[] { source.ElementType, propertyType },
			   query.Expression,
				Expression.Quote(lambda));
		query = (IQueryable<T>)query.Provider.CreateQuery(methodCallExpression);
	}

So, is this something that is align with the project? If so I'm happy to create a PR.

Regards

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions