我尝试将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字符串不完整,丢失了所有Geo元素Lat,Long,...
它只包含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方法中是否存在处理复杂对象的错误?
答案 0 :(得分:1)
DataTable.WriteXml()
在内部使用XmlSerializer
来序列化复杂的列值。由于Lat
和Long
都是get-only,SqlGeography
没有实现IXmlSerializable
,因此XmlSerializer
无法实现此功能不会序列化只获取属性。
相反,您需要将SqlGeography
的实例替换为适当的data transfer object才能对其进行序列化。但是,DTO应包含什么以及如何创建它?有几种选择:
使用GML和SqlGeography.AsGml()
手动转换为SqlGeography.GeomFromGml()
。{/ p>
在这种情况下,您的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的转换并不准确。
工作.Net fiddle。
(顺便说一下:AsGml()
返回一个SqlXml
类型的对象,它实现IXmlSerializable
,因此似乎可以直接在DTO中包含返回的SqlXml
。不幸的是,测试表明AsGml()
或SqlXml.WriteXml()
似乎有一个错误:即使XML被写为外部容器元素的嵌套子元素,也总是包含XML声明。结果,序列化的XML将是不一致的和破坏的。解析为中间XElement
通过剥离不需要的声明来避免这个错误。)
使用ToString()
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
使用的方法,例如。
由@ M.Hassan提供.Net fiddle。
使用SqlGeography.Write(BinaryWriter)
SqlGeography.Read (BinaryReader)
使用Dapper.EntityFramework.DbGeographyHandler
手动转换为byte []
二进制表示形式。
在此实现中,您的DTO类将包含public byte [] Geography { get; set; }
。
记录这些方法不会失去精确度,但由于二进制文件是不透明的,因此您将无法记录您的交换格式。这似乎是STAsBinary()
使用的方法。但请注意,Dapper正在调用How To Change DataType of a DataColumn in a DataTable?,根据文档,它会返回一个不包含实例携带的任何Z或M值的值。
获得DTO后,您可以使用其中一个答案替换当前DataTable
,例如https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration.html。
注意 - 上述DTO未经过全面测试。