使用 SQL Server 2012、Entity Framework 5 和 ASP.NET MVC 中的空间数据






4.82/5 (7投票s)
Entity Framework 5 中备受期待的功能之一是空间支持。
引言
自 SQL 2008 发布以来,许多开发人员一直在请求在 Entity Framework 中支持空间数据类型。对于 Microsoft ORM 用户来说,能够使用空间数据快速创建 .NET 业务应用程序是一个梦想。今年五月,Entity Framework 5 (EF5) 的发布候选版发布了。与早期 EF 版本相比,此版本提高了性能,并且还支持空间类型。EF5 中的空间功能需要 .NET 4.5。
EF5 中的空间功能需要 .NET 4.5。这意味着您需要安装 Visual Studios 2012。您可以在此处下载 VS 2012 的发布候选版:http://www.microsoft.com/visualstudio/en-us
背景
Entity Framework 中的空间数据
在 Entity Framework 5.0 on .NET 4.5 之前,要使用上述数据,需要使用存储过程或原始 SQL 命令来访问空间数据。然而,在 Entity Framework 5 中,Microsoft 引入了新的 DbGeometry 和 DbGeography 类型。这些不可变的位置类型提供了大量用于处理几何函数中的空间点(进而可用于执行我上面 SQL 语法中所述的常见空间查询)的功能。
DbGeography/DbGeometry 类型是不可变的,这意味着一旦创建,就无法对其进行写入。它们有点特殊,您需要使用工厂方法来实例化它们——它们没有构造函数(),并且您无法为其属性(如 Latitude 和
Longitude)赋值。
值得一提的是,这些类型定义在 System.Data.Spatial 命名空间中的 System.Data.Entity 程序集中。到目前为止,您可能已经使用了定义在 Microsoft.SqlServer.Types 命名空间中的 SqlGeometry
和 SqlGeography 类型。
例如,一个名为 world 的实体包含一个类型为 DbGeometry 的 geom 属性。
[EdmScalarPropertyAttribute(EntityKeyProperty=false, IsNullable=true)]
[DataMemberAttribute()]
public global::System.Data.Spatial.DbGeometry geom
{
get
{
return _geom;
}
set
{
OngeomChanging(value);
ReportPropertyChanging("geom");
_geom = StructuralObject.SetValidValue(value, true, "geom");
ReportPropertyChanged("geom");
OngeomChanged();
}
}
private global::System.Data.Spatial.DbGeometry _geom;
partial void OngeomChanging(global::System.Data.Spatial.DbGeometry value);
partial void OngeomChanged();
使用代码
使用 Entity Framework 5 RC 和空间数据的 ASP.NET MVC 4 应用程序。
控制器 (Controller)
控制器返回一个包含 UI 控件的视图,这些控件显示空间元素。
数据维护在控制器中实现。本文也侧重于 MVC 的这部分。
#region DashboardJs
public ActionResult DashboardJs()
{
ViewBag.Message = "Spatial Data Dashboard";
return View();
}
#endregion //DashboardJs
空间数据维护
当您拥有来自 DbGeometry / DbGeography 类型的数据时,您无法对其进行序列化。有两种选择:
将空间数据类型转换为 WKT(文本标记语言),并作为 JSON 或 XML 的一部分发送到客户端(视图)。
- 使用您可以序列化自己的类。
- 此示例演示了第二种方法。
您需要有一个方法可以将结果序列化为 JSON,以便在视图中使用。
#region CountryByName
[OutputCache(VaryByParam = "countryName", Duration = 120)]
public JsonResult CountryByName(string countryName)
{
switch (countryName)
{
case "UK":
countryName = "United Kingdom";
break;
case "USA":
countryName = "United States";
break;
}
var results = spDemo.worlds.Where(x => x.CNTRY_NAME == countryName);
List<CountryInfo> ret = new List<CountryInfo>();
foreach (world country in results)
{
CountryInfo info = new CountryInfo
{
Id = country.ID,
Code = country.CODE,
CountryName = country.CNTRY_NAME,
Population = country.POP_CNTRY,
Extend = GetGeometryBoundary(country)
};
ret.Add(info);
}
var retVal = Json(ret, JsonRequestBehavior.AllowGet);
return retVal;
}
#endregion //CountryByName
您还需要使用几个辅助方法来获取表示 DbGeometry 实例包络的点列表。请记住,DbGeometry/DbGeography 的点索引从 1 开始!
#region GetGeometryBoundary
public static SpatialRect GetGeometryBoundary(world country)
{
List<SpatialPoint> multiPoints = new List<SpatialPoint>();
var numPoints = country.geom.Envelope.ElementAt(1).PointCount;
for (int i = 1; i <= numPoints; i++)
{
SpatialPoint pnt = new SpatialPoint((double)(
country.geom.Envelope.ElementAt(1).PointAt(i).XCoordinate),
(double)(country.geom.Envelope.ElementAt(1).PointAt(i).YCoordinate));
multiPoints.Add(pnt);
}
SpatialRect rect = multiPoints.GetBounds();
return rect;
}
#endregion //GetGeometryBoundary
用于序列化数据的辅助类
#region CountryInfo
public class CountryInfo
{
public int Id { get; set; }
public string Code { get; set; }
public string CountryName { get; set; }
public long? Population { get; set; }
public SpatialRect Extend { get; set; }
}
#endregion //CountryInfo
用于保存点数据的辅助类。
#region SpatialPoint
public class SpatialPoint
{
public SpatialPoint(double x, double y)
{
this.X = x;
this.Y = y;
}
public double X { get; set; }
public double Y { get; set; }
}
#endregion //SpatialPoint
用于保存几何对象(此处为国家)范围的辅助类。
#region SpatialRect
public struct SpatialRect
{
public SpatialRect(double pLeft, double pTop, double pWidth, double pHeight)
{
left = pLeft;
top = pTop;
width = pWidth;
height = pHeight;
}
public double left;
public double top;
public double width;
public double height;
}
#endregion //SpatialRect
用于获取点列表边界的扩展方法。
#region Extensions
public static class Extensions
{
#region GetBounds
public static SpatialRect GetBounds(this IList<SpatialPoint> points)
{
double xmin = Double.PositiveInfinity;
double ymin = Double.PositiveInfinity;
double xmax = Double.NegativeInfinity;
double ymax = Double.NegativeInfinity;
SpatialPoint p;
for (var i = 0; i < points.Count; i++)
{
p = points[i];
xmin = Math.Min(xmin, p.X);
ymin = Math.Min(ymin, p.Y);
xmax = Math.Max(xmax, p.X);
ymax = Math.Max(ymax, p.Y);
}
if (Double.IsInfinity(xmin) || Double.IsInfinity(ymin) ||
Double.IsInfinity(ymin) || Double.IsInfinity(ymax))
{
return new SpatialRect(0.0, 0.0, 0.0, 0.0);
}
return new SpatialRect(xmin, ymin, xmax - xmin, ymax - ymin);
}
#endregion //GetBounds
}
#endregion //Extensions
视图
视图显示一个带有 UI 组件的仪表板。
示例中最重要的一部分是如何查询控制器返回空间数据的(此处为国家范围的)方法。
关于一种可能的解决方案的更多细节,您可以在我的博客 这里 找到。
在此处下载源代码:此处。
兴趣点
此方法可用于不同的平台。本文提到了 JSON 序列化,但您可以使用不同的 Web 服务(如 WCF、OData)以不同的方式(二进制、XML 或 JSON)序列化空间数据。希望这种方法对各种客户端平台的开发人员有用。
历史
我将尽快尝试为移动 Web 平台添加实现。