Search nearby locations using .Net core and EF

The latest version of Entity Framework Core has changed how GeoSpatial searches work. I ran into this problem and I ended up following out of date guides that didn’t work and the ones that did use EF core were incomplete. So I am attempting to put together all that I have learned.

Project Set Up

We need to create our Asp.Net Core Web Application

We will need to download the following NuGets to use entity framework core. We do this by right clicking on the selecting “Manage NuGet Packages”

Select “browse from the top nav bar and search and install
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite

We need to create a data model to represent the data in the database. We will create a class called “Pub.cs” in a new folder “Data->DataModels”

The class Pub.cs contains the properties that we want to persist to the database.

We have included the [Key] annotation to show that we want Id to be the primary key when the database table is created. We need to include the using System.ComponentModel.DataAnnotations; at the top of the class for this too. The [Key] annotation is not required as EF will work out that the Id is the primary Key but it is good practice to add it anyway.
There is a property “Location” that is of Type NetTopologySuite.Geometries.Point we need to add the using NetTopologySuite.Geometries to allow this to work.

Database Context

The next step is to create the database context. We will create this at the root level of the data folder.

The PubContext.cs class will inherit the DBcontext class and set up the Pubs DbSet. We will also add the OnModelCreating override method that will create the Pubs database table and we want to give it the name “Pubs”.

To use the PubContext we have to register it. This is done in the Startup.cs class in a simular way to how other services are registered with dependency injection.

!IMPORTANT! we have to add the opt=>opt.UseNetTopologySuite() when Registering the Database connection. If we don’t the database will not be able to map the NetTopologySuite.Geometries.Point type to type Geography.

In the startup.cs there is a ConfigureServices method. In there we need to add Services.AddDbContext to register the new database context. You will note that it is using a connections string called “Default”. We will need to add this to the applicationSetting.json file. If you want to change what database to point to just update this string. We will need to add a using Microsoft.EntityFrameworkCore; so that the UseSqlServer is available and there will also need to be a using using Pubs.Data; so that the PubContext is available.

Initialize DB

We need to create a class called DbInitializer.cs to create and populate the database. We will use the EnsureCreated method to automatically create the database. We will also seed the database with some test data.

We will create this in the Data folder.

We need to create static method Initialize and add the line context.Database.EnsureCreated(); to create the database.

We check the Pubs table to see if it is empty and if it is we will create some new records.

if (!context.Pubs.Any())
{
var pubs = new Pub[]
{
new Pub { Name =….},
new Pub { Name =….},

}
}
context.Pubs.AddRange(pubs);
context.SaveChanges();

When we are creating the “Location” data we can not just add a lat and long we need to construct a using NetTopologySuite.Geometries.Point. This is done by creating a geometryFactory
var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);

SRID 4326 means that latitude and longitude geographic coordinates are being used and when creating the location the geometryFactory knows to create the correct type.
new Pub { Name =...Location= geometryFactory.CreatePoint(new Coordinate(-5.9348902, 54.5881783)) },
This creates the Point Type from the Latitude and longtitude supplied.

!IMPORTANT — The Coordinates have to be specified Longtiude and Latitude not the other way round. That has caused me a lot of pain in the past.

We then need to Run the Initialize method when the application is started. To do this we add the following code to the Program.cs Main Method.

We have to add the following usings to allow access.
using Microsoft.Extensions.DependencyInjection;
using Pubs.Data;

If you run the application now it will create the database and seed to with the locations. :)

The Front End

We now want to consume all that lovely data we have created in the database. To to that we need to create a model, view and controller.

  • Right-click the Controllers folder in Solution Explorer and select Add > New Scaffolded Item.
  • In the Add Scaffold dialog box:
  • Select MVC controller with views, using Entity Framework.
  • Click Add. The Add MVC Controller with views, using Entity Framework dialog box appears.

Enter the details like so

This will create the controller with default methods such as Get, Get/1 etc.
It also injects the database context for us.

We can see that there is a constructor with the context injected for us to use.

If we open Views -> Pubs-> Index.cshtml we can remove the “Location” since it is a complex type and will not render well.

Now we have the basic list of Pubs being returned we need to do the important work and find a nearby pub. We will create a new method in the PubsController.cs called “FindNearBy” that will take 3 parameters lat, lon (long is a key word) and distance. The latitude and longtitude parameters will be the latitude and longtitude of our current location. You can add javascript to find this for you automatically but for this demonstration we will pass them in manually. The third parmeter is the distance (in meters) we are prepared to walk to get to the pub.

Here we will create our current location using the geometryFactory. Please note that it is lon and lat not the other way around. We then get the distance from our current location to the Pubs and filter out any that are too far away.

_context.Pubs.Where(x => x.Location.Distance(location)< distance).ToListAsync();

We then need to return the results to the page. We can reuse the Index page for this. Run the application and enter the url. Update the lat and lon to your current location and change the distance and you should get a list of public houses within the search distance in meters.

/pubs/FindNearBy?lat=54.5900622&lon=-5.9389685&distance=500

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store