|
//------------------------------------------------------------------------------
// <copyright file="SqlSpatialServices.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @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
{
/// <summary>
/// SqlClient specific implementation of <see cref="DbSpatialServices"/>
/// </summary>
[Serializable]
internal sealed partial class SqlSpatialServices : DbSpatialServices, ISerializable
{
/// <summary>
/// Do not allow instantiation
/// </summary>
internal static readonly SqlSpatialServices Instance = new SqlSpatialServices(SqlProviderServices.GetSqlTypesAssembly);
private static Dictionary<string, SqlSpatialServices> otherSpatialServices;
[NonSerialized]
private readonly Singleton<SqlTypesAssembly> _sqlTypesAssemblySingleton;
private SqlSpatialServices(Func<SqlTypesAssembly> getSqlTypes)
{
Debug.Assert(getSqlTypes != null, "Validate SqlTypes assembly delegate before constructing SqlSpatialServiceS");
this._sqlTypesAssemblySingleton = new Singleton<SqlTypesAssembly>(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<string, SqlSpatialServices>(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<TValue>(IDbSpatialValue spatialValue, Func<Exception> onMissingSrid, Func<Exception> onMissingWkbAndWkt, Func<int, byte[], string, TValue> 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
}
}
|