空间数据

空间数据表示对象的物理位置和形状。 许多数据库为此类数据提供支持,以便可以与其他数据一起编制索引和查询数据。 常见方案包括查询给定距离某个位置内的对象,或选择其边框包含给定位置的对象。 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
FileBaseContext 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

  • 几何
    • 线串
    • 多边形
    • GeometryCollection
      • MultiPoint
      • 多线字符串
      • MultiPolygon

警告

NTS 不支持 CircularString、CompoundCurve 和 CurePolygon。

使用基本的 Geometry 类型,可以通过属性指定任何类型的形状。

经度和纬度

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 = await db.Cities
    .OrderBy(c => c.Location.Distance(currentLocation))
    .FirstOrDefaultAsync();
// Find the containing country
var currentCountry = await db.Countries
    .FirstOrDefaultAsync(c => c.Border.Contains(currentLocation));

反向工程

空间 NuGet 包还支持具有空间属性的反向工程模型,但需在运行Scaffold-DbContext或运行dotnet ef dbcontext scaffold之前安装该包。 如果不这样做,您将收到有关未找到列类型映射的警告,并会跳过这些列。

在客户端操作期间忽略 SRID

NTS 在进行操作时忽略 SRID 值。 它假定平面坐标系。 这意味着,如果在经度和纬度方面指定坐标,则某些客户端评估的值(如距离、长度和面积)将以度而不是米为单位。 对于更有意义的值,首先需要使用 ProjNet(for GeoAPI)等库将坐标投影到另一个坐标系。

注释

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

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

下面是使用 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 和其他地理系统中使用的标准。

其他资源

特定于数据库的信息

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

其他资源