具有SqlGeography列的数据表无法正确序列化为xml,同时丢失了Lat,Long和其他元素

时间:2017-08-26 11:51:25

标签: c# xml serialization datatable sqlgeography

我尝试将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方法中是否存在处理复杂对象的错误?

1 个答案:

答案 0 :(得分:1)

DataTable.WriteXml()在内部使用XmlSerializer来序列化复杂的列值。由于LatLong都是get-only,SqlGeography没有实现IXmlSerializable,因此XmlSerializer无法实现此功能不会序列化只获取属性。

相反,您需要将SqlGeography的实例替换为适当的data transfer object才能对其进行序列化。但是,DTO应包含什么以及如何创建它?有几种选择:

  1. 使用GMLSqlGeography.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通过剥离不需要的声明来避免这个错误。)

  2. 使用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

  3. 使用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值的值。

  4. 获得DTO后,您可以使用其中一个答案替换当前DataTable,例如https://developer.android.com/studio/build/gradle-plugin-3-0-0-migration.html

    注意 - 上述DTO未经过全面测试。