0

I would like to add a function in my project so that I can filter my data.

I would like that function to returns the distance in kms between 2 GPS positions.

So far I've done : added this in my edmx in the ConceptualModels>Schema :

<Function Name="DistanceBetweenTwoPositions" ReturnType="Edm.Double">
          <Parameter Name="latitude_1" Type="Edm.Double" />
          <Parameter Name="longitude_1" Type="Edm.Double" />
          <Parameter Name="latitude_2" Type="Edm.Double" />
          <Parameter Name="longitude_2" Type="Edm.Double" />
          <DefiningExpression>
            DistanceBetweenTwoPositions(latitude_1, longitude_1, latitude_2, longitude_2)
          </DefiningExpression>
        </Function>

Created a partial class with the same name to be able to define that function :

[DbFunctionAttribute("DataModel", "DistanceBetweenTwoPositions")]
    public static double DistanceBetweenTwoPositions(double latitude_1, double longitude_1, double latitude_2, double longitude_2)
    {
        var rlat1 = Math.PI * latitude_1 / 180;
        var rlat2 = Math.PI * latitude_2 / 180;
        var rlon1 = Math.PI * longitude_1 / 180;
        var rlon2 = Math.PI * longitude_2 / 180;

        var theta = longitude_1 - longitude_2;
        var rtheta = Math.PI * theta / 180;

        var dist = Math.Sin(rlat1) * Math.Sin(rlat2) + Math.Cos(rlat1) * Math.Cos(rlat2) * Math.Cos(rtheta);
        dist = Math.Acos(dist);
        dist = dist * 180 / Math.PI;
        dist = dist * 60 * 1.1515;

        dist = dist * 1.609344; // Conversion to kms
        return dist;
    }

And called it in my code :

double latitude = 0;
double longitude = 0;
var request = (from house in db.Houses
                                select 
                                new
                                {
                                    house,
                                    DistanceFromUser = BackboneDBEntitiesLocal.DistanceBetweenTwoPositions(latitude, longitude, house.Latitude.Value), house.Longitude.Value)) 
                                })
                                .Where(u=>u.DistanceFromUser <= range)
                                .OrderBy(u=>u.DistanceFromUser)
                                ;

But it doesn't work I get the following exception :

An error occurred while preparing definition of the function 'DataModel.DistanceBetweenTwoPositions'. See the inner exception for details.

InnerException :

System.Data.Entity.Core.EntitySqlException: 'DistanceBetweenTwoPositions' cannot be resolved into a valid type or function. Near simple identifier, line 2, column 13. at System.Data.Entity.Core.Common.EntitySql.SemanticAnalyzer.ConvertMethodExpr(MethodExpr methodExpr, Boolean includeInlineFunctions, SemanticResolver sr) at System.Data.Entity.Core.Common.EntitySql.SemanticAnalyzer.ConvertMethodExpr(Node expr, SemanticResolver sr) at System.Data.Entity.Core.Common.EntitySql.SemanticAnalyzer.Convert(Node astExpr, SemanticResolver sr) at System.Data.Entity.Core.Common.EntitySql.SemanticAnalyzer.ConvertValueExpressionAllowUntypedNulls(Node astExpr, SemanticResolver sr) at System.Data.Entity.Core.Common.EntitySql.SemanticAnalyzer.ConvertQueryStatementToDbExpression(Statement astStatement, SemanticResolver sr, List1& functionDefs) at System.Data.Entity.Core.Common.EntitySql.SemanticAnalyzer.AnalyzeQueryCommand(Node astExpr) at System.Data.Entity.Core.Common.EntitySql.CqlQuery.<AnalyzeQueryExpressionSemantics>b__8(SemanticAnalyzer analyzer, Node astExpr) at System.Data.Entity.Core.Common.EntitySql.CqlQuery.AnalyzeSemanticsCommon[TResult](Node astExpr, Perspective perspective, ParserOptions parserOptions, IEnumerable1 parameters, IEnumerable1 variables, Func3 analysisFunction) at System.Data.Entity.Core.Common.EntitySql.CqlQuery.AnalyzeQueryExpressionSemantics(Node astQueryCommand, Perspective perspective, ParserOptions parserOptions, IEnumerable1 parameters, IEnumerable1 variables) at System.Data.Entity.Core.Common.EntitySql.CqlQuery.<>c__DisplayClass4.b__3(Node astCommand, ParserOptions validatedParserOptions) at System.Data.Entity.Core.Common.EntitySql.CqlQuery.CompileCommon[TResult](String commandText, ParserOptions parserOptions, Func3 compilationFunction) at System.Data.Entity.Core.Common.EntitySql.CqlQuery.CompileQueryCommandLambda(String queryCommandText, Perspective perspective, ParserOptions parserOptions, IEnumerable1 parameters, IEnumerable1 variables)
at System.Data.Entity.Core.Mapping.ViewGeneration.Utils.ExternalCalls.CompileFunctionDefinition(String functionDefinition, IList
1 functionParameters, EdmItemCollection edmItemCollection) at System.Data.Entity.Core.Metadata.Edm.EdmItemCollection.GenerateFunctionDefinition(EdmFunction function) at System.Data.Entity.Core.Common.Utils.Memoizer2.<>c__DisplayClass2.<Evaluate>b__0() at System.Data.Entity.Core.Common.Utils.Memoizer2.Result.GetValue()
at System.Data.Entity.Core.Common.Utils.Memoizer`2.Evaluate(TArg arg) at System.Data.Entity.Core.Metadata.Edm.EdmItemCollection.GetGeneratedFunctionDefinition(EdmFunction function) at System.Data.Entity.Core.Metadata.Edm.MetadataWorkspace.GetGeneratedFunctionDefinition(EdmFunction function) at System.Data.Entity.Core.Query.PlanCompiler.ITreeGenerator.Visit(DbFunctionExpression e)

I've been following the answer of that article, thanks to him for the nice explanation : LINQ to Entities does not recognize the method 'Double Parse(System.String)' method, and this method cannot be translated into a store expression

user2088807
  • 1,378
  • 2
  • 25
  • 47

2 Answers2

1

My first observation is should this even be in the EF mapping? Conceptually does a Location know how to calculate a distance to another arbitrary point? I'd actually put all this in separate class called DistanceCalculator that takes 2 arbitrary points and gives you back a distance. This could then be called after you materialize your query results.

The issue in your implementation is that your function can't be converted back to SQL. EF doesn't know how to translate those Math.* functions to SQL. First replace all those Math.* calls with the corresponding calls in SqlFunctions. This class has

Provides common language runtime (CLR) methods that call functions in the database in LINQ to Entities queries.

That should generate usable SQL for EF.

Fran
  • 6,440
  • 1
  • 23
  • 35
0

If you are using EF6+, you can move the logic of DistanceBetweenTwoPositions as a SQL scalar valued function.

Update your EF model and import the Scalar valued function into your Model.

If you have it mapped that way, you would be able to use it in your query like you have it.

Hope that helps.