Datatable With Sqlgeography Column Can't Be Serialized To Xml Correctly With Loss Of Lat,long And Other Elements
Solution 1:
DataTable.WriteXml()
internally uses XmlSerializer
to serialize complex column values. And since Lat
and Long
are both get-only, and SqlGeography
doesn't implement IXmlSerializable
, there's no way this is going to work, since XmlSerializer
doesn't serialize get-only properties.
Instead, you're going to need to replace instances of SqlGeography
with some appropriate data transfer object in order to serialize it. But, what should that DTO contain and how should it be created? There are several options:
Manually convert to and from GML using
SqlGeography.AsGml()
andSqlGeography.GeomFromGml()
.In this scenario, your DTO would look like something this:
publicclassSqlGeographyDTO { constint DefaultSRID = 4326; // TODO: check if this is the correct default.publicint? STSrid { get; set; } public XElement Geography { get; set; } publicstaticimplicitoperatorSqlGeographyDTO(SqlGeography geography) { if (geography == null || geography.IsNull) returnnull; returnnew SqlGeographyDTO { STSrid = geography.STSrid.IsNull ? (int?)null : geography.STSrid.Value, Geography = geography.AsGml().ToXElement(), }; } publicstaticimplicitoperatorSqlGeography(SqlGeographyDTO dto) { if (dto == null) return SqlGeography.Null; var sqlXml = dto.Geography.ToSqlXml(); var geography = SqlGeography.GeomFromGml(sqlXml, dto.STSrid.GetValueOrDefault(DefaultSRID)); return geography; } publicoverridestringToString() { return Geography == null ? "" : Geography.ToString(SaveOptions.DisableFormatting); } } publicstaticclassXNodeExtensions { publicstatic SqlXml ToSqlXml(this XNode node) { if (node == null) return SqlXml.Null; using (var reader = node.CreateReader()) { returnnew SqlXml(reader); } } } publicstaticclassSqlXmlExtensions { publicstatic XElement ToXElement(this SqlXml sql) { if (sql == null || sql.IsNull) returnnull; using (var reader = sql.CreateReader()) return XElement.Load(reader); } }
Since GML is an XML-based format the result will be parsable by any XML parser. Note, however, that conversion from and to GML is documented not to be precise.
Working .Net fiddle.
(As an aside:
AsGml()
returns an object of typeSqlXml
which implementsIXmlSerializable
, so it would seem possible to include the returnedSqlXml
directly in the DTO. Unfortunately, testing reveals that eitherAsGml()
orSqlXml.WriteXml()
seem to have a bug: an XML declaration is always included, even when the XML is being written as a nested child element of an outer container element. Thus the resulting, serialized XML will be nonconformant and broken. Parsing to an intermediateXElement
avoids this bug by stripping the unwanted declaration.)Manually convert to and from Open Geospatial Consortium (OGC) Well-Known Text (WKT) representation augmented with any Z (elevation) and M (measure) values by using
ToString()
withSqlGeography.STGeomFromText()
.In this implementation your DTO would look something like:
publicclassSqlGeographyDTO { constint DefaultSRID = 4326; // TODO: check if this is the correct default.publicint? STSrid { get; set; } publicstring Geography { get; set; } publicstaticimplicitoperatorSqlGeographyDTO(SqlGeography geography) { if (geography == null || geography.IsNull) returnnull; returnnew SqlGeographyDTO { STSrid = geography.STSrid.IsNull ? (int?)null : geography.STSrid.Value, Geography = geography.ToString(), }; } publicstaticimplicitoperatorSqlGeography(SqlGeographyDTO dto) { if (dto == null) return SqlGeography.Null; var geography = SqlGeography.STGeomFromText(new SqlChars(dto.Geography), dto.STSrid.GetValueOrDefault(DefaultSRID)); return geography; } }
Again this implementation might lose precision, but has the advantage of being usable with many formats and serializers including JSON and Json.NET. This seems to be the approach used by
ServiceStack.OrmLite.SqlServer.Converters/SqlServerGeographyTypeConverter
, for instance.Working .Net fiddle courtesy of @M.Hassan.
Manually convert from and to a
byte []
binary representation usingSqlGeography.Write(BinaryWriter)
withSqlGeography.Read (BinaryReader)
.In this implementation your DTO class would contain
public byte [] Geography { get; set; }
.These methods are documented not to lose precision, but since the binary is opaque you won't be able to document your interchange format. This seems to be the approach used by
Dapper.EntityFramework.DbGeographyHandler
. Note however that Dapper is callingSTAsBinary()
, which, according to the documentation, returns a value that will not contain any Z or M values carried by the instance.
Once you have your DTO, you could replace it in your current DataTable
using one of the answers from, e.g., How To Change DataType of a DataColumn in a DataTable?.
Note - above DTOs are not fully tested.
Post a Comment for "Datatable With Sqlgeography Column Can't Be Serialized To Xml Correctly With Loss Of Lat,long And Other Elements"