3

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;
    }
Trixz
  • 190
  • 13
  • 2
    Have a look at `ThenBy`: `source.OrderBy(x => ...).ThenBy(x => ...)` – Dmitry Bychenko Sep 09 '15 at 15:08
  • Check out: http://stackoverflow.com/questions/307512/how-do-i-apply-orderby-on-an-iqueryable-using-a-string-column-name-within-a-gene – Magnus Sep 09 '15 at 15:14
  • First, you should post an example of what you want to do, not a link to a post that probably doesn't apply to your case. Second, you don't have to fiddle with expressions to order by a property that you select at runtime. You can apply `OrderBy` to an Queryable, then apply `ThenBy` to the returned queryable. Repeat as many times as needed – Panagiotis Kanavos Sep 09 '15 at 15:14
  • @Magnus expressions may not be needed at all, eg it's possible to write `if (x) { myQuery=myQuery.ThenBy(it=>it.Date);} ` – Panagiotis Kanavos Sep 09 '15 at 15:16
  • @PanagiotisKanavos If I've understood the question correctly the problem is with selecting from a complex property in the class. eg. `OrderBy("SomeProperty.Name")` rather than a simple one `OrderBy("Name")` – Magnus Sep 09 '15 at 15:19
  • It's an EF question - there are no "complex properties", there are related entities which *can't* be ordered. There *are* workarounds that don't require loading everything in memory though. – Panagiotis Kanavos Sep 09 '15 at 15:26
  • @Trixz there are no complex properties in EF, there are related entities. You are asking how to sort by a child property. You can't write in LINQ something that doesn't translate in SQL. In this case, what does "sort by a child property" mean? Do you want to sort the root entities or the child entities in each root? How would you express that in SQL? – Panagiotis Kanavos Sep 09 '15 at 15:31
  • I do a quick comment and à longer tomorrow (evening in sweden now). I do want to sort by a child property and by a string "SomePropery.Name" and even "SomeProperty.AnotherProp.Name". In select i would do a select t1.prop, t2.prop from table t1 join table t2 on t1.id = t2.otherId order by t2.prop Edit: iPad posted to quick, added text. – Trixz Sep 09 '15 at 20:55
  • Updated the question with additional information and tried to fix some errors. – Trixz Sep 10 '15 at 06:51

1 Answers1

1

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" 
            );
Trixz
  • 190
  • 13