1 //------------------------------------------------------------------------------
2 // <copyright file="SqlSpatialServices.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 //------------------------------------------------------------------------------
10 using System.Collections.Generic;
11 using System.Data.Spatial;
12 using System.Data.SqlClient.Internal;
13 using System.Data.Spatial.Internal;
14 using System.Data.Common.Utils;
15 using System.Diagnostics;
16 using System.Reflection;
17 using System.Runtime.Serialization;
18 using System.Threading;
20 namespace System.Data.SqlClient
23 /// SqlClient specific implementation of <see cref="DbSpatialServices"/>
26 internal sealed partial class SqlSpatialServices : DbSpatialServices, ISerializable
29 /// Do not allow instantiation
31 internal static readonly SqlSpatialServices Instance = new SqlSpatialServices(SqlProviderServices.GetSqlTypesAssembly);
33 private static Dictionary<string, SqlSpatialServices> otherSpatialServices;
36 private readonly Singleton<SqlTypesAssembly> _sqlTypesAssemblySingleton;
38 private SqlSpatialServices(Func<SqlTypesAssembly> getSqlTypes)
40 Debug.Assert(getSqlTypes != null, "Validate SqlTypes assembly delegate before constructing SqlSpatialServiceS");
41 this._sqlTypesAssemblySingleton = new Singleton<SqlTypesAssembly>(getSqlTypes);
43 // Create Singletons that will delay-initialize the MethodInfo and PropertyInfo instances used to invoke SqlGeography/SqlGeometry methods via reflection.
44 this.InitializeMemberInfo();
47 private SqlSpatialServices(SerializationInfo info, StreamingContext context)
49 SqlSpatialServices instance = Instance;
50 this._sqlTypesAssemblySingleton = instance._sqlTypesAssemblySingleton;
51 this.InitializeMemberInfo(instance);
54 // Given an assembly purportedly containing SqlServerTypes for spatial values, attempt to
55 // create a corersponding Sql spefic DbSpatialServices value backed by types from that assembly.
56 // Uses a dictionary to ensure that there is at most db spatial service per assembly. It's important that
57 // this be done in a way that ensures that the underlying SqlTypesAssembly value is also atomized,
58 // since that's caching compilation.
59 // Relies on SqlTypesAssembly to verify that the assembly is appropriate.
60 private static bool TryGetSpatialServiceFromAssembly(Assembly assembly, out SqlSpatialServices services)
62 if (otherSpatialServices == null || !otherSpatialServices.TryGetValue(assembly.FullName, out services))
66 if (otherSpatialServices == null || !otherSpatialServices.TryGetValue(assembly.FullName, out services))
68 SqlTypesAssembly sqlAssembly;
69 if (SqlTypesAssembly.TryGetSqlTypesAssembly(assembly, out sqlAssembly))
71 if (otherSpatialServices == null)
73 otherSpatialServices = new Dictionary<string, SqlSpatialServices>(1);
75 services = new SqlSpatialServices(() => sqlAssembly);
76 otherSpatialServices.Add(assembly.FullName, services);
85 return services != null;
88 private SqlTypesAssembly SqlTypes { get { return this._sqlTypesAssemblySingleton.Value; } }
90 public override object CreateProviderValue(DbGeographyWellKnownValue wellKnownValue)
92 wellKnownValue.CheckNull("wellKnownValue");
95 if (wellKnownValue.WellKnownText != null)
97 result = this.SqlTypes.SqlTypesGeographyFromText(wellKnownValue.WellKnownText, wellKnownValue.CoordinateSystemId);
99 else if (wellKnownValue.WellKnownBinary != null)
101 result = this.SqlTypes.SqlTypesGeographyFromBinary(wellKnownValue.WellKnownBinary, wellKnownValue.CoordinateSystemId);
105 throw SpatialExceptions.WellKnownGeographyValueNotValid("wellKnownValue");
111 public override DbGeography GeographyFromProviderValue(object providerValue)
113 providerValue.CheckNull("providerValue");
114 object normalizedProviderValue = NormalizeProviderValue(providerValue, this.SqlTypes.SqlGeographyType);
115 return this.SqlTypes.IsSqlGeographyNull(normalizedProviderValue) ? null: DbSpatialServices.CreateGeography(this, normalizedProviderValue);
118 // Ensure that provider values are from the expected version of the Sql types assembly. If they aren't try to
119 // convert them so that they are.
121 // Normally when we obtain values from the store, we try to use the appropriate SqlSpatialDataReader. This will make sure that
122 // any spatial values are instantiated with the provider type from the appropriate SqlServerTypes assembly. However,
123 // in one case (output parameter values) we don't have an opportunity to make this happen. There we get whatever value
124 // the underlying SqlDataReader produces which doesn't necessarily produce values from the assembly we expect.
125 private object NormalizeProviderValue(object providerValue, Type expectedSpatialType)
127 Debug.Assert(expectedSpatialType == this.SqlTypes.SqlGeographyType || expectedSpatialType == this.SqlTypes.SqlGeometryType);
128 Type providerValueType = providerValue.GetType();
129 if (providerValueType != expectedSpatialType)
131 SqlSpatialServices otherServices;
132 if (TryGetSpatialServiceFromAssembly(providerValue.GetType().Assembly, out otherServices))
134 if (expectedSpatialType == this.SqlTypes.SqlGeographyType)
136 if (providerValueType == otherServices.SqlTypes.SqlGeographyType)
138 return ConvertToSqlValue(otherServices.GeographyFromProviderValue(providerValue), "providerValue");
141 else // expectedSpatialType == this.SqlTypes.SqlGeometryType
143 if (providerValueType == otherServices.SqlTypes.SqlGeometryType)
145 return ConvertToSqlValue(otherServices.GeometryFromProviderValue(providerValue), "providerValue");
150 throw SpatialExceptions.SqlSpatialServices_ProviderValueNotSqlType(expectedSpatialType);
153 return providerValue;
156 public override DbGeographyWellKnownValue CreateWellKnownValue(DbGeography geographyValue)
158 geographyValue.CheckNull("geographyValue");
159 var spatialValue = geographyValue.AsSpatialValue();
161 DbGeographyWellKnownValue result = CreateWellKnownValue(spatialValue,
162 () => SpatialExceptions.CouldNotCreateWellKnownGeographyValueNoSrid("geographyValue"),
163 () => SpatialExceptions.CouldNotCreateWellKnownGeographyValueNoWkbOrWkt("geographyValue"),
164 (srid, wkb, wkt) => new DbGeographyWellKnownValue() { CoordinateSystemId = srid, WellKnownBinary = wkb, WellKnownText = wkt });
169 public override object CreateProviderValue(DbGeometryWellKnownValue wellKnownValue)
171 wellKnownValue.CheckNull("wellKnownValue");
173 object result = null;
174 if (wellKnownValue.WellKnownText != null)
176 result = this.SqlTypes.SqlTypesGeometryFromText(wellKnownValue.WellKnownText, wellKnownValue.CoordinateSystemId);
178 else if (wellKnownValue.WellKnownBinary != null)
180 result = this.SqlTypes.SqlTypesGeometryFromBinary(wellKnownValue.WellKnownBinary, wellKnownValue.CoordinateSystemId);
184 throw SpatialExceptions.WellKnownGeometryValueNotValid("wellKnownValue");
190 public override DbGeometry GeometryFromProviderValue(object providerValue)
192 providerValue.CheckNull("providerValue");
193 object normalizedProviderValue = NormalizeProviderValue(providerValue, this.SqlTypes.SqlGeometryType);
194 return this.SqlTypes.IsSqlGeometryNull(normalizedProviderValue) ? null : DbSpatialServices.CreateGeometry(this, normalizedProviderValue);
197 public override DbGeometryWellKnownValue CreateWellKnownValue(DbGeometry geometryValue)
199 geometryValue.CheckNull("geometryValue");
200 var spatialValue = geometryValue.AsSpatialValue();
202 DbGeometryWellKnownValue result = CreateWellKnownValue(spatialValue,
203 () => SpatialExceptions.CouldNotCreateWellKnownGeometryValueNoSrid("geometryValue"),
204 () => SpatialExceptions.CouldNotCreateWellKnownGeometryValueNoWkbOrWkt("geometryValue"),
205 (srid, wkb, wkt) => new DbGeometryWellKnownValue() { CoordinateSystemId = srid, WellKnownBinary = wkb, WellKnownText = wkt });
210 void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
212 // no need to serialize anything, on de-serialization we reinitialize all fields to match the
213 // those of Instance.
216 private static TValue CreateWellKnownValue<TValue>(IDbSpatialValue spatialValue, Func<Exception> onMissingSrid, Func<Exception> onMissingWkbAndWkt, Func<int, byte[], string, TValue> onValidValue)
218 int? srid = spatialValue.CoordinateSystemId;
222 throw onMissingSrid();
225 string wkt = spatialValue.WellKnownText;
228 return onValidValue(srid.Value, null, wkt);
232 byte[] wkb = spatialValue.WellKnownBinary;
235 return onValidValue(srid.Value, wkb, null);
239 throw onMissingWkbAndWkt();
242 public override string AsTextIncludingElevationAndMeasure(DbGeography geographyValue)
244 return this.SqlTypes.GeographyAsTextZM(geographyValue);
247 public override string AsTextIncludingElevationAndMeasure(DbGeometry geometryValue)
249 return this.SqlTypes.GeometryAsTextZM(geometryValue);
253 #region API used by generated spatial implementation methods
255 #region Reflection - remove if SqlSpatialServices uses compiled expressions instead of reflection to invoke SqlGeography/SqlGeometry methods
257 private MethodInfo FindSqlGeographyMethod(string methodName, params Type[] argTypes)
259 return this.SqlTypes.SqlGeographyType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance, null, argTypes, null);
262 private MethodInfo FindSqlGeographyStaticMethod(string methodName, params Type[] argTypes)
264 return this.SqlTypes.SqlGeographyType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static, null, argTypes, null);
267 private PropertyInfo FindSqlGeographyProperty(string propertyName)
269 return this.SqlTypes.SqlGeographyType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
272 private MethodInfo FindSqlGeometryStaticMethod(string methodName, params Type[] argTypes)
274 return this.SqlTypes.SqlGeometryType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static, null, argTypes, null);
277 private MethodInfo FindSqlGeometryMethod(string methodName, params Type[] argTypes)
279 return this.SqlTypes.SqlGeometryType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance, null, argTypes, null);
282 private PropertyInfo FindSqlGeometryProperty(string propertyName)
284 return this.SqlTypes.SqlGeometryType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
293 #region Argument Conversion (conversion to SQL Server Types)
295 private object ConvertToSqlValue(DbGeography geographyValue, string argumentName)
297 if (geographyValue == null)
302 return this.SqlTypes.ConvertToSqlTypesGeography(geographyValue);
305 private object ConvertToSqlValue(DbGeometry geometryValue, string argumentName)
307 if (geometryValue == null)
312 return this.SqlTypes.ConvertToSqlTypesGeometry(geometryValue);
315 private object ConvertToSqlBytes(byte[] binaryValue, string argumentName)
317 if (binaryValue == null)
322 return this.SqlTypes.SqlBytesFromByteArray(binaryValue);
325 private object ConvertToSqlChars(string stringValue, string argumentName)
327 if (stringValue == null)
332 return this.SqlTypes.SqlCharsFromString(stringValue);
335 private object ConvertToSqlString(string stringValue, string argumentName)
337 if (stringValue == null)
342 return this.SqlTypes.SqlStringFromString(stringValue);
345 private object ConvertToSqlXml(string stringValue, string argumentName)
347 if (stringValue == null)
352 return this.SqlTypes.SqlXmlFromString(stringValue);
357 #region Return Value Conversion (conversion from SQL Server types)
359 private bool ConvertSqlBooleanToBoolean(object sqlBoolean)
361 return this.SqlTypes.SqlBooleanToBoolean(sqlBoolean);
364 private bool? ConvertSqlBooleanToNullableBoolean(object sqlBoolean)
366 return this.SqlTypes.SqlBooleanToNullableBoolean(sqlBoolean);
369 private byte[] ConvertSqlBytesToBinary(object sqlBytes)
371 return this.SqlTypes.SqlBytesToByteArray(sqlBytes);
374 private string ConvertSqlCharsToString(object sqlCharsValue)
376 return this.SqlTypes.SqlCharsToString(sqlCharsValue);
379 private string ConvertSqlStringToString(object sqlCharsValue)
381 return this.SqlTypes.SqlStringToString(sqlCharsValue);
384 private double ConvertSqlDoubleToDouble(object sqlDoubleValue)
386 return this.SqlTypes.SqlDoubleToDouble(sqlDoubleValue);
389 private double? ConvertSqlDoubleToNullableDouble(object sqlDoubleValue)
391 return this.SqlTypes.SqlDoubleToNullableDouble(sqlDoubleValue);
394 private int ConvertSqlInt32ToInt(object sqlInt32Value)
396 return this.SqlTypes.SqlInt32ToInt(sqlInt32Value);
399 private int? ConvertSqlInt32ToNullableInt(object sqlInt32Value)
401 return this.SqlTypes.SqlInt32ToNullableInt(sqlInt32Value);
404 private string ConvertSqlXmlToString(object sqlXmlValue)
406 return this.SqlTypes.SqlXmlToString(sqlXmlValue);