如何在LINQ中使用动态生成的OrderBy?

时间:2017-10-30 16:23:32

标签: c# linq

基于API,我可以有多个参数可以在order by中使用。有一个函数可以创建一个dynamic order by参数作为字符串。我想在.OrderBy中使用它,但不知道如何做到这一点。

API调用:

{{url}}?keyword=singer&page=12&size=5&sortby=LastName&sortby=FirstName

代码:

public CallCenterPageResult<CallCenterCustomerSummary> GetCustomers(int page, int pageSize, IEnumerable<SortParameter> sortParameters, string keyword)
{
    using (var ctx = new EFCallCenterContext())
    {
        var customerDetails = ctx.CallCenterCustomers
                                 .Where(ccs => ccs.IsDeleted == false && (ccs.FirstName.Contains(keyword) || ccs.LastName.Contains(keyword) || ccs.Phone.Contains(keyword)))
                                 .OrderBy(sortParameters.ToOrderBy()) // "LastName ASC, FirstName ASC"
                                 .Skip(pageSize * (page - 1)).Take(pageSize)
                                 .ToList();

        return customerDetails;
    }
}

通过以下方式获取订单的扩展方法:

static class RepositoryExtensions
{
    public static string ToOrderBy(this IEnumerable<SortParameter> parameters)
    {
        return string.Join(", ", parameters.Select(p => p.SortBy + (p.Descending ? " DESC" : " ASC")));
    }
}

Output:

"LastName ASC, FirstName ASC"

接受动态LINQ的扩展方法:

public static class Extension
{
    public static IOrderedQueryable<T> OrderBy<T>(this IQueryable<T> source, string property)
    {
        return ApplyOrder<T>(source, property, "OrderBy");
    }
    public static IOrderedQueryable<T> OrderByDescending<T>(this IQueryable<T> source, string property)
    {
        return ApplyOrder<T>(source, property, "OrderByDescending");
    }
    public static IOrderedQueryable<T> ThenBy<T>(this IOrderedQueryable<T> source, string property)
    {
        return ApplyOrder<T>(source, property, "ThenBy");
    }
    public static IOrderedQueryable<T> ThenByDescending<T>(this IOrderedQueryable<T> source, string property)
    {
        return ApplyOrder<T>(source, property, "ThenByDescending");
    }
    static IOrderedQueryable<T> ApplyOrder<T>(IQueryable<T> source, string property, string methodName)
    {
        var props = property.Split('.');
        var type = typeof(T);
        var arg = Expression.Parameter(type, "x");
        Expression expr = arg;
        foreach (string prop in props)
        {
            // use reflection (not ComponentModel) to mirror LINQ
            PropertyInfo pi = type.GetProperty(prop);
            expr = Expression.Property(expr, pi); // Errors out here.
            type = pi.PropertyType;
        }
        var delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
        var lambda = Expression.Lambda(delegateType, expr, arg);

        var result = typeof(Queryable).GetMethods().Single(
                method => method.Name == methodName
                        && method.IsGenericMethodDefinition
                        && method.GetGenericArguments().Length == 2
                        && method.GetParameters().Length == 2)
                .MakeGenericMethod(typeof(T), type)
                .Invoke(null, new object[] { source, lambda });
        return (IOrderedQueryable<T>)result;
    }
}

错误:

System.ArgumentNullException: Value cannot be null.
Parameter name: property
Scrren开枪: enter image description here

这是第一次使用此复杂查询,因此不确定如何执行此操作。如果需要,我可以添加更多信息。

3 个答案:

答案 0 :(得分:0)

看起来错误发生是因为#Model class Object(models.Model): owner = models.ForeignKey(User) category = models.ForeignKey(Category, null=True, default=None) name = models.CharField(max_length=255) address = models.CharField(max_length=255, default='') #Serializer class ObjectSerializer(ModelSerializer): is_folder = BooleanField(read_only=True) class Meta: model = Object fields = ('id', 'category', 'is_folder', 'name', 'address') #ViewSet class ObjectsViewSet(BaseViewSet, RetrieveModelMixin, ListModelMixin, CreateModelMixin, UpdateModelMixin, DestroyModelMixin): queryset = Object.objects serializer_class = ObjectSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly, ObjectPermission) def get_queryset(self): queryset = super(ObjectsViewSet, self).get_queryset() return queryset.filter(owner=self.request.user) #Permission class ObjectPermission(permissions.BasePermission): def has_permission(self, request, view): if request.method in ['GET']: return request.user.has_perm('view_object') if request.method in ['POST']: return request.user.has_perm('add_object') if request.method in ['PUT', 'PATCH']: return request.user.has_perm('change_object') if request.method in ['DELETE']: return request.user.has_perm('delete_object') return False def has_object_permission(self, request, view, obj): if request.method in ['GET']: return request.user.has_perm('view_object', obj) if request.method in ['PUT', 'PATCH']: return request.user.has_perm('change_object', obj) if request.method in ['DELETE']: return request.user.has_perm('delete_object', obj) return False 为空。并且它是null,因为,我认为,代表pi泛型的类没有名为T的属性。我会尝试以下内容:

LastName ASC, FirstName ASC

希望这会让你朝着正确的方向前进。

答案 1 :(得分:-1)

经过一些试验和错误后,我能够找到答案。

测试如下:

.OrderBy("LastName ASC, FirstName ASC")
.OrderBy("LastName ASC")
.OrderBy("LastName ASC,FirstName DESC")

<强>的LINQ:

public CallCenterPageResult<CallCenterCustomerSummary> GetCustomers(int page, int pageSize, IEnumerable<SortParameter> sortParameters, string keyword)
{
    using (var ctx = new EFCallCenterContext())
    {
        var customerDetails = ctx.CallCenterCustomers
                                 .Where(ccs => ccs.IsDeleted == false && (ccs.FirstName.Contains(keyword) || ccs.LastName.Contains(keyword) || ccs.Phone.Contains(keyword)))
                                 .OrderBy(o => o.Equals(sortParameters.ToOrderBy()))
                                 .Skip(pageSize * (page - 1)).Take(pageSize)
                                 .ToList();

        return customerDetails;
    }
}

助手类:

public static class OrderByHelper
{
    public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> enumerable, string orderBy)
    {
        return enumerable.AsQueryable().OrderBy(orderBy).AsEnumerable();
    }

    public static IQueryable<T> OrderBy<T>(this IQueryable<T> collection, string orderBy)
    {
        foreach (var orderByInfo in ParseOrderBy(orderBy))
        {
            collection = ApplyOrderBy(collection, orderByInfo);
        }

        return collection;
    }

    private static IQueryable<T> ApplyOrderBy<T>(IQueryable<T> collection, OrderByInfo orderByInfo)
    {
        var props = orderByInfo.PropertyName.Split('.');
        var type = typeof (T);

        var arg = Expression.Parameter(type, "x");
        Expression expr = arg;
        foreach (var prop in props)
        {
            var pi = type.GetProperty(prop);
            expr = Expression.Property(expr, pi);
            type = pi.PropertyType;
        }
        var delegateType = typeof (Func<,>).MakeGenericType(typeof (T), type);
        var lambda = Expression.Lambda(delegateType, expr, arg);
        string methodName;

        if (!orderByInfo.Initial && collection is IOrderedQueryable<T>)
        {
            methodName = orderByInfo.Direction == SortDirection.Ascending ? "ThenBy" : "ThenByDescending";
        }
        else
        {
            methodName = orderByInfo.Direction == SortDirection.Ascending ? "OrderBy" : "OrderByDescending";
        }

        return (IOrderedQueryable<T>) typeof (Queryable).GetMethods().Single(
            method => method.Name == methodName
                      && method.IsGenericMethodDefinition
                      && method.GetGenericArguments().Length == 2
                      && method.GetParameters().Length == 2)
            .MakeGenericMethod(typeof (T), type)
            .Invoke(null, new object[] {collection, lambda});
    }

    private static IEnumerable<OrderByInfo> ParseOrderBy(string orderBy)
    {
        if (string.IsNullOrEmpty(orderBy))
        {
            yield break;
        }

        var items = orderBy.Split(',');
        var initial = true;
        foreach (var item in items)
        {
            var pair = item.Trim().Split(' ');

            if (pair.Length > 2)
            {
                throw new ArgumentException(
                    $"Invalid OrderBy string '{item}'. Order By Format: Property, Property2 ASC, Property2 DESC");
            }

            var prop = pair[0].Trim();

            if (string.IsNullOrEmpty(prop))
            {
                throw new ArgumentException(
                    "Invalid Property. Order By Format: Property, Property2 ASC, Property2 DESC");
            }

            var 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
    }

<强> Referances:

Dynamic LINQ OrderBy on IEnumerable<T>

http://aonnull.blogspot.com/2010/08/dynamic-sql-like-linq-orderby-extension.html

答案 2 :(得分:-1)

如果我理解正确的问题。

Expression<Func<TEntity, TKey>> genericParameter = null;

genericParameter = x => x.foo;

var customerDetails = ctx.CallCenterCustomers
                                 .Where(ccs => ccs.IsDeleted == false && (ccs.FirstName.Contains(keyword) || ccs.LastName.Contains(keyword) || ccs.Phone.Contains(keyword)))
                                 .OrderBy(genericParameter)