空间数据

空间数据表示对象的物理位置和形状。 许多数据库都支持此类型的数据,因此可以同时索引和查询该数据与其他数据。 常见方案包括查询距某个位置指定距离内的对象,或选择边界包含指定位置的对象。 EF Core 支持使用 NetTopologySuite 空间库映射到空间数据类型。

安装

为在 EF Core 中使用空间数据,需安装相应的支持 NuGet 包。 需安装的包取决于所使用的提供程序。

EF Core 提供程序 空间 NuGet 包
Microsoft.EntityFrameworkCore.SqlServer Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite
Microsoft.EntityFrameworkCore.Sqlite Microsoft.EntityFrameworkCore.Sqlite.NetTopologySuite
Microsoft.EntityFrameworkCore.InMemory NetTopologySuite
Npgsql.EntityFrameworkCore.PostgreSQL Npgsql.EntityFrameworkCore.PostgreSQL.NetTopologySuite
Pomelo.EntityFrameworkCore.MySql Pomelo.EntityFrameworkCore.MySql.NetTopologySuite
Devart.Data.MySql.EFCore Devart.Data.MySql.EFCore.NetTopologySuite
Devart.Data.Oracle.EFCore Devart.Data.Oracle.EFCore.NetTopologySuite
Devart.Data.PostgreSql.EFCore Devart.Data.PostgreSql.EFCore.NetTopologySuite
Devart.Data.SQLite.EFCore Devart.Data.SQLite.EFCore.NetTopologySuite
Teradata.EntityFrameworkCore Teradata.EntityFrameworkCore.NetTopologySuite

NetTopologySuite

NetTopologySuite (NTS) 是 .NET 的空间库。 EF Core 支持在模型中使用 NTS 类型映射到数据库中的空间数据类型。

若要支持通过 NTS 映射到空间类型,请调用提供程序的 DbContext 选项构建器上的 UseNetTopologySuite 方法。 例如,在 SQL Server 中可以通过以下方式对其进行调用。

options.UseSqlServer(
    @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=WideWorldImporters;ConnectRetryCount=0",
    x => x.UseNetTopologySuite());

空间数据类型有多种。 使用哪种类型取决于要允许的形状类型。 以下是可在模型中用于属性的 NTS 类型的层次结构。 它们位于 NetTopologySuite.Geometries 命名空间内。

  • 几何
    • Point
    • LineString
    • 多边形
    • GeometryCollection
      • MultiPoint
      • MultiLineString
      • MultiPolygon

警告

NTS 不支持 CircularString、CompoundCurve 和 CurePolygon。

使用基本几何类型支持通过属性指定任意类型的形状。

经度和纬度

NTS 中的坐标以 X 和 Y 值表示。 若要表示经度和纬度,请使用 X 表示经度,使用 Y 表示纬度。 请注意,这与这些值的常见的 latitude, longitude 格式相反

查询数据

以下实体类可用于映射到 Wide World Importers 示例数据库中的表。

[Table("Cities", Schema = "Application")]
public class City
{
    public int CityID { get; set; }

    public string CityName { get; set; }

    public Point Location { get; set; }
}
[Table("Countries", Schema = "Application")]
public class Country
{
    public int CountryID { get; set; }

    public string CountryName { get; set; }

    // Database includes both Polygon and MultiPolygon values
    public Geometry Border { get; set; }
}

在 LINQ 中,可用作数据库函数的 NTS 方法和属性将转换为 SQL。 例如,以下查询中会转换 Distance 和 Contains 方法。 参阅提供程序文档,了解支持哪些方法。

// Find the nearest city
var nearestCity = db.Cities
    .OrderBy(c => c.Location.Distance(currentLocation))
    .FirstOrDefault();
// Find the containing country
var currentCountry = db.Countries
    .FirstOrDefault(c => c.Border.Contains(currentLocation));

反向工程

空间 NuGet 包还支持具有空间属性的反向工程模型,但需先安装该包才能运行 Scaffold-DbContextdotnet ef dbcontext scaffold。 如果不这样做,你将收到未找到列的类型映射警告,并且这些列将被跳过。

客户端操作期间忽略的 SRID

NTS 忽略操作期间的 SRID 值。 它假定一个平面坐标系。 这意味着如果以经度和纬度为单位指定坐标,则部分客户端评估的值(例如距离、长度和面积)将以度为单位,而不是米。 若要获得更有意义的值,首先需要使用 ProjNet(用于 GeoAPI)等库将坐标投射到另一个坐标系。

注意

使用较新的 ProjNet NuGet 包,而不是名为 ProjNet4GeoAPI 的旧包

如果 EF Core 通过 SQL 对操作进行服务器评估,则结果的单位将由数据库决定。

以下示例使用 ProjNet 计算两个城市之间的距离。

public static class GeometryExtensions
{
    private static readonly CoordinateSystemServices _coordinateSystemServices
        = new CoordinateSystemServices(
            new Dictionary<int, string>
            {
                // Coordinate systems:

                [4326] = GeographicCoordinateSystem.WGS84.WKT,

                // This coordinate system covers the area of our data.
                // Different data requires a different coordinate system.
                [2855] =
                    @"
                        PROJCS[""NAD83(HARN) / Washington North"",
                            GEOGCS[""NAD83(HARN)"",
                                DATUM[""NAD83_High_Accuracy_Regional_Network"",
                                    SPHEROID[""GRS 1980"",6378137,298.257222101,
                                        AUTHORITY[""EPSG"",""7019""]],
                                    AUTHORITY[""EPSG"",""6152""]],
                                PRIMEM[""Greenwich"",0,
                                    AUTHORITY[""EPSG"",""8901""]],
                                UNIT[""degree"",0.01745329251994328,
                                    AUTHORITY[""EPSG"",""9122""]],
                                AUTHORITY[""EPSG"",""4152""]],
                            PROJECTION[""Lambert_Conformal_Conic_2SP""],
                            PARAMETER[""standard_parallel_1"",48.73333333333333],
                            PARAMETER[""standard_parallel_2"",47.5],
                            PARAMETER[""latitude_of_origin"",47],
                            PARAMETER[""central_meridian"",-120.8333333333333],
                            PARAMETER[""false_easting"",500000],
                            PARAMETER[""false_northing"",0],
                            UNIT[""metre"",1,
                                AUTHORITY[""EPSG"",""9001""]],
                            AUTHORITY[""EPSG"",""2855""]]
                    "
            });

    public static Geometry ProjectTo(this Geometry geometry, int srid)
    {
        var transformation = _coordinateSystemServices.CreateTransformation(geometry.SRID, srid);

        var result = geometry.Copy();
        result.Apply(new MathTransformFilter(transformation.MathTransform));

        return result;
    }

    private class MathTransformFilter : ICoordinateSequenceFilter
    {
        private readonly MathTransform _transform;

        public MathTransformFilter(MathTransform transform)
            => _transform = transform;

        public bool Done => false;
        public bool GeometryChanged => true;

        public void Filter(CoordinateSequence seq, int i)
        {
            var x = seq.GetX(i);
            var y = seq.GetY(i);
            var z = seq.GetZ(i);
            _transform.Transform(ref x, ref y, ref z);
            seq.SetX(i, x);
            seq.SetY(i, y);
            seq.SetZ(i, z);
        }
    }
}
var seattle = new Point(-122.333056, 47.609722) { SRID = 4326 };
var redmond = new Point(-122.123889, 47.669444) { SRID = 4326 };

// In order to get the distance in meters, we need to project to an appropriate
// coordinate system. In this case, we're using SRID 2855 since it covers the
// geographic area of our data
var distanceInDegrees = seattle.Distance(redmond);
var distanceInMeters = seattle.ProjectTo(2855).Distance(redmond.ProjectTo(2855));

注意

4326 指的是 WGS 84,一种在 GPS 和其他地理系统中使用的标准。

其他资源

特定于数据库的信息

务必阅读提供程序文档,了解有关使用空间数据的其他信息。

其他资源