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;
}
}
}
我正在使用 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;
}
}
}