带有 SqlGeography 列的数据表无法序列化为 xml 正确丢失纬度、经度和其他元素
Datatable with SqlGeography column can't be serialized to xml correctly with loss of Lat,Long and other elements
我试图将 DataTable 对象序列化为 xml。
DataTable 有一个类型为 'geography' 的列,其中包含类型为 SqlGeography.
的实例
以下代码用于将数据表序列化为xml:
var writer = new StringWriter();
dt.WriteXmlSchema(writer); //get schema
//serialize to xml
dt.WriteXml(writer);
Console.WriteLine(writer.ToString());
生成的 xml 字符串不完整,丢失了所有地理元素纬度、经度、...
它只包含STSrid 元素。
下面是生成的xml字符串:
<table>
<f1 xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<STSrid>4326</STSrid>
</f1>
<id>1</id>
</table>
<table>
<f1 xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<STSrid>4326</STSrid>
</f1>
<id>2</id>
</table>
这意味着您不能使用 xml 进行反序列化,并且您丢失了 SqlGeography 数据。
模式生成正确:
<xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:MainDataTable="table" msdata:UseCurrentLocale="true">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="table">
<xs:complexType>
<xs:sequence>
<xs:element name="f1" msdata:DataType="Microsoft.SqlServer.Types.SqlGeography, Microsoft.SqlServer.Types, Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" type="xs:anyType" minOccurs="0" />
<xs:element name="id" type="xs:int" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
您可以找到 complete C# program online 来说明问题。
我的问题:
1) 我错过了什么以获得 SqlGeography 列的有效 xml 序列化?
2)DataTable.WriteXml处理复杂对象的方法不是bug吗?
DataTable.WriteXml()
内部使用 XmlSerializer
to serialize complex column values. And since Lat
and Long
are both get-only, and SqlGeography
doesn't implement IXmlSerializable
,这是行不通的,因为 XmlSerializer
不序列化 get-only 属性。
相反,您将需要用一些适当的 data transfer object 替换 SqlGeography
的实例以序列化它。但是,该 DTO 应该包含什么以及应该如何创建它?有几个选项:
手动转换为 GML using SqlGeography.AsGml()
and SqlGeography.GeomFromGml()
。
在这种情况下,您的 DTO 将如下所示:
public class SqlGeographyDTO
{
const int DefaultSRID = 4326; // TODO: check if this is the correct default.
public int? STSrid { get; set; }
public XElement Geography { get; set; }
public static implicit operator SqlGeographyDTO(SqlGeography geography)
{
if (geography == null || geography.IsNull)
return null;
return new SqlGeographyDTO
{
STSrid = geography.STSrid.IsNull ? (int?)null : geography.STSrid.Value,
Geography = geography.AsGml().ToXElement(),
};
}
public static implicit operator SqlGeography(SqlGeographyDTO dto)
{
if (dto == null)
return SqlGeography.Null;
var sqlXml = dto.Geography.ToSqlXml();
var geography = SqlGeography.GeomFromGml(sqlXml, dto.STSrid.GetValueOrDefault(DefaultSRID));
return geography;
}
public override string ToString()
{
return Geography == null ? "" : Geography.ToString(SaveOptions.DisableFormatting);
}
}
public static class XNodeExtensions
{
public static SqlXml ToSqlXml(this XNode node)
{
if (node == null)
return SqlXml.Null;
using (var reader = node.CreateReader())
{
return new SqlXml(reader);
}
}
}
public static class SqlXmlExtensions
{
public static XElement ToXElement(this SqlXml sql)
{
if (sql == null || sql.IsNull)
return null;
using (var reader = sql.CreateReader())
return XElement.Load(reader);
}
}
由于 GML 是一种基于 XML 的格式,因此任何 XML 解析器都可以解析结果。但是请注意,从 GML 到 GML 的转换记录并不准确。
工作.Net fiddle。
(顺便说一句:AsGml()
returns 类型 SqlXml
which implements IXmlSerializable
, so it would seem possible to include the returned SqlXml
directly in the DTO. Unfortunately, testing reveals that either AsGml()
or SqlXml.WriteXml()
的对象似乎有一个错误:始终包含 XML 声明,即使 XML 被写为外部容器元素的嵌套子元素。因此,生成的序列化 XML 将不符合要求并被破坏。解析为中间 XElement
通过剥离不需要的元素来避免此错误声明。)
使用 ToString()
with SqlGeography.STGeomFromText()
手动与开放地理空间联盟 (OGC) 知名文本 (WKT) 表示相互转换,并增加了任何 Z(高程)和 M(测量)值。
在此实现中,您的 DTO 类似于:
public class SqlGeographyDTO
{
const int DefaultSRID = 4326; // TODO: check if this is the correct default.
public int? STSrid { get; set; }
public string Geography { get; set; }
public static implicit operator SqlGeographyDTO(SqlGeography geography)
{
if (geography == null || geography.IsNull)
return null;
return new SqlGeographyDTO
{
STSrid = geography.STSrid.IsNull ? (int?)null : geography.STSrid.Value,
Geography = geography.ToString(),
};
}
public static implicit operator SqlGeography(SqlGeographyDTO dto)
{
if (dto == null)
return SqlGeography.Null;
var geography = SqlGeography.STGeomFromText(new SqlChars(dto.Geography), dto.STSrid.GetValueOrDefault(DefaultSRID));
return geography;
}
}
同样,此实现可能会损失精度,但具有可用于许多格式和序列化程序的优势,包括 JSON 和 Json.NET。例如,这似乎是 ServiceStack.OrmLite.SqlServer.Converters/SqlServerGeographyTypeConverter
使用的方法。
工作 .Net fiddle @M.Hassan.
使用 SqlGeography.Write(BinaryWriter)
with SqlGeography.Read (BinaryReader)
手动转换成 byte []
二进制表示。
在此实现中,您的 DTO class 将包含 public byte [] Geography { get; set; }
。
记录这些方法不会丢失精度,但由于二进制文件是不透明的,您将无法记录您的交换格式。这似乎是 Dapper.EntityFramework.DbGeographyHandler
. Note however that Dapper is calling STAsBinary()
使用的方法,根据文档,returns 一个值 将不包含实例携带的任何 Z 或 M 值。
获得 DTO 后,您可以使用来自 How To Change DataType of a DataColumn in a DataTable?.
的答案之一在当前 DataTable
中替换它
注意 - 以上 DTO 未经过全面测试。
我试图将 DataTable 对象序列化为 xml。 DataTable 有一个类型为 'geography' 的列,其中包含类型为 SqlGeography.
的实例以下代码用于将数据表序列化为xml:
var writer = new StringWriter();
dt.WriteXmlSchema(writer); //get schema
//serialize to xml
dt.WriteXml(writer);
Console.WriteLine(writer.ToString());
生成的 xml 字符串不完整,丢失了所有地理元素纬度、经度、...
它只包含STSrid 元素。
下面是生成的xml字符串:
<table>
<f1 xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<STSrid>4326</STSrid>
</f1>
<id>1</id>
</table>
<table>
<f1 xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<STSrid>4326</STSrid>
</f1>
<id>2</id>
</table>
这意味着您不能使用 xml 进行反序列化,并且您丢失了 SqlGeography 数据。
模式生成正确:
<xs:schema id="NewDataSet" xmlns="" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:MainDataTable="table" msdata:UseCurrentLocale="true">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="table">
<xs:complexType>
<xs:sequence>
<xs:element name="f1" msdata:DataType="Microsoft.SqlServer.Types.SqlGeography, Microsoft.SqlServer.Types, Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" type="xs:anyType" minOccurs="0" />
<xs:element name="id" type="xs:int" minOccurs="0" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
您可以找到 complete C# program online 来说明问题。
我的问题:
1) 我错过了什么以获得 SqlGeography 列的有效 xml 序列化?
2)DataTable.WriteXml处理复杂对象的方法不是bug吗?
DataTable.WriteXml()
内部使用 XmlSerializer
to serialize complex column values. And since Lat
and Long
are both get-only, and SqlGeography
doesn't implement IXmlSerializable
,这是行不通的,因为 XmlSerializer
不序列化 get-only 属性。
相反,您将需要用一些适当的 data transfer object 替换 SqlGeography
的实例以序列化它。但是,该 DTO 应该包含什么以及应该如何创建它?有几个选项:
手动转换为 GML using
SqlGeography.AsGml()
andSqlGeography.GeomFromGml()
。在这种情况下,您的 DTO 将如下所示:
public class SqlGeographyDTO { const int DefaultSRID = 4326; // TODO: check if this is the correct default. public int? STSrid { get; set; } public XElement Geography { get; set; } public static implicit operator SqlGeographyDTO(SqlGeography geography) { if (geography == null || geography.IsNull) return null; return new SqlGeographyDTO { STSrid = geography.STSrid.IsNull ? (int?)null : geography.STSrid.Value, Geography = geography.AsGml().ToXElement(), }; } public static implicit operator SqlGeography(SqlGeographyDTO dto) { if (dto == null) return SqlGeography.Null; var sqlXml = dto.Geography.ToSqlXml(); var geography = SqlGeography.GeomFromGml(sqlXml, dto.STSrid.GetValueOrDefault(DefaultSRID)); return geography; } public override string ToString() { return Geography == null ? "" : Geography.ToString(SaveOptions.DisableFormatting); } } public static class XNodeExtensions { public static SqlXml ToSqlXml(this XNode node) { if (node == null) return SqlXml.Null; using (var reader = node.CreateReader()) { return new SqlXml(reader); } } } public static class SqlXmlExtensions { public static XElement ToXElement(this SqlXml sql) { if (sql == null || sql.IsNull) return null; using (var reader = sql.CreateReader()) return XElement.Load(reader); } }
由于 GML 是一种基于 XML 的格式,因此任何 XML 解析器都可以解析结果。但是请注意,从 GML 到 GML 的转换记录并不准确。
工作.Net fiddle。
(顺便说一句:
AsGml()
returns 类型SqlXml
which implementsIXmlSerializable
, so it would seem possible to include the returnedSqlXml
directly in the DTO. Unfortunately, testing reveals that eitherAsGml()
orSqlXml.WriteXml()
的对象似乎有一个错误:始终包含 XML 声明,即使 XML 被写为外部容器元素的嵌套子元素。因此,生成的序列化 XML 将不符合要求并被破坏。解析为中间XElement
通过剥离不需要的元素来避免此错误声明。)使用
ToString()
withSqlGeography.STGeomFromText()
手动与开放地理空间联盟 (OGC) 知名文本 (WKT) 表示相互转换,并增加了任何 Z(高程)和 M(测量)值。在此实现中,您的 DTO 类似于:
public class SqlGeographyDTO { const int DefaultSRID = 4326; // TODO: check if this is the correct default. public int? STSrid { get; set; } public string Geography { get; set; } public static implicit operator SqlGeographyDTO(SqlGeography geography) { if (geography == null || geography.IsNull) return null; return new SqlGeographyDTO { STSrid = geography.STSrid.IsNull ? (int?)null : geography.STSrid.Value, Geography = geography.ToString(), }; } public static implicit operator SqlGeography(SqlGeographyDTO dto) { if (dto == null) return SqlGeography.Null; var geography = SqlGeography.STGeomFromText(new SqlChars(dto.Geography), dto.STSrid.GetValueOrDefault(DefaultSRID)); return geography; } }
同样,此实现可能会损失精度,但具有可用于许多格式和序列化程序的优势,包括 JSON 和 Json.NET。例如,这似乎是
ServiceStack.OrmLite.SqlServer.Converters/SqlServerGeographyTypeConverter
使用的方法。工作 .Net fiddle @M.Hassan.
使用
SqlGeography.Write(BinaryWriter)
withSqlGeography.Read (BinaryReader)
手动转换成byte []
二进制表示。在此实现中,您的 DTO class 将包含
public byte [] Geography { get; set; }
。记录这些方法不会丢失精度,但由于二进制文件是不透明的,您将无法记录您的交换格式。这似乎是
Dapper.EntityFramework.DbGeographyHandler
. Note however that Dapper is callingSTAsBinary()
使用的方法,根据文档,returns 一个值 将不包含实例携带的任何 Z 或 M 值。
获得 DTO 后,您可以使用来自 How To Change DataType of a DataColumn in a DataTable?.
的答案之一在当前DataTable
中替换它
注意 - 以上 DTO 未经过全面测试。