//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner willa // @backupOwner [....] //------------------------------------------------------------------------------ using System.Data.Common.Utils; using System.Data.Entity; using System.Data.Spatial; using System.Data.SqlTypes; using System.Diagnostics; using System.IO; using System.Linq.Expressions; using System.Reflection; namespace System.Data.SqlClient { /// /// SqlClient specific implementation of /// internal sealed partial class SqlSpatialDataReader : DbSpatialDataReader { private readonly SqlDataReader reader; private const string geometrySqlType = "sys.geometry"; private const string geographySqlType = "sys.geography"; internal SqlSpatialDataReader(SqlDataReader underlyingReader) { this.reader = underlyingReader; } public override DbGeography GetGeography(int ordinal) { EnsureGeographyColumn(ordinal); SqlBytes geogBytes = this.reader.GetSqlBytes(ordinal); object providerValue = sqlGeographyFromBinaryReader.Value(new BinaryReader(geogBytes.Stream)); return SqlSpatialServices.Instance.GeographyFromProviderValue(providerValue); } public override DbGeometry GetGeometry(int ordinal) { EnsureGeometryColumn(ordinal); SqlBytes geomBytes = this.reader.GetSqlBytes(ordinal); object providerValue = sqlGeometryFromBinaryReader.Value(new BinaryReader(geomBytes.Stream)); return SqlSpatialServices.Instance.GeometryFromProviderValue(providerValue); } private static readonly Singleton> sqlGeographyFromBinaryReader = new Singleton>(() => CreateBinaryReadDelegate(SqlProviderServices.GetSqlTypesAssembly().SqlGeographyType)); private static readonly Singleton> sqlGeometryFromBinaryReader = new Singleton>(() => CreateBinaryReadDelegate(SqlProviderServices.GetSqlTypesAssembly().SqlGeometryType)); // test to ensure that the SQL column has the expected SQL type. Don't use the CLR type to avoid having to worry about differences in // type versions between the client and the database. private void EnsureGeographyColumn(int ordinal) { string fieldTypeName = this.reader.GetDataTypeName(ordinal); if (!fieldTypeName.EndsWith(geographySqlType, StringComparison.Ordinal)) // Use EndsWith so that we just see the schema and type name, not the database name. { throw new InvalidDataException(Strings.SqlProvider_InvalidGeographyColumn(fieldTypeName)); } } private void EnsureGeometryColumn(int ordinal) { string fieldTypeName = this.reader.GetDataTypeName(ordinal); if (!fieldTypeName.EndsWith(geometrySqlType, StringComparison.Ordinal)) // Use EndsWith so that we just see the schema and type name, not the database name. { throw new InvalidDataException(Strings.SqlProvider_InvalidGeometryColumn(fieldTypeName)); } } /// /// Builds and compiles the Expression equivalent of the following: /// /// (BinaryReader r) => { var result = new SpatialType(); result.Read(r); return r; } /// /// The construct/read pattern is preferred over casting the result of calling GetValue on the DataReader, /// because constructing the value directly allows client code to specify the type, rather than SqlClient using /// the server-specified assembly qualified type name from TDS to try to locate the correct type on the client. /// /// /// private static Func CreateBinaryReadDelegate(Type spatialType) { Debug.Assert(spatialType != null, "Ensure spatialType is non-null before calling CreateBinaryReadDelegate"); var readerParam = Expression.Parameter(typeof(BinaryReader)); var binarySerializable = Expression.Variable(spatialType); var readMethod = spatialType.GetMethod("Read", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(System.IO.BinaryReader) }, null); var ex = Expression.Lambda>( Expression.Block( new[] { binarySerializable }, Expression.Assign(binarySerializable, Expression.New(spatialType)), Expression.Call(binarySerializable, readMethod, readerParam), binarySerializable ), readerParam ); Func result = ex.Compile(); return result; } } }