Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I call a local function inside a LINQ query?

I tried:

 var doctorPractise = from d in db.DoctorsPrivateClinics
                                     where  GetDistance(db, d)<1.0
                                     select d;

But this doesn't work, GetDistance is a local function, I didn't get an exeption but it didn't seem to work, any ideas? Thank you

The GetDistance is:

 private double GetDistance(DBClassesDataContext db, DoctorsPrivateClinic docPra)
    {
        double distance=-1;
        double longitude;
        double latitude;

        var city = from c in db.Cities
                   where c.Code == Convert.ToInt32(Request.QueryString["Area"])
                   select c;


        foreach (var cit in city)//it will be just one row that will be found but I don't know how to express it without foreach
        {
            Calculations.GetLongitudeLatitudeGoogle getLongLat = new Calculations.GetLongitudeLatitudeGoogle();
            getLongLat.GetLongitudeLatitude("", cit.Name, "", out longitude, out latitude);
            distance = CalcualateDistance(Convert.ToDouble(docPra.PrivateClinic.Latitude), Convert.ToDouble(docPra.PrivateClinic.Longtitude), latitude, longitude);
        }
        return distance;
    }

What I want to achieve is to calculate the distance between a specific point on the map (cit.Name) and the location of a private clinic. The longitude and latitude of the specific point on the map is been retrieved from Googlemaps. In my query I specify that this distance must be less than 1 Km

like image 938
orestispan Avatar asked Oct 28 '12 01:10

orestispan


2 Answers

The trouble is that your query is being translated into SQL by the LINQ provider (EF/Linq2Sql?). It's not capable of translation of arbitrary .net methods. The only way round this is to enumerate the entire collection locally, dispensing with the database goodness of indexes etc, and probably bottlenecking on network IO from the DB.

If this isn't a problem:

var doctorPractise = from d in db.DoctorsPrivateClinics.AsEnumerable()
                                 where  GetDistance(db, d)<1.0
                                 select d;

otherwise, consider restating your query in terms that can be translated to SQL. Seeing your GetDistance method would help us to help you here.

like image 182
spender Avatar answered Sep 27 '22 17:09

spender


As it was proposed above adding db.DoctorsPrivateClinics.AsEnumerable() was what solved my problem. After improving a little bit my function here is the solution:

                double longitude=0;
                double latitude=0;

                var city = from c in db.Cities
                           where c.Code == Convert.ToInt32(Request.QueryString["Area"])
                           select c;
                city = city.Take(1);//take the first value, that sould be the only value in this case
                if (city.Count() == 0)
                {
                    //hanlde error
                }
                else
                {
                    City cit  = city.First();
                    Calculations.GetLongitudeLatitudeGoogle getLongLat = new Calculations.GetLongitudeLatitudeGoogle();
                    getLongLat.GetLongitudeLatitude("", cit.Name, "", out longitude, out latitude);
                }


                var doctorPractise = from d in db.DoctorsPrivateClinics.AsEnumerable()//or .ToList()
                                     where CalcualateDistance(Convert.ToDouble(d.PrivateClinic.Latitude), Convert.ToDouble(d.PrivateClinic.Longtitude), latitude, longitude)<5.0f

                                     select d;

where the function CalcualateDistance is:

 {
        /*
            The Haversine formula according to Dr. Math.
            http://mathforum.org/library/drmath/view/51879.html

            dlon = lon2 - lon1
            dlat = lat2 - lat1
            a = (sin(dlat/2))^2 + cos(lat1) * cos(lat2) * (sin(dlon/2))^2
            c = 2 * atan2(sqrt(a), sqrt(1-a)) 
            d = R * c

            Where
                * dlon is the change in longitude
                * dlat is the change in latitude
                * c is the great circle distance in Radians.
                * R is the radius of a spherical Earth.
                * The locations of the two points in 
                    spherical coordinates (longitude and 
                    latitude) are lon1,lat1 and lon2, lat2.
        */
        double dDistance = Double.MinValue;
        double dLat1InRad = Lat1 * (Math.PI / 180.0);
        double dLong1InRad = Long1 * (Math.PI / 180.0);
        double dLat2InRad = Lat2 * (Math.PI / 180.0);
        double dLong2InRad = Long2 * (Math.PI / 180.0);

        double dLongitude = dLong2InRad - dLong1InRad;
        double dLatitude = dLat2InRad - dLat1InRad;

        // Intermediate result a.
        double a = Math.Pow(Math.Sin(dLatitude / 2.0), 2.0) +
                   Math.Cos(dLat1InRad) * Math.Cos(dLat2InRad) *
                   Math.Pow(Math.Sin(dLongitude / 2.0), 2.0);

        // Intermediate result c (great circle distance in Radians).
        double c = 2.0 * Math.Asin(Math.Sqrt(a));

        // Distance.
        // const Double kEarthRadiusMiles = 3956.0;
        const Double kEarthRadiusKms = 6376.5;
        dDistance = kEarthRadiusKms * c;

        return dDistance;
    }

Even though this worked form me it not an efficient way to use LINQ. So I searched for a better solution that is to re write my function in T-SQL so the improved solution is:

 double longitude=0;
                double latitude=0;

                var city = from c in db.Cities
                           where c.Code == Convert.ToInt32(Request.QueryString["Area"])
                           select c;
                city = city.Take(1);//take the first value, that should be the only value in this case
                if (city.Count() == 0)
                {
                    //hanlde error
                }
                else
                {
                    City cit  = city.First();
                    Calculations.GetLongitudeLatitudeGoogle getLongLat = new Calculations.GetLongitudeLatitudeGoogle();
                    getLongLat.GetLongitudeLatitude("", cit.Name, "", out longitude, out latitude);
                }


                var doctorPractise = from d in db.DoctorsPrivateClinics
                                     where db.CalculateDistance(Convert.ToDouble(d.PrivateClinic.Latitude), Convert.ToDouble(d.PrivateClinic.Longtitude), latitude, longitude) < 5.0f                            
                                     select d;

Where the function written in T-SQL is:

ALTER FUNCTION PublicSiteDBUser.CalculateDistance
(

@latitudeArea float(53),
@longitudeArea float(53),
@latitudePractise float(53),
@longitudePractise float(53)
)

RETURNS float(53)
AS
BEGIN

DECLARE @dDistance as float(53)=0
DECLARE @dLat1InRad as float(53)=0
DECLARE @dLong1InRad as float(53)=0
DECLARE @dLat2InRad as float(53)=0
DECLARE @dLong2InRad as float(53)=0

DECLARE @dLongitude as float(53)=0
DECLARE @dLatitude as float(53)=0

DECLARE @a as float(53)=0
DECLARE @c as float(53)=0

DECLARE @kEarthRadiusKms as float(53)=6376.5  

SET @dLat1InRad = @latitudeArea * PI() / 180.0
SET @dLong1InRad= @longitudeArea * PI()/180.0
SET @dLat2InRad= @latitudePractise * PI()/180.0
SET @dLong2InRad= @longitudePractise * PI()/180.0

SET @dLongitude = @dLong2InRad - @dLong1InRad
SET @dLatitude = @dLat2InRad - @dLat1InRad

SET @a = POWER(SIN(@dLatitude/2.0), 2.0)+COS(@dLat1InRad)*COS(@dLat2InRad) * POWER (SIN(@dLongitude/2.0),2.0)
SET @c = 2.0*ASIN(SQRT(@a))      
SET @dDistance = @kEarthRadiusKms * @c 

RETURN @dDistance
END 
like image 28
orestispan Avatar answered Sep 27 '22 17:09

orestispan