DEV Community

Cover image for Setting up Entity Framework  to work with geographical distances
Daniel Blackwell
Daniel Blackwell

Posted on

Setting up Entity Framework to work with geographical distances

This is a post about a simple solution to a simple problem:

Find distances between things in a database using .net core

Here's how I did it.

The requirements:

.Net Core 3.1 with Entity Framework

This is the base stack.

A Linux database

I couldn't use MSSQL as I'm restricted to Linux based docker images.

Server evaluated geographical distances

I need to find the distances between two points on the database side. Over-returning results and then filtering isn't ideal for anything.

The solution

Database - PostGIS a.k.a Postgres + GIS

It took a few goes (MySQL, Sqlite and then Postgres), but Postgres was the winner in getting things to work actually work and easily work with distance.
npgsql.org has very good documentation and the steps are easy to follow.

Using docker compose

version: '3.1'
services:
  db:
    image: postgis/postgis
    restart: always
    environment:
      POSTGRES_PASSWORD: mysecretpassword_shhhh
    ports:
     - 5432:5432
  adminer:
    image: adminer
    restart: always
    ports:
      - 8080:8080
Enter fullscreen mode Exit fullscreen mode

The connection string for this setup is:
"Server=localhost;Database=mydatabase;Uid=postgres;Pwd=mysecretpassword_shhhh;"

Entity Framework and NetTopologySuite

Install Packages

  • NetTopologySuite <- "NTS" Provides Points and other spatial types.
  • Microsoft.EntityFrameworkCore
  • Microsoft.EntityFrameworkCore.Proxies <- Lazy loading
  • Npgsql.EntityFrameworkCore.PostgreSQL
  • Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite <- "NTS " Adapter

Use Points for your locations

If you are using Lat/Lon co-ordinates, then the points should be stored in Postgres as geoGRAPHY and not geoMETRY (Capitalisation is for emphasis only).
There is supposedly an extra CPU cost to calculating queries against Geographic data, but it's negligible.

using Point = NetTopologySuite.Geometries.Point;
public class MyEntity
{ 
   [Key] public int Id { get; set; }

   // Set the column type either here or in the context. 
   // NOTE: The type is geoGRAPHY not geoMETRY
   [Column(TypeName="geography")] 
   [Required] public Point Location { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

Setup your EF database context

The postgis extension is required for geometry and geographical types and is installed per database. The call to HasPostgresExtension ensures that this is installed.

public class MyPostgresContext: MyDatabaseContext
{
public PostgresMyContext(DbContextOptions options) : base(options){}
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
builder.HasPostgresExtension("postgis");

// Equivalent to using [Column(TypeName="geography")] 
// NOTE: The type is geoGRAPHY not geoMETRY
builder.Entity<MyEntity>().Property(b => b.Location).HasColumnType("geography (point)");
}
Enter fullscreen mode Exit fullscreen mode

And in your Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
services
 .AddDbContext<MyDatabaseContext, MyPostgresContext>(builder =>
 {
     builder.UseNpgsql(CONNECTION_STRING, x => x.UseNetTopologySuite());
     builder.EnableDetailedErrors();
     builder.UseLazyLoadingProxies();     
 }) 
}
Enter fullscreen mode Exit fullscreen mode

Query all the things

With this all in place, you can use the methods that deal with distance, or many other things.

var distanceInMetres = 1000; // 1 km
var entities = context.MyEntities.Select(x => x.Location.Distance(new Point(1,1)) <= distanceInMetres) .ToArray();
Enter fullscreen mode Exit fullscreen mode

Hope this helps!

Top comments (0)