0

I am trying to grab all of the products in my database if they are within a certain distance of the user. For each row in my database, I want to calculate the distance between the user and the product, and return the product if and only if that value is less than the maximum distance allowed from the user.

 const double PIx = Math.PI;
        const double earth = 6378000.16;
        const double distanceFromUser = 50; // Change this later.

        double dLat = Convert.ToDouble(lat + (distanceFromUser / earth) * (180 / PIx));
        double dLng = Convert.ToDouble(lng + (distanceFromUser / earth) * (180 / PIx) / Math.Cos(Convert.ToDouble(lat) * PIx / 180));
        var maximumDistance = _calculator.DistanceBetweenPlaces(lat, lng, dLat, dLng);
        var paginatedProducts = products.Where(x => _calculator.DistanceBetweenPlaces(Convert.ToDouble(lng), Convert.ToDouble(lat), x.Longitude, x.Latitude) < maximumDistance )

The idea is that I:

  1. Have the user's location (lat, lng)

  2. Have the location of the product in a row in my table (x.Longitude, x.Latitude)

  3. I use distanceBetweenPlaces to determine if the distance between the user's location and the product's location is less than the maximumDistance allowed.

Right now, the LINQ query says

could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().

How else can I write this?

Edit:

Here's the error if I try to turn it into a list:

System.InvalidOperationException: The LINQ expression 'DbSet<Product>
.Where(p => ___calculator_0.DistanceBetweenPlaces(
    lon1: __ToDouble_1, 
    lat1: __ToDouble_2, 
    lon2: p.Longitude, 
    lat2: p.Latitude) < __calc_3)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

edit 2:

Here's the code for DistanceBetweenPlaces and the utility function it uses:

/// <summary>
    /// cos(d) = sin(φА)·sin(φB) + cos(φА)·cos(φB)·cos(λА − λB), where φА, φB are latitudes and λА, λB are longitudes  Distance = d * R
    /// </summary>
    /// <param name="lon1"></param>
    /// <param name="lat1"></param>
    /// <param name="lon2"></param>
    /// <param name="lat2"></param>
    /// <returns></returns>
    public double DistanceBetweenPlaces(double lon1, double lat1, double lon2, double lat2)
    {
        double R = 6371; // km

        double sLat1 = Math.Sin(ConvertToRadians(lat1));
        double sLat2 = Math.Sin(ConvertToRadians(lat2));
        double cLat1 = Math.Cos(ConvertToRadians(lat1));
        double cLat2 = Math.Cos(ConvertToRadians(lat2));
        double cLon = Math.Cos(ConvertToRadians(lon1) - ConvertToRadians(lon2));

        double cosD = sLat1 * sLat2 + cLat1 * cLat2 * cLon;

        double d = Math.Acos(cosD);

        double dist = R * d;

        return dist;
    }


    const double PIx = Math.PI;
    /// <summary>
    /// Convert degrees to Radians
    /// </summary>
    /// <param name="x">Degrees</param>
    /// <returns>The equivalent in radians</returns>
    public double ConvertToRadians(double x)
    {
        return x * PIx / 180;
    }

I ended up taking another approach for anyone this might help later -

const double PIx = Math.PI;
        const double earth = 6378000.16;
        const double distanceFromUser = 80467.2;
        const double distanceFromUserHalved = distanceFromUser / 2; // Change this later.

        // This new approach adds half the distance of the radius to find a latitude upper and lower bound, and a longitude upper and lower bound.
        var TopLatBound = Convert.ToDouble(lat + (distanceFromUserHalved / earth) * (180 / PIx));
        var BottomLatBound = Convert.ToDouble(lat - (distanceFromUserHalved / earth) * (180 / PIx));
        var topLngBound = Convert.ToDouble(lng + (distanceFromUserHalved / earth) * (180 / PIx) / Math.Cos(Convert.ToDouble(lat) * PIx / 180));
        var bottomLngBound = Convert.ToDouble(lng - (distanceFromUserHalved / earth) * (180 / PIx) / Math.Cos(Convert.ToDouble(lat) * PIx / 180));

        var paginatedProducts = await PaginatedList<Product>.CreateAsync(products.Where(x => (x.Latitude < TopLatBound && x.Latitude > BottomLatBound) && (x.Longitude < topLngBound && x.Longitude> bottomLngBound) && x.IsArchived == false && x.Decay > 0)
            .OrderByDescending(x => x.DateTime), offset ?? 1, pageSize).ConfigureAwait(false);
kalittles
  • 325
  • 1
  • 4
  • 17
  • Append ToList and check if link querys theows exxception `var paginatedProducts = products.Where(x => _calculation.DistanceBetweenPlaces(Convert.ToDouble(lng), Convert.ToDouble(lat), x.Longitude, x.Latitude) < maximumDistance ).ToList()` – Clint Jan 27 '20 at 02:36
  • Hi! I've tried - I added the error to the comment as an edit. – kalittles Jan 27 '20 at 02:45
  • 1
    gotcha, I see you're using a method inside the where clause, and that's not allowed. – Clint Jan 27 '20 at 02:57
  • you have to convert that to an expression and include it in where clause – Clint Jan 27 '20 at 02:58
  • Okay, thank you for the help! Would something like this https://hassantariqblog.wordpress.com/2016/02/18/entity-framework-use-expression-trees-to-build-dynamic-queries-or-filter-using-expressionfunc/ be the right direction to learning how to do that? – kalittles Jan 27 '20 at 03:01
  • yes good start, can you also paste the method body for `distanceBetweenPlaces` I can help out if/when I have some time – Clint Jan 27 '20 at 03:11
  • Okay, awesome! I would appreciate that a lot. I've added the method body as requested. – kalittles Jan 27 '20 at 03:17
  • My recommendation is to use LINQ to EF to retrieve products within a square around the user (which SQL can process) and then convert to client side and reduce to just those within the radius like [my answer here](https://stackoverflow.com/a/49140268/2557128). – NetMage Jan 27 '20 at 22:57
  • Thank you, NetMage! I wrote a similar solution yesterday, and this confirms it! – kalittles Jan 27 '20 at 23:41

0 Answers0