//------------------------------------------------------------------------------ // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner willa // @backupOwner Microsoft //------------------------------------------------------------------------------ using System.Collections.Generic; using System.Data.Spatial; using System.Data.SqlClient.Internal; using System.Data.Spatial.Internal; using System.Data.Common.Utils; using System.Diagnostics; using System.Reflection; using System.Runtime.Serialization; using System.Threading; namespace System.Data.SqlClient { /// /// SqlClient specific implementation of /// [Serializable] internal sealed partial class SqlSpatialServices : DbSpatialServices, ISerializable { /// /// Do not allow instantiation /// internal static readonly SqlSpatialServices Instance = new SqlSpatialServices(SqlProviderServices.GetSqlTypesAssembly); private static Dictionary otherSpatialServices; [NonSerialized] private readonly Singleton _sqlTypesAssemblySingleton; private SqlSpatialServices(Func getSqlTypes) { Debug.Assert(getSqlTypes != null, "Validate SqlTypes assembly delegate before constructing SqlSpatialServiceS"); this._sqlTypesAssemblySingleton = new Singleton(getSqlTypes); // Create Singletons that will delay-initialize the MethodInfo and PropertyInfo instances used to invoke SqlGeography/SqlGeometry methods via reflection. this.InitializeMemberInfo(); } private SqlSpatialServices(SerializationInfo info, StreamingContext context) { SqlSpatialServices instance = Instance; this._sqlTypesAssemblySingleton = instance._sqlTypesAssemblySingleton; this.InitializeMemberInfo(instance); } // Given an assembly purportedly containing SqlServerTypes for spatial values, attempt to // create a corersponding Sql spefic DbSpatialServices value backed by types from that assembly. // Uses a dictionary to ensure that there is at most db spatial service per assembly. It's important that // this be done in a way that ensures that the underlying SqlTypesAssembly value is also atomized, // since that's caching compilation. // Relies on SqlTypesAssembly to verify that the assembly is appropriate. private static bool TryGetSpatialServiceFromAssembly(Assembly assembly, out SqlSpatialServices services) { if (otherSpatialServices == null || !otherSpatialServices.TryGetValue(assembly.FullName, out services)) { lock (Instance) { if (otherSpatialServices == null || !otherSpatialServices.TryGetValue(assembly.FullName, out services)) { SqlTypesAssembly sqlAssembly; if (SqlTypesAssembly.TryGetSqlTypesAssembly(assembly, out sqlAssembly)) { if (otherSpatialServices == null) { otherSpatialServices = new Dictionary(1); } services = new SqlSpatialServices(() => sqlAssembly); otherSpatialServices.Add(assembly.FullName, services); } else { services = null; } } } } return services != null; } private SqlTypesAssembly SqlTypes { get { return this._sqlTypesAssemblySingleton.Value; } } public override object CreateProviderValue(DbGeographyWellKnownValue wellKnownValue) { wellKnownValue.CheckNull("wellKnownValue"); object result = null; if (wellKnownValue.WellKnownText != null) { result = this.SqlTypes.SqlTypesGeographyFromText(wellKnownValue.WellKnownText, wellKnownValue.CoordinateSystemId); } else if (wellKnownValue.WellKnownBinary != null) { result = this.SqlTypes.SqlTypesGeographyFromBinary(wellKnownValue.WellKnownBinary, wellKnownValue.CoordinateSystemId); } else { throw SpatialExceptions.WellKnownGeographyValueNotValid("wellKnownValue"); } return result; } public override DbGeography GeographyFromProviderValue(object providerValue) { providerValue.CheckNull("providerValue"); object normalizedProviderValue = NormalizeProviderValue(providerValue, this.SqlTypes.SqlGeographyType); return this.SqlTypes.IsSqlGeographyNull(normalizedProviderValue) ? null: DbSpatialServices.CreateGeography(this, normalizedProviderValue); } // Ensure that provider values are from the expected version of the Sql types assembly. If they aren't try to // convert them so that they are. // // Normally when we obtain values from the store, we try to use the appropriate SqlSpatialDataReader. This will make sure that // any spatial values are instantiated with the provider type from the appropriate SqlServerTypes assembly. However, // in one case (output parameter values) we don't have an opportunity to make this happen. There we get whatever value // the underlying SqlDataReader produces which doesn't necessarily produce values from the assembly we expect. private object NormalizeProviderValue(object providerValue, Type expectedSpatialType) { Debug.Assert(expectedSpatialType == this.SqlTypes.SqlGeographyType || expectedSpatialType == this.SqlTypes.SqlGeometryType); Type providerValueType = providerValue.GetType(); if (providerValueType != expectedSpatialType) { SqlSpatialServices otherServices; if (TryGetSpatialServiceFromAssembly(providerValue.GetType().Assembly, out otherServices)) { if (expectedSpatialType == this.SqlTypes.SqlGeographyType) { if (providerValueType == otherServices.SqlTypes.SqlGeographyType) { return ConvertToSqlValue(otherServices.GeographyFromProviderValue(providerValue), "providerValue"); } } else // expectedSpatialType == this.SqlTypes.SqlGeometryType { if (providerValueType == otherServices.SqlTypes.SqlGeometryType) { return ConvertToSqlValue(otherServices.GeometryFromProviderValue(providerValue), "providerValue"); } } } throw SpatialExceptions.SqlSpatialServices_ProviderValueNotSqlType(expectedSpatialType); } return providerValue; } public override DbGeographyWellKnownValue CreateWellKnownValue(DbGeography geographyValue) { geographyValue.CheckNull("geographyValue"); var spatialValue = geographyValue.AsSpatialValue(); DbGeographyWellKnownValue result = CreateWellKnownValue(spatialValue, () => SpatialExceptions.CouldNotCreateWellKnownGeographyValueNoSrid("geographyValue"), () => SpatialExceptions.CouldNotCreateWellKnownGeographyValueNoWkbOrWkt("geographyValue"), (srid, wkb, wkt) => new DbGeographyWellKnownValue() { CoordinateSystemId = srid, WellKnownBinary = wkb, WellKnownText = wkt }); return result; } public override object CreateProviderValue(DbGeometryWellKnownValue wellKnownValue) { wellKnownValue.CheckNull("wellKnownValue"); object result = null; if (wellKnownValue.WellKnownText != null) { result = this.SqlTypes.SqlTypesGeometryFromText(wellKnownValue.WellKnownText, wellKnownValue.CoordinateSystemId); } else if (wellKnownValue.WellKnownBinary != null) { result = this.SqlTypes.SqlTypesGeometryFromBinary(wellKnownValue.WellKnownBinary, wellKnownValue.CoordinateSystemId); } else { throw SpatialExceptions.WellKnownGeometryValueNotValid("wellKnownValue"); } return result; } public override DbGeometry GeometryFromProviderValue(object providerValue) { providerValue.CheckNull("providerValue"); object normalizedProviderValue = NormalizeProviderValue(providerValue, this.SqlTypes.SqlGeometryType); return this.SqlTypes.IsSqlGeometryNull(normalizedProviderValue) ? null : DbSpatialServices.CreateGeometry(this, normalizedProviderValue); } public override DbGeometryWellKnownValue CreateWellKnownValue(DbGeometry geometryValue) { geometryValue.CheckNull("geometryValue"); var spatialValue = geometryValue.AsSpatialValue(); DbGeometryWellKnownValue result = CreateWellKnownValue(spatialValue, () => SpatialExceptions.CouldNotCreateWellKnownGeometryValueNoSrid("geometryValue"), () => SpatialExceptions.CouldNotCreateWellKnownGeometryValueNoWkbOrWkt("geometryValue"), (srid, wkb, wkt) => new DbGeometryWellKnownValue() { CoordinateSystemId = srid, WellKnownBinary = wkb, WellKnownText = wkt }); return result; } void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context) { // no need to serialize anything, on de-serialization we reinitialize all fields to match the // those of Instance. } private static TValue CreateWellKnownValue(IDbSpatialValue spatialValue, Func onMissingSrid, Func onMissingWkbAndWkt, Func onValidValue) { int? srid = spatialValue.CoordinateSystemId; if (!srid.HasValue) { throw onMissingSrid(); } string wkt = spatialValue.WellKnownText; if (wkt != null) { return onValidValue(srid.Value, null, wkt); } else { byte[] wkb = spatialValue.WellKnownBinary; if (wkb != null) { return onValidValue(srid.Value, wkb, null); } } throw onMissingWkbAndWkt(); } public override string AsTextIncludingElevationAndMeasure(DbGeography geographyValue) { return this.SqlTypes.GeographyAsTextZM(geographyValue); } public override string AsTextIncludingElevationAndMeasure(DbGeometry geometryValue) { return this.SqlTypes.GeometryAsTextZM(geometryValue); } #region API used by generated spatial implementation methods #region Reflection - remove if SqlSpatialServices uses compiled expressions instead of reflection to invoke SqlGeography/SqlGeometry methods private MethodInfo FindSqlGeographyMethod(string methodName, params Type[] argTypes) { return this.SqlTypes.SqlGeographyType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance, null, argTypes, null); } private MethodInfo FindSqlGeographyStaticMethod(string methodName, params Type[] argTypes) { return this.SqlTypes.SqlGeographyType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static, null, argTypes, null); } private PropertyInfo FindSqlGeographyProperty(string propertyName) { return this.SqlTypes.SqlGeographyType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance); } private MethodInfo FindSqlGeometryStaticMethod(string methodName, params Type[] argTypes) { return this.SqlTypes.SqlGeometryType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static, null, argTypes, null); } private MethodInfo FindSqlGeometryMethod(string methodName, params Type[] argTypes) { return this.SqlTypes.SqlGeometryType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance, null, argTypes, null); } private PropertyInfo FindSqlGeometryProperty(string propertyName) { return this.SqlTypes.SqlGeometryType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance); } #endregion // #region Argument Conversion (conversion to SQL Server Types) private object ConvertToSqlValue(DbGeography geographyValue, string argumentName) { if (geographyValue == null) { return null; } return this.SqlTypes.ConvertToSqlTypesGeography(geographyValue); } private object ConvertToSqlValue(DbGeometry geometryValue, string argumentName) { if (geometryValue == null) { return null; } return this.SqlTypes.ConvertToSqlTypesGeometry(geometryValue); } private object ConvertToSqlBytes(byte[] binaryValue, string argumentName) { if (binaryValue == null) { return null; } return this.SqlTypes.SqlBytesFromByteArray(binaryValue); } private object ConvertToSqlChars(string stringValue, string argumentName) { if (stringValue == null) { return null; } return this.SqlTypes.SqlCharsFromString(stringValue); } private object ConvertToSqlString(string stringValue, string argumentName) { if (stringValue == null) { return null; } return this.SqlTypes.SqlStringFromString(stringValue); } private object ConvertToSqlXml(string stringValue, string argumentName) { if (stringValue == null) { return null; } return this.SqlTypes.SqlXmlFromString(stringValue); } #endregion #region Return Value Conversion (conversion from SQL Server types) private bool ConvertSqlBooleanToBoolean(object sqlBoolean) { return this.SqlTypes.SqlBooleanToBoolean(sqlBoolean); } private bool? ConvertSqlBooleanToNullableBoolean(object sqlBoolean) { return this.SqlTypes.SqlBooleanToNullableBoolean(sqlBoolean); } private byte[] ConvertSqlBytesToBinary(object sqlBytes) { return this.SqlTypes.SqlBytesToByteArray(sqlBytes); } private string ConvertSqlCharsToString(object sqlCharsValue) { return this.SqlTypes.SqlCharsToString(sqlCharsValue); } private string ConvertSqlStringToString(object sqlCharsValue) { return this.SqlTypes.SqlStringToString(sqlCharsValue); } private double ConvertSqlDoubleToDouble(object sqlDoubleValue) { return this.SqlTypes.SqlDoubleToDouble(sqlDoubleValue); } private double? ConvertSqlDoubleToNullableDouble(object sqlDoubleValue) { return this.SqlTypes.SqlDoubleToNullableDouble(sqlDoubleValue); } private int ConvertSqlInt32ToInt(object sqlInt32Value) { return this.SqlTypes.SqlInt32ToInt(sqlInt32Value); } private int? ConvertSqlInt32ToNullableInt(object sqlInt32Value) { return this.SqlTypes.SqlInt32ToNullableInt(sqlInt32Value); } private string ConvertSqlXmlToString(object sqlXmlValue) { return this.SqlTypes.SqlXmlToString(sqlXmlValue); } #endregion #endregion } }