Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

IEnumerable OrderBy by String with complex property

I need to sort a query by string and found this that works well. The problem is that it can't handle complex properties related entities such as "ComplexProperty.Property""RelatedEntity.Property" since it only searches the main class.

I'm thinking I should be able to parse the parts[0] by a . and somehow recursively check each type, but I can't figure out exacly how. Is this possible or am I at a dead end?

The reason for this is that I have an "old" solution (MVC 3) with a webgrid on, and the webgrid needs all data to do the sorting (it's an detatched EF4 solution) and it just takes to much time. I need to pass the sort into the query and only retrieve the posts for that page in the paging. The webgrid calls the same controller with sort and sortdir parameters and update with ajax.

If it's not possible, maybe there is another solution that I should look at that anyoune can hint about?

Edit with clarifications Today the solution gets all violations, sends them to a webGrid and lets the webgrid do the paging and sorting. The solution has been live for many years and the violation table has grown to the point that the page is really slow, mostly becouse of that all the data is fetched every time. I have implemented paging to the repository to recieve only a portion of the class. The repository today works with IEnumerable and the ServiceLayer (business) between presentation and repository, always returns a List to presentation layer.

Here is the SQL i want to be able to use

SELECT vi.ViolationId, ar.AreaName
  FROM [Violation] vi
  join [ControlPoint] cp on vi.ControlPointId = cp.ControlPointId
  join [Area] ar on ar.AreaId = cp.AreaId
order by ar.AreaName

I need to do this with an orderBy(string sortExpression) like this.

violations.OrderBy("ControlPoint.Area.AreaName")

and found this function (linked above) as a foundation for this.

    public static IEnumerable<T> OrderBy<T>(this IEnumerable<T> list, string sortExpression)
    {
        sortExpression += "";
        string[] parts = sortExpression.Split(' ');
        bool descending = false;
        string property = "";

        if (parts.Length > 0 && parts[0] != "")
        {
            property = parts[0];

            if (parts.Length > 1)
            {
                descending = parts[1].ToLower().Contains("esc");
            }

            PropertyInfo prop = typeof(T).GetProperty(property);

            // handle Prop.SubProp
            // prop.GetType().GetProperty

            if (prop == null)
            {
                throw new Exception("No property '" + property + "' in + " + typeof(T).Name + "'");
            }

            if (descending)
                return list.OrderByDescending(x => prop.GetValue(x, null));
            else
                return list.OrderBy(x => prop.GetValue(x, null));
        }
        return list;
    }
like image 740
Trixz Avatar asked Mar 09 '26 11:03

Trixz


1 Answers

Ok, now it's working and I post my final results. Thanks for all the input, I would have thought this was not doable without all the comments

First all the helper methods

public static IQueryable<T> OrderBy<T>(this IQueryable<T> source, string property, bool descending)
    {
        if(!descending)
            return ApplyOrder<T>(source, property, "OrderBy");
        else
            return ApplyOrder<T>(source, property, "OrderByDescending");
    }

    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)
    {
        string[] props = property.Split('.');
        Type type = typeof(T);
        ParameterExpression 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);
            type = pi.PropertyType;
        }
        Type delegateType = typeof(Func<,>).MakeGenericType(typeof(T), type);
        LambdaExpression lambda = Expression.Lambda(delegateType, expr, arg);

        object 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;
    }

    public static bool TryParseSortText(this string sortExpression, out string property, out bool descending)
    {
        descending = false;
        property = string.Empty;

        if (string.IsNullOrEmpty(sortExpression))
            return false;

        string[] parts = sortExpression.Split(' ');

        if (parts.Length > 0 && !string.IsNullOrEmpty(parts[0]))
        {
            property = parts[0];

            if (parts.Length > 1)
            {
                descending = parts[1].ToLower().Contains("esc");
            }
        }
        else
            return false;

        return true;
    }

Then in service layer I have this

    public PagedResult<Violation> GetViolationByModule(PagedRequest pagedRequest, long moduleId, long stakeHolderId, Expression<Func<Violation, bool>> filter, string sort = "")
    {
        return ExceptionManager.Process(() => _GetViolationByModule(pagedRequest, moduleId, stakeHolderId, filter, sort),
         "ServicePolicy");

    }

    private PagedResult<Violation> _GetViolationByModule(PagedRequest pagedRequest, long moduleId, long stakeHolderId, Expression<Func<Violation, bool>> filter, string sort = "")
    {
        var query = ViolationRepository.GetViolationByModule(moduleId, stakeHolderId);

        if(!string.IsNullOrEmpty(sort))
        {
            string sortProperty = string.Empty;
            bool desc = false;
            if(sort.TryParseSortText(out sortProperty, out desc))
            {
                query = query.OrderBy(sortProperty, desc);
            }

        }
        if (filter != null)
        {
            query = query.Where(filter);
        }

        var violations = _GetPagedResult(pagedRequest, query);
        foreach (var violation in violations.Results)
        {
            var user = FrontendUserRepository.GetFrontendUserByName(violation.ReportBy);
            if (user != null)
            {
                violation.ReportUserInitial = user.Initial;
            }
            else
            {
                violation.ReportUserInitial = violation.ReportBy;
            }

        }

        return violations;
    }

And from the controller I can call this by

        var pagedUnamendedViolationList = ViolationService.GetViolationByModule(new PagedRequest() { Page = page, PageSize = pageSize },
            moduleId,
            stakeholderId,
            v => (fUser == null || v.ControlPoint.Area.FronendUsers.Contains(fUser)) && !v.IsAmended,
            "ControlPoint.Area.AreaName DESC" 
            );
like image 109
Trixz Avatar answered Mar 12 '26 03:03

Trixz