Entity Framework Core 3.1 with NetTopologySuite.Geometries.Point: SqlException: 提供的值不是数据类型地理的有效实例

Entity Framework Core 3.1 with NetTopologySuite.Geometries.Point: SqlException: The supplied value is not a valid instance of data type geography

我有一个看起来像这样的模型:

public class Facility
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public NetTopologySuite.Geometries.Point Location { get; set; }
}

加点测试代码:

var testFacility = new Facility();
testFacility.Location = new NetTopologySuite.Geometries.Point(13.003725d, 55.604870d) { SRID = 3857 };

//Other values tested with the same error error

//testFacility.Location = new NetTopologySuite.Geometries.Point(13.003725d, 55.604870d);

//testFacility.Location = new NetTopologySuite.Geometries.Point(55.604870d, 13.003725d);

//var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid: 3857);
//var currentLocation = geometryFactory.CreatePoint(new Coordinate(13.003725d, 55.604870d));
//testFacility.Location = currentLocation;

db.Facilities.Add(testFacility);
//Exception on Save
db.SaveChanges();

我正在使用以下 NuGets,版本 3.1.0

Microsoft.AspNetCore.Identity.EntityFrameworkCore
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools
Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite

我在保存时遇到的异常如下:

SqlException: The incoming tabular data stream (TDS) remote procedure call (RPC) protocol stream is incorrect. Parameter 7 ("@p6"): The supplied value is not a valid instance of data type geography. Check the source data for invalid values. An example of an invalid value is data of numeric type with scale greater than precision.

根据所有文档,经度应为 X,纬度应为 Y,因此我认为这不是问题。我试图反转坐标以防万一,但我得到了与您在我尝试过的示例中看到的相同的错误。

https://docs.microsoft.com/en-us/ef/core/modeling/spatial

纬度 = Y 经度 = X

https://gis.stackexchange.com/a/68856/71364

我找不到任何明显的错误。 Optionsbuilder 已设置,table 是使用数据类型 geography 创建的,它与 DbGeography for Entity Framework 6.

一起工作得非常好
var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
optionsBuilder.UseSqlServer("Server=(localdb)\mssqllocaldb;Database=TestDb;Trusted_Connection=True;MultipleActiveResultSets=true",
    x => x.UseNetTopologySuite());

var db = new ApplicationDbContext(optionsBuilder.Options);

我在 SQL 服务器的文档中看到,没有针对单个 Point 处理的具体情况。

https://docs.microsoft.com/en-us/ef/core/modeling/spatial#sql-server

我保存的坐标来自 Google 地图,因此使用 EPSG 3857

https://gis.stackexchange.com/questions/48949/epsg-3857-or-4326-for-googlemaps-openstreetmap-and-leaflet

我错过了什么?

TLDR

SQL 服务器 sys.spatial_reference_systems

中不存在 SRID

更改为像 4326 这样存在的一个,它将起作用:

select *
from sys.spatial_reference_systems
where spatial_reference_id = '4326'

长答案:

Google Maps API 使用 EPSG 3857 但 Google Maps Web 应用程序使用 EPSG 4326

https://developers.google.com/maps/documentation/javascript/markers

https://www.google.com/maps/@55.604933,13.003662,14z

因此,Google 地图 Web 应用程序中的一个点应该像这样创建和保存:

var testFacility = new Facility();
testFacility.Location = new NetTopologySuite.Geometries.Point(13.003725d, 55.604870d) { SRID = 4326 };
db.Facilities.Add(testFacility);
db.SaveChanges();

然而,将 EPSG 4326 坐标投影到 EPSG 3857 坐标系有点棘手。 Microsoft 建议使用 ProjNet4GeoAPI,所以我决定使用它。

https://docs.microsoft.com/en-us/ef/core/modeling/spatial#srid-ignored-during-client-operations

我已经验证它在这里有效:

http://epsg.io/transform#s_srs=4326&t_srs=3857&x=13.003725&y=55.604870

转换示例:

var x = 13.003725d;
var y = 55.604870d;

var epsg3857ProjectedCoordinateSystem = ProjNet.CoordinateSystems.ProjectedCoordinateSystem.WebMercator;
var epsg4326GeographicCoordinateSystem = ProjNet.CoordinateSystems.GeographicCoordinateSystem.WGS84;

var coordinateTransformationFactory = new ProjNet.CoordinateSystems.Transformations.CoordinateTransformationFactory();
var coordinateTransformation = coordinateTransformationFactory.CreateFromCoordinateSystems(epsg4326GeographicCoordinateSystem, epsg3857ProjectedCoordinateSystem);

var epsg4326Coordinate = new GeoAPI.Geometries.Coordinate(x, y);

var epsg3857Coordinate = coordinateTransformation.MathTransform.Transform(epsg4326Coordinate);

完整的示例程序:

获取运行:

  • 安装 NuGet
    • 以下 NuGet 的版本为 3.1:
      • Microsoft.EntityFrameworkCore
      • Microsoft.EntityFrameworkCore.SqlServer
      • Microsoft.EntityFrameworkCore.工具
      • Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite
    • ProjNET4GeoAPI
  • 添加迁移 InitialCreate
  • 更新数据库

代码:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using NetTopologySuite;
using NetTopologySuite.Geometries;
using ProjNet.CoordinateSystems;
using ProjNet.CoordinateSystems.Transformations;
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace TestConsoleAppEFGeo
{
    public class ApplicationDbContextFactory : IDesignTimeDbContextFactory<ApplicationDbContext>
    {
        public ApplicationDbContext CreateDbContext(string[] args)
        {
            var optionsBuilder = new DbContextOptionsBuilder<ApplicationDbContext>();
            optionsBuilder.UseSqlServer("Server=(localdb)\mssqllocaldb;Database=TestApp;Trusted_Connection=True;MultipleActiveResultSets=true",
                x => x.UseNetTopologySuite());

            return new ApplicationDbContext(optionsBuilder.Options);
        }
    }

    public class ApplicationDbContext : DbContext
    {
        public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
            : base(options)
        {
        }

        public virtual DbSet<Facility> Facilities { get; set; }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
        }
    }

    public class Facility
    {
        [Key]
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int Id { get; set; }

        public NetTopologySuite.Geometries.Point Location { get; set; }
    }

    class Program
    {
        static void Main(string[] args)
        {
            var applicationDbContextFactory = new ApplicationDbContextFactory();
            var db = applicationDbContextFactory.CreateDbContext(null);

            var x = 13.003725d;
            var y = 55.604870d;
            var srid = 4326;

            if (!db.Facilities.AnyAsync(x => x.Id == 1).Result)
            {
                var testFacility = new Facility();
                var geometryFactory = NtsGeometryServices.Instance.CreateGeometryFactory(srid);
                var currentLocation = geometryFactory.CreatePoint(new NetTopologySuite.Geometries.Coordinate(x, y));
                testFacility.Id = 1;
                testFacility.Location = currentLocation;

                var testFacility2 = new Facility();
                testFacility2.Id = 2;
                testFacility2.Location = new Point(x, y) { SRID = srid };
                db.Facilities.Add(testFacility);
                db.Facilities.Add(testFacility2);

                //Will throw an exception
                //var testFacility3 = new Facility();
                //testFacility3.Id = 3;
                //testFacility3.Location = new Point(1447568.0454157612d, 7480155.2276327936d) { SRID = 3857 };
                //db.Facilities.Add(testFacility3);

                db.SaveChanges();
            }

            var facility1 = db.Facilities.FirstAsync(x => x.Id == 1).Result;
            var facility2 = db.Facilities.FirstAsync(x => x.Id == 2).Result;

            if(facility1.Location == facility2.Location)
            {
                Console.WriteLine("facility1.Location is equal to facility2.Location");
            }
            else
            {
                Console.WriteLine("facility1.Location is NOT equal to facility2.Location");
            }

            //Test conversion
            //Show coordinate: http://epsg.io/map#srs=4326&x=13.003725&y=55.604870&z=14&layer=streets
            //Conversion: http://epsg.io/transform#s_srs=4326&t_srs=3857&x=13.0037250&y=55.6048700
            //Google Maps - https://www.google.se/maps shows EPSG:4326 when viewing a location
            //https://epsg.io/3857 - Google Maps API is EPSG:3857 however
            //Example: https://developers.google.com/maps/documentation/javascript/markers

            var epsg3857ProjectedCoordinateSystem = ProjectedCoordinateSystem.WebMercator;
            var epsg4326GeographicCoordinateSystem = GeographicCoordinateSystem.WGS84;

            var coordinateTransformationFactory = new CoordinateTransformationFactory();
            var coordinateTransformation = coordinateTransformationFactory.CreateFromCoordinateSystems(epsg4326GeographicCoordinateSystem, epsg3857ProjectedCoordinateSystem);

            var epsg4326Coordinate = new GeoAPI.Geometries.Coordinate(facility1.Location.Coordinate.X, facility1.Location.Coordinate.Y);

            var epsg3857Coordinate = coordinateTransformation.MathTransform.Transform(epsg4326Coordinate);

        }
    }
}

更多信息在这里:

https://github.com/dotnet/efcore/issues/19416

It's probably already in 4326,快乐的日子,易于存储,sql 应该让你存储这个(API 可能使用 3857 但提供位置的 lat/lon 以度为单位而不是米,实际上你已经在 4326)

中获得了 lat/lon

假设您在 SRID=3857 中得到 lat/lon 并想尝试以这种方式存储它:

检查您是否拥有与 3857 等效的 SRID 版本,该版本将在您的数据库中运行

SELECT * FROM sys.spatial_reference_systems 
WHERE authorized_spatial_reference_id 
IN('3857', '900913', '3587', '54004', '41001', '102113', '102100', '3785')

例如,如果您碰巧有 900913 try using that on a lat/lon insert with no conversion if you have it, I'm basing this assumption on comparing the properties of the hyperlinked "alternatives codes" to EPSG:3857

我不知道它是否有效,这完全不是我的知识领域。

假设您没有得到 SQL 行,那么您必须将 3857 转换为 4326 才能存储在您的数据库中...


如何将 3857 转换为 4326 以便您可以存储它:

通过 NuGet 安装 ProjNet4GeoAPI 并使用以下代码:

using GeoAPI.Geometries;
using ProjNet.CoordinateSystems;
using ProjNet.CoordinateSystems.Transformations;

...

// setup
var epsg3857 = ProjectedCoordinateSystem.WebMercator;
var epsg4326 = GeographicCoordinateSystem.WGS84;
var convertTo4326 = new CoordinateTransformationFactory()
                        .CreateFromCoordinateSystems(epsg3857, epsg4326);

// input 6415816.17/171048.38 (Brussels lat/lon in meters SRID 3857)
//       N.B. method called needs the values as lon/lat (x/y), not lat/lon 
var coordIn3857 = new GeoAPI.Geometries.Coordinate(171048.38, 6415816.17);

var coordIn4326 = convertTo4326.MathTransform.Transform(coordIn3857);
// output 49.82379612579344/1.5365537407788388 (Brussels lat/lon in degrees SRID 4326)

现在将其保存到您的数据库中

testFacility.Location = new NetTopologySuite.Geometries.Point(1.536553, 49.823796) 
                             { SRID = 4326 };

要从另一个方向转换并使用存储的 4326 值的 3857,很容易找出或查看 Ogglas 的