Spatial data, also known as geospatial data, is information about a physical object that can be represented by numerical values in a geographic coordinate system. Many applications nowadays make heavy use of spatial data for analysis and to make important business decisions. Consider the examples of online taxi apps, like Uber or Ola. Their whole business model is running based on the location-based data.
A spatial database is a database that is optimized for storing and querying data that represents objects defined in a geometric space. Most spatial databases allow the representation of simple geometric objects, such as points, lines, and polygons. Microsoft introduced two spatial data types with SQL Server 2008: geometry and geography. Similarly, databases like PostgreSQL, MySQL also provide spatial data types.
With the release of Entity Framework Core 2.2, Microsoft brought support for spatial types and queries in EF Core. With the introduction of spatial types, we could do queries like whether a location falls in a certain area, or queries based on the distance between two points, etc.
EF Core supports mapping to spatial data types using the NetTopologySuite spatial library.
We will create a sample ASP.NET Core MVC application to learn how to use spatial types in EF Core. Note that we need EF Core version 2.2 and above to support spatial types. This MVC application will collect a latitude and longitude as user input and list a set of tourist attraction places in the ascending order of distance from the input location. I will be using Visual Studio Code and DotNet CLI for development.
- Create a directory SpatialSample. Open Visual Studio Code and run the command dotnet new mvc --name SpatialSample in the terminal. This shall create an ASP.NET Core MVC application for us.
- In order to use spatial data with EF Core, we need to install the appropriate supporting NetTopologySuite package from Nuget based on the SQL provider we use. Since I am using SQL Server as my database I need to add the Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite package. Run the command dotnet add package Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite --version 2.2.4 in the terminal to add the package.
- using System.ComponentModel.DataAnnotations.Schema;
- using GeoAPI.Geometries;
- namespace SpatialSample.Entities
- {
- public class TouristAttraction
- {
- public int Id { get; set; }
- public string Name { get; set; }
- [Column(TypeName = "geometry")]
- public IPoint Location { get; set; }
- }
- }
Observe the Location property in the entity. It is of type IPoint. It is a NetTopologySuite type which is used to represent a location as a point. Note that I have configured the Location properties column type as geometry. This is because, in SQL Server, spatial properties are mapped to geography type by default.
Now, let's add our DbContext class. Create a class called SpatialDbContext.cs.
- using GeoAPI.Geometries;
- using Microsoft.EntityFrameworkCore;
- using NetTopologySuite;
- using SpatialSample.Entities;
- namespace SpatialSample
- {
- public class SpatialDbContext : DbContext
- {
- public DbSet<TouristAttraction> TouristAttractions { get; set; }
- public SpatialDbContext(DbContextOptions options) : base(options) { }
- }
- }
- services.AddDbContext<SpatialDbContext>(opts =>
- {
- opts.UseSqlServer(
- "<Connection string goes here>",
- x => x.UseNetTopologySuite()
- );
- });
We need to add the migration to create our database. Run the command dotnet ef migrations add AddTouristAttractionTable. After the migrations are created, run the command dotnet ef database update to apply the migrations on the database.
We need to add some seed data for the application. We will add some tourist attraction destinations in the database. We can override the OnModelCreating method of DbContext class to add seed data.
- protected override void OnModelCreating(ModelBuilder modelBuilder)
- {
- var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 4326);
- modelBuilder.Entity<TouristAttraction>()
- .HasData(
- new TouristAttraction
- {
- Id = 1,
- Name = "Taj Mahal",
- Location = geometryFactory.CreatePoint(new Coordinate(27.175015, 78.042155))
- },
- new TouristAttraction
- {
- Id = 2,
- Name = "The Golden Temple of Amritsar",
- Location = geometryFactory.CreatePoint(new Coordinate(31.619980, 74.876485))
- },
- new TouristAttraction
- {
- Id = 3,
- Name = "The Red Fort, New Delhi",
- Location = geometryFactory.CreatePoint(new Coordinate(28.656159, 77.241020))
- },
- new TouristAttraction
- {
- Id = 4,
- Name = "The Gateway of India, Mumbai",
- Location = geometryFactory.CreatePoint(new Coordinate(18.921984, 72.834654))
- },
- new TouristAttraction
- {
- Id = 5,
- Name = "Mysore Palace",
- Location = geometryFactory.CreatePoint(new Coordinate(12.305025, 76.655753))
- },
- new TouristAttraction
- {
- Id = 6,
- Name = "Qutb Minar",
- Location = geometryFactory.CreatePoint(new Coordinate(28.524475, 77.185521))
- }
- );
- }
After adding the seed data, we need to create another migration as data seeding has become part of EF migrations in .NET Core. Run the command dotnet ef migrations add AddSeedData. After the migrations are created run the command dotnet ef database update to apply the migrations on the database.
We can verify by checking the TouristAttractions table in the database.

We need to create the web part of our application next. We shall create View Model classes first. We create a View Model to bind the user input and a view model to bind the list of location information based on the user search.
- namespace SpatialSample.Models
- {
- public class TouristAttractionViewModel
- {
- public double Latitude { get; set; }
- public double Longitude { get; set; }
- public string Name { get; set; }
- public double Distance { get; set; }
- }
- public class SearchInputModel
- {
- public double Latitude { get; set; }
- public double Longitude { get; set; }
- }
- public class IndexPageViewModel
- {
- public SearchInputModel SearchInput { get; set; }
- public List<TouristAttractionViewModel> TouristAttractions { get; set; }
- }
- }
Now, let's create the view for user interaction. In the Index.cshtml file in the Views\Home folder, replace the existing code with the following code.
- @{
- ViewData["Title"] = "Spatial Sample";
- }
- @model IndexPageViewModel
- <form
- asp-controller="Home"
- asp-action="Search"
- method="post"
- class="form-horizontal"
- role="form">
- <div class="form-group">
- <label for="Title">Latitude</label>
- <input
- class="form-control"
- placeholder="Enter Latitude"
- asp-for="SearchInput.Latitude">
- </div>
- <div class="form-group">
- <label for="Title">Longitude</label>
- <input
- class="form-control"
- placeholder="Enter Longitude"
- asp-for="SearchInput.Longitude">
- </div>
- <button type="submit" class="btn btn-primary">Submit</button>
- </form>
- @{
- if(Model?.TouristAttractions?.Any() == true)
- {
- <br>
- <br>
- <table class="table table-bordered table-responsive table-hover">
- <tr>
- <th>Name</th>
- <th>Distance</th>
- <th>Coordinates</th>
- </tr>
- @foreach (var t in Model.TouristAttractions)
- {
- <tr>
- <td>@t.Name</td>
- <td>@t.Distance</td>
- <td>@t.Latitude, @t.Longitude</td>
- </tr>
- }
- </table>
- }
- else
- {
- <br>
- <br>
- <div class="alert alert-warning" role="alert">
- <strong>Please enter Latitude and Longitude of the location</strong>
- </div>
- }
- }
The View contains a form that accepts a latitude and longitude value from the user and submits the input to a controller action. Let's create the controller next. Replace the code in the HomeController.cs with the following.
- using System;
- using System.Linq;
- using Microsoft.AspNetCore.Mvc;
- using NetTopologySuite.Geometries;
- using SpatialSample.Models;
- namespace SpatialSample.Controllers
- {
- public class HomeController : Controller
- {
- private readonly SpatialDbContext _spatialDbContext;
- public HomeController(SpatialDbContext spatialDbContext)
- {
- this._spatialDbContext = spatialDbContext;
- }
- public IActionResult Index()
- {
- return View();
- }
- [HttpPost]
- public IActionResult Search([FromForm] IndexPageViewModel indexModel)
- {
- var indexViewModel = new IndexPageViewModel
- {
- SearchInput = indexModel.SearchInput
- };
- // Convert the input latitude and longitude to a Point
- var location = new Point(indexModel.SearchInput.Latitude, indexModel.SearchInput.Longitude) { SRID = 4326 };
- // Fetch the tourist attractions and their
- // distances from the input location
- // using spatial queries.
- var touristAttractions = _spatialDbContext
- .TouristAttractions
- .Select(t => new { Place = t, Distance = t.Location.Distance(location) })
- .ToList();
- // Ordering the result in the ascending order of distance
- indexViewModel.TouristAttractions = touristAttractions
- .OrderBy(x => x.Distance)
- .Select(t => new TouristAttractionViewModel
- {
- Distance = Math.Round(t.Distance, 6),
- Latitude = t.Place.Location.X,
- Longitude = t.Place.Location.Y,
- Name = t.Place.Name
- }).ToList();
- return View("Index", indexViewModel);
- }
- }
- }
The latitude and longitude submitted by the user are passed to the Search action. The latitude and longitude are first converted to a point object and are passed to the spatial query to be run by EF Core. The query will compare every location in the database and return the distance of each with the input location. Then we order the result set in ascending order of the distances and returns the result to the view.
NB
The distance calculated y NetTopologySuite is not the actual distance in kilometers between two locations. The distance is calculated based on a Cartesian plane between two coordinates. If we need to get the actual distance between two locations in kilometers we need to perform some advanced spatial queries and transforms.
The distance calculated y NetTopologySuite is not the actual distance in kilometers between two locations. The distance is calculated based on a Cartesian plane between two coordinates. If we need to get the actual distance between two locations in kilometers we need to perform some advanced spatial queries and transforms.
Now run the application using the command dotnet run. The application will be loaded in the browser with a form to accept a latitude and longitude value. Enter any coordinates and click the Submit button. Let's try New Delhi (28.613939, 77.209023). We shall get a screen like below.

Summary
In this article, we have looked at the spatial types and queries support in Entity Framework core. For more information on the spatial support in EF Core, check out this link.

Mohammad KomaeiPosted Oct 20, 2020, 4:38 PM
How can we save circular or polygonal area of a restaurant delivery border in sql server with .net core 3.1 web api? could you give us a sample for that?
Sarathlal SaseendranPosted Jun 3, 2019, 10:58 AM
Very interesting one Geo.