PetaPoco 无法提取地理记录

PetaPoco Fails to Pull Geography Records

我正在使用 C#、.NET Framework 4.5(打算升级到 .NET 5)和 PetaPoco 作为我的 ORM。

我有一个名为 Jurisdiction 的 table,具有以下字段定义:

CREATE Table [Jurisdiction]
  ...
  [GeographicArea] [geography] NULL
  ...
);

在我的数据库层,我有以下内容:

var sql = @"
    SELECT
        Jurisdiction.*,
        State.StateName
    FROM
        Jurisdiction
        LEFT OUTER JOIN State ON Jurisdiction.StateId = State.StateId
";

if (where.Count > 0)
{
    sql += $" WHERE {string.Join(" AND ", where)}";
}

sql += orderBy;


var jurisdictions = _database.Query<T>(sql, parameters.ToArray()).ToList();

然而,当这个方法运行时我得到以下异常:

'Could not load file or assembly 'Microsoft.SqlServer.Types, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91' or one of its dependencies. The system cannot find the file specified.'

当我让 database.tt 文件自动为 Jurisdiction 生成 POCO 定义时,这导致了一个问题,所以我对 tt 文件所做的是添加以下内容,以便它停止尝试自动使用 SqlServers.Geography 类型:

tables["Jurisdiction"]["GeographicArea"].PropertyType="string";

然而,即使将字段定义为字符串,它仍然抛出异常,不管 是否将 Microsoft.SqlServer.Types 库添加到项目或没有。

我怎么能不使用 PetaPoco 来欺骗 Microsoft.SqlServer.Types 库?

我最终能够解决我的问题,尽管它非常复杂。在我的查询中,我没有执行 SELECT *,而是必须拼出每一列并使用以下方法手动转换地理值:

SELECT
    Jurisdiction.JurisdictionId,
    Jurisdiction.CreatedBy,
    Jurisdiction.CreatedOn,
    -- etc...
    CASE WHEN Jurisdiction.GeographicArea IS NULL THEN NULL ELSE Jurisdiction.GeographicArea.ToString() END AS GeographicArea -- CASE/WHEN/THEN/ELSE speeds up query
FROM
    Jurisdiction

然后在我的视图模型中,我使用以下方法设置插入和更新模板:

[Column(InsertTemplate = "geography::STMPolyFromText({0}{1}, 4326)", UpdateTemplate = "{0} = geography::STMPolyFromText({1}{2}, 4326)")] new public string GeographicArea { get; set; }

最后在我的服务层中,我创建了以下两种方法 get/update 地理列使用:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

namespace MyApplication
{
    public class Coordinate
    {
        private float _latitude;
        private float _longitude;

        public float Latitude {
            get => _latitude;
            set {
                if (value < -90 || value > 90)
                {
                    throw new ArgumentOutOfRangeException(nameof(Latitude), "The latitude is not between -90 and 90.");
                }
                _latitude = value;
            }
        }
        public float Longitude
        {
            get => _longitude;
            set
            {
                if (value < -180 || value > 180)
                {
                    throw new ArgumentOutOfRangeException(nameof(Longitude), "The longitude is not between -180 and 180.");
                }
                _longitude = value;
            }
        }

        public Coordinate()
        {
            Latitude = 0;
            Longitude = 0;
        }

        public Coordinate(string latitude, string longitude)
        {
            if (!float.TryParse(latitude, out float latitudeFloat))
            {
                throw new ArgumentException("Latitude must be a valid number.");
            }

            if (!float.TryParse(longitude, out float longitudeFloat))
            {
                throw new ArgumentException("Longitude must be a valid number.");
            }
            Latitude = latitudeFloat;
            Longitude = longitudeFloat;
        }

        public Coordinate(float latitude, float longitude)
        {
            Latitude = latitude;
            Longitude = longitude;
        }
    }

    public class SpatialConverterService
    {
        // find everything but ([^...]): numbers (\d), decimal points (\.), spaces (\s), and commas (,)
        private readonly static Regex _geographyIrrelevantData = new Regex(@"[^\d\.\s\-,]");

        /// <summary>
        /// Takes a SQL geography string and converts it to a collection of Coordinate values
        /// </summary>
        /// <param name="geography"><see cref="string"/> the SQL geography string</param>
        /// <returns><see cref="IEnumerable{Coordinate}"/> the collection of coordinates</returns>
        public static IEnumerable<Coordinate> ConvertSqlGeographyToCoordinates(string geography)
        {
            var geographyPoints = _geographyIrrelevantData.Replace(geography, string.Empty);
            geographyPoints = geographyPoints.Trim();

            var coordinateStrings = geographyPoints.Split(new[] { ',' });
            var coordinates = coordinateStrings.Select(coordinate =>
            {
                coordinate = coordinate.Trim();
                var points = coordinate.Split(new[] { ' ' });
                if (points.Count() != 2)
                {
                    throw new Exception($"Coordinate is not in a valid format, expecting longitude and latitude separated by a space but got: {coordinate}");
                }

                // SQL represents points as: lng lat
                return new Coordinate(points[1], points[0]);
            });

            return coordinates;
        }

        /// <summary>
        /// Takes a collection of <see cref="Coordinate"/> and converts it to a SQL geography string
        /// </summary>
        /// <param name="coordinates"><see cref="IEnumerable{Coordinate}"/> the collection of coordinates to convert</param>
        /// <returns><see cref="string"/> the SQL geography string</returns>
        public static string ConvertCoordinatesToSqlGeography(IEnumerable<Coordinate> coordinates)
        {
            if (!coordinates.Any())
            {
                throw new ArgumentNullException(nameof(coordinates), "There are no coordinates in the collection.");
            }

            var sqlConversion = string.Join(", ", coordinates.Select(coordinate => $"{coordinate.Longitude} {coordinate.Latitude}"));
            if (coordinates.First() != coordinates.Last() || coordinates.Count() == 1)
            {
                // SQL requires that the geography get completed by ending on the first coordinate
                var firstCoordinate = coordinates.First();
                sqlConversion += $", {firstCoordinate.Longitude} {firstCoordinate.Latitude}";
            }
            
            var multipolygon = $"MULTIPOLYGON((({sqlConversion})))";
            return multipolygon;
        }
    }
}