Using Spatial Data in ASP.Net MVC with SQL Server 2012, Entity Framework 5 and Infragistics jQuery Map

[Infragistics] Mihail Mateev / Wednesday, July 11, 2012

One of the highly-anticipated features  in the Entity Framework 5 is a Spatial support.

Many developers have been asking since the release of SQL 2008 for support of Spatial data types in the Entity Framework. It was a dream for the Microsoft ORM users to create .NET business applications quickly, using spatial data.  In May of this year the release candidate for Entity Framework 5 (EF5) was announced.This version has increased performance when compared to earlier EF version and also has support for spatial types. The Spatial functionality in EF5 requires .NET 4.5.

The Spatial functionality in EF5 requires .NET 4.5. This means you will need Visual Studios 2012 installed. You can download the release candidate for VS 2012 here: http://www.microsoft.com/visualstudio/en-us

Spatial Data in the Entity Framework

Prior to Entity Framework 5.0 on .NET 4.5 consuming of the data above required using stored procedures or raw SQL commands to access the spatial data. In Entity Framework 5 however, Microsoft introduced the new DbGeometry andDbGeography types. These immutable location types provide a bunch of functionality for manipulating spatial points using geometry functions which in turn can be used to do common spatial queries like I described in the SQL syntax above.

The DbGeography/DbGeometry types are immutable, meaning that you can't write to them once they've been created. They are a bit odd in that you need to use factory methods in order to instantiate them - they have no constructor() and you can't assign to properties like Latitude and Longitude.

It is important to mention that these types are defined in System.Data.Entity assembly in System.Data.Spatial namespace. By now you have probably used types SqlGeometry and SqlGeography types, defined in Microsoft.SqlServer.Types namespace

Creating a Model with Spatial Data

Let's start by creating a simple Entity Framework model that includes entities from sample databases Northwind and SpatialDemo. The entity named world contains a geom property of type DbGeometry. Sample is using SQL Server 2012, but you could run it with SQL Server 2008.

 1: [EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
 2: [DataMemberAttribute()]
 3: public global::System.Data.Spatial.DbGeometry geom
 4: {
 5:     get
 6:     {
 7:         return _geom;
 8:     }
 9:     set
 10:     {
 11:         OngeomChanging(value);
 12:         ReportPropertyChanging("geom");
 13:         _geom = StructuralObject.SetValidValue(value, true, "geom");
 14:         ReportPropertyChanged("geom");
 15:         OngeomChanged();
 16:     }
 17: }
 18: private global::System.Data.Spatial.DbGeometry _geom;
 19: partial void OngeomChanging(global::System.Data.Spatial.DbGeometry value);
 20: partial void OngeomChanged();

 

Northwind Entities

Sample SpatialDemo database, including spatial data. Table world a filed from Geometry type, named “geom”. This field  contains the contours of countries as polygons

Entity world, generated from SpatialDemo database contains a field “geom” from Geometry type.

ASP.Net MVC 4 Application with Entity Framework 5 RC and Spatial data

Now it is  a pretty easy to use spatial data in ASP.Net MVC applications.

  • Controller

Controller returns a view that contains a dashboard with Infragistics jQuery  controls.

 1: #region DashboardJs
 2: public ActionResult DashboardJs()
 3: {
 4:     ViewBag.Message = "Spatial Data Dashboard";
 5:  
 6:     return View();
 7: }
 8: #endregion //DashboardJs

 

  • Spatial Data Maintenance

When you have a data from DbGeometry / DbGeography type you can’t serialize it. There are two options:

  • To convert spatial data type to WKT (Well Known Text) and send it to the client (view) as part of JSON or XML
  • To use your own classes that could be serialized

This sample is demonstrates the second approach

CountryByName method serializes results to JSON to be possible to use it in the view

 1: #region CountryByName
 2: [OutputCache(VaryByParam = "countryName", Duration = 120)]
 3: public JsonResult CountryByName(string countryName)
 4: {
 5:     switch (countryName)
 6:     {
 7:         case "UK":
 8:             countryName = "United Kingdom";
 9:             break;
 10:         case "USA":
 11:             countryName = "United States";
 12:             break;
 13:     }
 14:     var results = spDemo.worlds.Where(x => x.CNTRY_NAME == countryName);
 15:  
 16:  
 17:     List ret = new List();
 18:     foreach (world country in results)
 19:     {
 20:         CountryInfo info = new CountryInfo
 21:         {
 22:             Id = country.ID,
 23:             Code = country.CODE,
 24:             CountryName = country.CNTRY_NAME,
 25:             Population = country.POP_CNTRY,
 26:             Extend = GetGeometryBoundary(country)
 27:         };
 28:         ret.Add(info);
 29:     }
 30:  
 31:     var retVal = Json(ret, JsonRequestBehavior.AllowGet);
 32:     return retVal;
 33: }
 34: #endregion //CountryByName

 

GetGeometryBoundary is a helper method used to get a list of points, representing an envelope of a DbGeometry instance. Don't forget that DbGeometry/DbGeography point indexes start from 1 !.

 1: #region GetGeometryBoundary
 2: public static SpatialRect GetGeometryBoundary(world country)
 3: {
 4:     List multiPoints = new List();
 5:     var numPoints = country.geom.Envelope.ElementAt(1).PointCount;
 6:     for (int i = 1; i <= numpoints="" i="" pre="">
 7:     {
 8:         SpatialPoint pnt = new SpatialPoint((double)(country.geom.Envelope.ElementAt(1).PointAt(i).XCoordinate), (double)(country.geom.Envelope.ElementAt(1).PointAt(i).YCoordinate));
 9:         multiPoints.Add(pnt);
 10:  
 11:     }
 12:     SpatialRect rect = multiPoints.GetBounds();
 13:     return rect;
 14: }
 15: #endregion //GetGeometryBoundary

 

ContryInfo is a helper class used to serialize data

 1: #region CountryInfo
 2:  
 3: public class CountryInfo
 4: {
 5:     public int Id { get; set; }
 6:     public string Code { get; set; }
 7:     public string CountryName { get; set; }
 8:     public long? Population { get; set; }
 9:     public SpatialRect Extend { get; set; }
 10:  
 11: }
 12: #endregion //CountryInfo

 

SpatialPoint is a helper class to keep a point data. You could use

 1: #region SpatialPoint
 2:  
 3: public class SpatialPoint
 4: {
 5:     public SpatialPoint(double x, double y)
 6:     {
 7:         this.X = x;
 8:         this.Y = y;
 9:     }
 10:  
 11:     public double X { get; set; }
 12:     public double Y { get; set; }
 13: }
 14:  
 15: #endregion //SpatialPoint

 

SpatialRect is a helper class to keep an extend of the country

 1: #region SpatialRect
 2:  
 3: public struct SpatialRect
 4: {
 5:     public SpatialRect(double pLeft, double pTop, double pWidth, double pHeight)
 6:     {
 7:         left = pLeft;
 8:         top = pTop;
 9:         width = pWidth;
 10:         height = pHeight;
 11:     }
 12:  
 13:     public double left; 
 14:     public double top; 
 15:     public double width; 
 16:     public double height; 
 17: }
 18:  
 19: #endregion //SpatialRect

 

GetBounds is an extension method used to get a boundary of the list of points.

 1: #region Extensions
 2: public static class Extensions
 3: {
 4:  
 5:     #region GetBounds
 6:     public static SpatialRect GetBounds(this IList points)
 7:     {
 8:         double xmin = Double.PositiveInfinity;
 9:         double ymin = Double.PositiveInfinity;
 10:         double xmax = Double.NegativeInfinity;
 11:         double ymax = Double.NegativeInfinity;
 12:  
 13:         SpatialPoint p;
 14:         for (var i = 0; i < points.Count; i++)
 15:         {
 16:             p = points[i];
 17:             xmin = Math.Min(xmin, p.X);
 18:             ymin = Math.Min(ymin, p.Y);
 19:  
 20:             xmax = Math.Max(xmax, p.X);
 21:             ymax = Math.Max(ymax, p.Y);
 22:         }
 23:  
 24:         if (Double.IsInfinity(xmin) || Double.IsInfinity(ymin) || Double.IsInfinity(ymin) || Double.IsInfinity(ymax))
 25:         {
 26:             return new SpatialRect(0.0, 0.0, 0.0, 0.0);
 27:         }
 28:  
 29:         return new SpatialRect(xmin, ymin, xmax - xmin, ymax - ymin);
 30:     }
 31:     #endregion //GetBounds
 32: } 
 33: #endregion //Extensions

 

View

The view presents a dashboard from Infragistics jQuery Grid, Chart and Map.

The most important part in the sample is how to query the controller’s method that returns spatial data (the country extend in this case).

 1: var countryUrl = "/Home/CountryByName?countryName=" + args.row.element[0].cells[1].textContent
 2:  
 3: ...
 4:  
 5: $.getJSON(countryUrl,
 6:   function (json, text) {
 7:       $.each(json, function (index, value) {
 8:           var country = value;
 9:           var extend = country["Extend"];
 10:  
 11:           var zoom = $("#map").igMap("getZoomFromGeographic", extend);
 12:           $("#map").igMap("option", "windowRect", zoom);
 13:  
 14:       });
 15: });

 

Infragistics jQuery Map instance definition.

 1: $("#map").igMap({
 2:      width: "500px",
 3:      height: "500px",
 4:      panModifier: "control",
 5:      horizontalZoomable: true,
 6:      verticalZoomable: true,
 7:      windowResponse: "immediate",
 8:      overviewPlusDetailPaneVisibility: "visible",
 9:      seriesMouseLeftButtonUp: function (ui, args) {
 10:          var tets = args;
 11:      }
 12:  });

 

Infragistics jQuery Grid with zoom around the selected customer’s country.

 1: $('#grid').igGrid({
 2:      virtualization: false, height: 280, width: 650,
 3:      dataSource: "/Home/Customers",
 4:      autoGenerateColumns: false,
 5:      columns: [
 6:          { headerText: "Customer ID", key: "CustomerID", width: "120px", dataType: "string" },
 7:          { headerText: "Country", key: "Country", width: "150px", dataType: "string" },
 8:          { headerText: "City", key: "City", dataType: "string" },
 9:          { headerText: "Contact Name", key: "ContactName", dataType: "string" },
 10:          {headerText: "Phone", key: "Phone", dataType: "string" }
 11:      ],
 12:      features: [
 13:  
 14:       {
 15:           name: 'Selection',
 16:           mode: 'row',
 17:           multipleSelection: false,
 18:           rowSelectionChanged: function (ui, args) {
 19:               $("#chart").igDataChart({
 20:                   dataSource: "/Home/Orders?userID=" + args.row.element[0].cells[0].textContent
 21:               });
 22:  
 23:               selected = args.row.element[0].cells[0].textContent; //keep track of selected user
 24:               var countryUrl = "/Home/CountryByName?countryName=" + args.row.element[0].cells[1].textContent
 25:   
 26:  
 27:              $.getJSON(countryUrl,
 28:                 function (json, text) {
 29:                     $.each(json, function (index, value) {
 30:                         var country = value;
 31:                         var extend = country["Extend"];
 32:  
 33:                         var zoom = $("#map").igMap("getZoomFromGeographic", extend);
 34:                         $("#map").igMap("option", "windowRect", zoom);
 35:  
 36:                     });
 37:               });
 38:  
 39:           }
 40:       }
 41:      ,
 42:  
 43:      {
 44:          name: 'Sorting',
 45:          type: "remote"
 46:      },
 47:      {
 48:          name: 'Paging',
 49:          type: "local",
 50:          pageSize: 10
 51:      }]
 52:  
 53:  })

 

Spatial Data in Action

Run the application and select the “Spatial Data Dashboard” from the menu.

Choose a customer from the igGrid and see how the map shows the country from which the client

You can download source code of the sample here.

To run this sample you could download Northwind and SpatialDemo sample databases

As always, you can follow us on Twitter: @mihailmateev and @Infragistics , all tweets with hashtag #infragistcs and stay in touch on Facebook, Google+ , LinkedIn and Infragistics Friends User Group !