Keep NULL rows last on Dynamic Linq Order By

后端 未结 4 1478
一向
一向 2021-02-14 00:28

I am using this snippet below for Ordering my Linq queries dynamically and works great. I am not great at reflection or complex linq queries but I need a way that when ascendin

4条回答
  •  别跟我提以往
    2021-02-14 00:45

    It's relatively simple. For each passed sort selector, the method executes one of the following:

    .OrderBy(x => x.Member)
    .ThenBy(x => x.Member)
    .OrderByDescending(x => x.Member)
    .ThenByDescendiong(x => x.Member)
    

    When the x.Member type is reference type or nullable value type, the desired behavior can be achieved by pre ordering with the same direction by the following expression

    x => x.Member == null ? 1 : 0
    

    Some people use ordering by bool, but I prefer to be explicit and use conditional operator with specific integer values. So the corresponding calls for the above calls would be:

    .OrderBy(x => x.Member == null ? 1 : 0).ThenBy(x => x.Member)
    .ThenBy(x => x.Member == null ? 1 : 0).ThenBy(x => x.Member)
    .OrderByDescending(x => x.Member == null ? 1 : 0).ThenByDescending(x => x.Member)
    .ThenByDescending(x => x.Member == null ? 1 : 0).ThenByDescending(x => x.Member)
    

    i.e. the original method on the pre order expression followed by the ThenBy(Descending) with the original expression.

    Here is the implementation:

    public static class OrderByHelper
    {
        public static IOrderedQueryable ThenBy(this IEnumerable source, string orderBy)
        {
            return source.AsQueryable().ThenBy(orderBy);
        }
    
        public static IOrderedQueryable ThenBy(this IQueryable source, string orderBy)
        {
            return OrderBy(source, orderBy, false);
        }
    
        public static IOrderedQueryable OrderBy(this IEnumerable source, string orderBy)
        {
            return source.AsQueryable().OrderBy(orderBy);
        }
    
        public static IOrderedQueryable OrderBy(this IQueryable source, string orderBy)
        {
            return OrderBy(source, orderBy, true);
        }
    
        private static IOrderedQueryable OrderBy(IQueryable source, string orderBy, bool initial)
        {
            if (string.IsNullOrWhiteSpace(orderBy))
                orderBy = "ID DESC";
            var parameter = Expression.Parameter(typeof(T), "x");
            var expression = source.Expression;
            foreach (var item in ParseOrderBy(orderBy, initial))
            {
                var order = item.PropertyName.Split('.')
                    .Aggregate((Expression)parameter, Expression.PropertyOrField);
                if (!order.Type.IsValueType || Nullable.GetUnderlyingType(order.Type) != null)
                {
                    var preOrder = Expression.Condition(
                            Expression.Equal(order, Expression.Constant(null, order.Type)),
                            Expression.Constant(1), Expression.Constant(0));
                    expression = CallOrderBy(expression, Expression.Lambda(preOrder, parameter), item.Direction, initial);
                    initial = false;
                }
                expression = CallOrderBy(expression, Expression.Lambda(order, parameter), item.Direction, initial);
                initial = false;
            }
            return (IOrderedQueryable)source.Provider.CreateQuery(expression);
        }
    
        private static Expression CallOrderBy(Expression source, LambdaExpression selector, SortDirection direction, bool initial)
        {
            return Expression.Call(
                typeof(Queryable), GetMethodName(direction, initial),
                new Type[] { selector.Parameters[0].Type, selector.Body.Type },
                source, Expression.Quote(selector));
        }
    
        private static string GetMethodName(SortDirection direction, bool initial)
        {
            return direction == SortDirection.Ascending ?
                (initial ? "OrderBy" : "ThenBy") :
                (initial ? "OrderByDescending" : "ThenByDescending");
        }
    
        private static IEnumerable ParseOrderBy(string orderBy, bool initial)
        {
            if (String.IsNullOrEmpty(orderBy))
                yield break;
    
            string[] items = orderBy.Split(',');
    
            foreach (string item in items)
            {
                string[] pair = item.Trim().Split(' ');
    
                if (pair.Length > 2)
                    throw new ArgumentException(String.Format("Invalid OrderBy string '{0}'. Order By Format: Property, Property2 ASC, Property2 DESC", item));
    
                string prop = pair[0].Trim();
    
                if (String.IsNullOrEmpty(prop))
                    throw new ArgumentException("Invalid Property. Order By Format: Property, Property2 ASC, Property2 DESC");
    
                SortDirection dir = SortDirection.Ascending;
    
                if (pair.Length == 2)
                    dir = ("desc".Equals(pair[1].Trim(), StringComparison.OrdinalIgnoreCase) ? SortDirection.Descending : SortDirection.Ascending);
    
                yield return new OrderByInfo() { PropertyName = prop, Direction = dir, Initial = initial };
    
                initial = false;
            }
    
        }
    
        private class OrderByInfo
        {
            public string PropertyName { get; set; }
            public SortDirection Direction { get; set; }
            public bool Initial { get; set; }
        }
    
        private enum SortDirection
        {
            Ascending = 0,
            Descending = 1
        }
    }
    

提交回复
热议问题