2016-08-03 10:59:49 +00:00
//------------------------------------------------------------------------------
// <copyright file="SqlSpatialDataReader.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner willa
2017-08-21 15:34:15 +00:00
// @backupOwner Microsoft
2016-08-03 10:59:49 +00:00
//------------------------------------------------------------------------------
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
{
/// <summary>
/// SqlClient specific implementation of <see cref="DbSpatialDataReader"/>
/// </summary>
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 < Func < BinaryReader , object > > sqlGeographyFromBinaryReader = new Singleton < Func < BinaryReader , object > > ( ( ) = > CreateBinaryReadDelegate ( SqlProviderServices . GetSqlTypesAssembly ( ) . SqlGeographyType ) ) ;
private static readonly Singleton < Func < BinaryReader , object > > sqlGeometryFromBinaryReader = new Singleton < Func < BinaryReader , object > > ( ( ) = > 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 ) ) ;
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="spatialType"></param>
/// <returns></returns>
private static Func < BinaryReader , object > 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 < Func < BinaryReader , object > > (
Expression . Block (
new [ ] { binarySerializable } ,
Expression . Assign ( binarySerializable , Expression . New ( spatialType ) ) ,
Expression . Call ( binarySerializable , readMethod , readerParam ) ,
binarySerializable
) ,
readerParam
) ;
Func < BinaryReader , object > result = ex . Compile ( ) ;
return result ;
}
}
}