question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Custom sort with value?

See original GitHub issue

DESCRIPTION

I’m looking to implement a custom sort that accepts a value, similar to GET /api/blogs?sort=count(articles) HTTP/1.1 like in the sorting example, but not sure I’m looking in the right place. Where would be a good place to look to implement something like this. I see examples with custom filtering, but I don’t see an example of a custom sort that accepts a parameter. Essentially I’m hoping to add a sort by distance feature that allows me to execute my code in Sql Server. Something like this:

// Distance parameters would be lat/long
GET /api/cities?sort=distance(27.0, -92.0)
// Somewhere in a ResourceDefinition, or Controller, or somewhere else?
var point = new Point(lat, long);
var results = queryable.OrderBy(m => m.Location.Distance(point));

Is there an exposed hook, or good place for me to look to get started on something like this?

VERSIONS USED

  • JsonApiDotNetCore version: 4.0.0-rc
  • ASP.NET Core version: 3.1
  • Entity Framework Core version: 5.0
  • Database provider: Sql Server

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:8 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
bart-degreedcommented, Dec 9, 2020

It looks like you did everything right, based on my suggestions. JADNC adds sort by ID if none is provided, to prevent random orderings. It is unaware of your custom sort expression. EF Core probably understands that the sort by ID already makes the rows unique, so it discards any additional sort expressions (yours). The problem stems from here, where we first append the outcome of OnRegisterQueryableHandlersForQueryStringParameters to the EF Core query, before adding the outcome of query string parameters (including default sort if omitted). We do things in this order because usually devs implement OnRegisterQueryableHandlersForQueryStringParameters to add extra filter conditions and we don’t want them to end up after sorting, projections etc. So my suggested approach does not work in your case.

What you can try instead:

public class CitiesRepository : EntityFrameworkCoreRepository<City>
{
    private readonly IHttpContextAccessor _httpContextAccessor;
    private double _fiftyMilesInMeters = 1.0;

    public CitiesRepository(IHttpContextAccessor httpContextAccessor, ITargetedFields targetedFields,
        IDbContextResolver contextResolver, IResourceGraph resourceGraph, IResourceFactory resourceFactory,
        IEnumerable<IQueryConstraintProvider> constraintProviders, ILoggerFactory loggerFactory)
        : base(targetedFields, contextResolver, resourceGraph, resourceFactory, constraintProviders, loggerFactory)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    protected override IQueryable<City> ApplyQueryLayer(QueryLayer layer)
    {
        var source = base.ApplyQueryLayer(layer);

        var parameterValue = GetDistanceFromQueryString();
        if (parameterValue != null)
        {
            var point = CreatePoint(parameterValue);

            source = source
                .Where(c => c.Location.IsWithinDistance(point, _fiftyMilesInMeters))
                .OrderBy(c => c.Location.Distance(point));
        }

        return source;
    }

    private string GetDistanceFromQueryString()
    {
        string value = _httpContextAccessor.HttpContext.Request.Query["distance"];
        return string.IsNullOrEmpty(value) ? null : value;
    }

    private Point CreatePoint(string parameterValue)
    {
        throw new NotImplementedException();
    }
}

Next, you’ll need to register the custom repository from Startup.ConfigureServices (if you’re using auto-discovery, you can skip this step):

services.AddResourceRepository<CitiesRepository>();

The above results in your ordering being added after any existing orderings, which makes the earlier orderings irrelevant. It now depends on how smart EF Core is in eliminating unneeded sorts and reordering the query so that your where clause executes server-side. I hope this helps.

Another solution would be to implement CitiesRepository.ApplyQueryLayer like this:

protected override IQueryable<City> ApplyQueryLayer(QueryLayer layer)
{
    layer.Sort = null;
    return base.ApplyQueryLayer(layer);
}

and keep using your resource definition with OnRegisterQueryableHandlersForQueryStringParameters override. The code above throws out any sort coming from query string or IResourceDefinition.OnApplySort. The downside is you’ll get random ordering in case users do not specify your distance query string parameter. But you can even work around that by only clearing layer.Sort if the query string parameter is provided (using IHttpContextAccessor check like above).

1reaction
bart-degreedcommented, Dec 5, 2020

There’s basically two ways to approach this: a generic solution and an endpoint-specific solution. The former is complex and involves a lot of work:

  1. Think about what the EF Core LINQ query would look like
  2. Write code to generate such an expression tree at runtime
  3. Extend QueryExpression class hierarchy to feed the required parameters
  4. Come up with universal query string syntax and update parsers
  5. Add type checks to the parsers to ensure the column type is compatible
  6. Write lots of automated tests
  7. Now that you’re into internals this deep, it would be good to consider what other gis features are worth adding

The latter (and easier) approach would be to implement IResourceDefinition.OnRegisterQueryableHandlersForQueryStringParameters. Using that you can bind a custom query string parameter to an IQueryable<T> to extend the EF Core query with custom sort. Example: /stores?sort-by-location=27.0,-92.0

Read more comments on GitHub >

github_iconTop Results From Across the Web

Custom sorting in pandas dataframe - python
You can use this to create custom sorting functions. This works on the dataframe used in Andy Hayden's answer:
Read more >
How to do a Custom Sort on Pandas DataFrame | by B. Chen
Pandas DataFrame has a built-in method sort_values() to sort values by the given variable(s). The method itself is fairly straightforward to use ...
Read more >
Sort data using a custom list
In a column of a worksheet, type the values to sort by. · Select all of the cells in that list, and then...
Read more >
Excel SORTBY function - custom sort with formula
The SORTBY function in Excel is designed to sort one range or array based on the values in another range or array. Sorting...
Read more >
How To Perform Custom Sort In Google Sheets - YouTube
Unlike Excel, Google Sheets doesn't offer the custom sort feature. However, there still a few workarounds to perform a custom sort in Google ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found