Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / SqlClient / SqlSpatialServices.cs
1 //------------------------------------------------------------------------------
2 // <copyright file="SqlSpatialServices.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner  willa
7 // @backupOwner [....]
8 //------------------------------------------------------------------------------
9
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;
19
20 namespace System.Data.SqlClient
21 {
22     /// <summary>
23     /// SqlClient specific implementation of <see cref="DbSpatialServices"/>
24     /// </summary>
25     [Serializable]
26     internal sealed partial class SqlSpatialServices : DbSpatialServices, ISerializable
27     {
28         /// <summary>
29         /// Do not allow instantiation
30         /// </summary>
31         internal static readonly SqlSpatialServices Instance = new SqlSpatialServices(SqlProviderServices.GetSqlTypesAssembly);
32
33         private static Dictionary<string, SqlSpatialServices> otherSpatialServices;
34
35         [NonSerialized]
36         private readonly Singleton<SqlTypesAssembly> _sqlTypesAssemblySingleton;
37
38         private SqlSpatialServices(Func<SqlTypesAssembly> getSqlTypes)
39         {
40             Debug.Assert(getSqlTypes != null, "Validate SqlTypes assembly delegate before constructing SqlSpatialServiceS");
41             this._sqlTypesAssemblySingleton = new Singleton<SqlTypesAssembly>(getSqlTypes);
42             
43             // Create Singletons that will delay-initialize the MethodInfo and PropertyInfo instances used to invoke SqlGeography/SqlGeometry methods via reflection.
44             this.InitializeMemberInfo();
45         }
46
47         private SqlSpatialServices(SerializationInfo info, StreamingContext context)
48         {
49             SqlSpatialServices instance = Instance;
50             this._sqlTypesAssemblySingleton = instance._sqlTypesAssemblySingleton;
51             this.InitializeMemberInfo(instance);
52         }
53
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)
61         {
62             if (otherSpatialServices == null || !otherSpatialServices.TryGetValue(assembly.FullName, out services))
63             {
64                 lock (Instance)
65                 {
66                     if (otherSpatialServices == null || !otherSpatialServices.TryGetValue(assembly.FullName, out services))
67                     {
68                         SqlTypesAssembly sqlAssembly;
69                         if (SqlTypesAssembly.TryGetSqlTypesAssembly(assembly, out sqlAssembly))
70                         {
71                             if (otherSpatialServices == null)
72                             {
73                                 otherSpatialServices = new Dictionary<string, SqlSpatialServices>(1);
74                             }
75                             services = new SqlSpatialServices(() => sqlAssembly);
76                             otherSpatialServices.Add(assembly.FullName, services);
77                         }
78                         else
79                         {
80                             services = null;
81                         }
82                     }
83                 }
84             }
85             return services != null;
86         }
87
88         private SqlTypesAssembly SqlTypes { get { return this._sqlTypesAssemblySingleton.Value; } }
89                 
90         public override object CreateProviderValue(DbGeographyWellKnownValue wellKnownValue)
91         {
92             wellKnownValue.CheckNull("wellKnownValue");
93
94             object result = null;
95             if (wellKnownValue.WellKnownText != null)
96             {
97                 result = this.SqlTypes.SqlTypesGeographyFromText(wellKnownValue.WellKnownText, wellKnownValue.CoordinateSystemId);
98             }
99             else if (wellKnownValue.WellKnownBinary != null)
100             {
101                 result = this.SqlTypes.SqlTypesGeographyFromBinary(wellKnownValue.WellKnownBinary, wellKnownValue.CoordinateSystemId);
102             }
103             else
104             {
105                 throw SpatialExceptions.WellKnownGeographyValueNotValid("wellKnownValue");
106             }
107
108             return result;
109         }
110
111         public override DbGeography GeographyFromProviderValue(object providerValue)
112         {
113             providerValue.CheckNull("providerValue");
114             object normalizedProviderValue = NormalizeProviderValue(providerValue, this.SqlTypes.SqlGeographyType);
115             return this.SqlTypes.IsSqlGeographyNull(normalizedProviderValue) ? null: DbSpatialServices.CreateGeography(this, normalizedProviderValue);
116         }
117
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.
120         // 
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)
126         {
127             Debug.Assert(expectedSpatialType == this.SqlTypes.SqlGeographyType || expectedSpatialType == this.SqlTypes.SqlGeometryType);            
128             Type providerValueType = providerValue.GetType();
129             if (providerValueType != expectedSpatialType)
130             {
131                 SqlSpatialServices otherServices;
132                 if (TryGetSpatialServiceFromAssembly(providerValue.GetType().Assembly, out otherServices))
133                 {
134                     if (expectedSpatialType == this.SqlTypes.SqlGeographyType)
135                     {
136                         if (providerValueType == otherServices.SqlTypes.SqlGeographyType)
137                         {
138                             return ConvertToSqlValue(otherServices.GeographyFromProviderValue(providerValue), "providerValue");
139                         }
140                     }
141                     else // expectedSpatialType == this.SqlTypes.SqlGeometryType
142                     {
143                         if (providerValueType == otherServices.SqlTypes.SqlGeometryType)
144                         {
145                             return ConvertToSqlValue(otherServices.GeometryFromProviderValue(providerValue), "providerValue");
146                         }
147                     }
148                 }
149
150                 throw SpatialExceptions.SqlSpatialServices_ProviderValueNotSqlType(expectedSpatialType);
151             }
152
153             return providerValue;
154         }
155
156         public override DbGeographyWellKnownValue CreateWellKnownValue(DbGeography geographyValue)
157         {
158             geographyValue.CheckNull("geographyValue");
159             var spatialValue = geographyValue.AsSpatialValue();
160
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 });
165             
166             return result;
167         }
168        
169         public override object CreateProviderValue(DbGeometryWellKnownValue wellKnownValue)
170         {
171             wellKnownValue.CheckNull("wellKnownValue");
172
173             object result = null;
174             if (wellKnownValue.WellKnownText != null)
175             {
176                 result = this.SqlTypes.SqlTypesGeometryFromText(wellKnownValue.WellKnownText, wellKnownValue.CoordinateSystemId);
177             }
178             else if (wellKnownValue.WellKnownBinary != null)
179             {
180                 result = this.SqlTypes.SqlTypesGeometryFromBinary(wellKnownValue.WellKnownBinary, wellKnownValue.CoordinateSystemId);
181             }
182             else
183             {
184                 throw SpatialExceptions.WellKnownGeometryValueNotValid("wellKnownValue");
185             }
186
187             return result;
188         }
189
190         public override DbGeometry GeometryFromProviderValue(object providerValue)
191         {
192             providerValue.CheckNull("providerValue");
193             object normalizedProviderValue = NormalizeProviderValue(providerValue, this.SqlTypes.SqlGeometryType);
194             return this.SqlTypes.IsSqlGeometryNull(normalizedProviderValue) ? null : DbSpatialServices.CreateGeometry(this, normalizedProviderValue);
195         }
196
197         public override DbGeometryWellKnownValue CreateWellKnownValue(DbGeometry geometryValue)
198         {
199             geometryValue.CheckNull("geometryValue");
200             var spatialValue = geometryValue.AsSpatialValue();
201
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 });
206             
207             return result;
208         }
209
210         void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
211         {
212             // no need to serialize anything, on de-serialization we reinitialize all fields to match the 
213             // those of Instance.
214         }
215
216         private static TValue CreateWellKnownValue<TValue>(IDbSpatialValue spatialValue, Func<Exception> onMissingSrid, Func<Exception> onMissingWkbAndWkt, Func<int, byte[], string, TValue> onValidValue)
217         {
218             int? srid = spatialValue.CoordinateSystemId;
219
220             if (!srid.HasValue)
221             {
222                 throw onMissingSrid();
223             }
224
225             string wkt = spatialValue.WellKnownText;
226             if (wkt != null)
227             {
228                 return onValidValue(srid.Value, null, wkt);
229             }
230             else
231             {
232                 byte[] wkb = spatialValue.WellKnownBinary;
233                 if (wkb != null)
234                 {
235                     return onValidValue(srid.Value, wkb, null);
236                 }
237             }
238
239             throw onMissingWkbAndWkt();
240         }
241
242         public override string AsTextIncludingElevationAndMeasure(DbGeography geographyValue)
243         {
244             return this.SqlTypes.GeographyAsTextZM(geographyValue);
245         }
246         
247         public override string AsTextIncludingElevationAndMeasure(DbGeometry geometryValue)
248         {
249             return this.SqlTypes.GeometryAsTextZM(geometryValue);
250         }
251
252
253         #region API used by generated spatial implementation methods
254
255         #region Reflection - remove if SqlSpatialServices uses compiled expressions instead of reflection to invoke SqlGeography/SqlGeometry methods
256                 
257         private MethodInfo FindSqlGeographyMethod(string methodName, params Type[] argTypes)
258         {
259             return this.SqlTypes.SqlGeographyType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance, null, argTypes, null);
260         }
261
262         private MethodInfo FindSqlGeographyStaticMethod(string methodName, params Type[] argTypes)
263         {
264             return this.SqlTypes.SqlGeographyType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static, null, argTypes, null);
265         }
266
267         private PropertyInfo FindSqlGeographyProperty(string propertyName)
268         {
269             return this.SqlTypes.SqlGeographyType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
270         }
271
272         private MethodInfo FindSqlGeometryStaticMethod(string methodName, params Type[] argTypes)
273         {
274             return this.SqlTypes.SqlGeometryType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Static, null, argTypes, null);
275         }
276
277         private MethodInfo FindSqlGeometryMethod(string methodName, params Type[] argTypes)
278         {
279             return this.SqlTypes.SqlGeometryType.GetMethod(methodName, BindingFlags.Public | BindingFlags.Instance, null, argTypes, null);
280         }
281
282         private PropertyInfo FindSqlGeometryProperty(string propertyName)
283         {
284             return this.SqlTypes.SqlGeometryType.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance);
285         }
286         
287         #endregion
288
289         // 
290
291
292
293         #region Argument Conversion (conversion to SQL Server Types)
294
295         private object ConvertToSqlValue(DbGeography geographyValue, string argumentName)
296         {
297             if (geographyValue == null)
298             {
299                 return null;
300             }
301
302             return this.SqlTypes.ConvertToSqlTypesGeography(geographyValue);
303         }
304
305         private object ConvertToSqlValue(DbGeometry geometryValue, string argumentName)
306         {
307             if (geometryValue == null)
308             {
309                 return null;
310             }
311
312             return this.SqlTypes.ConvertToSqlTypesGeometry(geometryValue);
313         }
314
315         private object ConvertToSqlBytes(byte[] binaryValue, string argumentName)
316         {
317             if (binaryValue == null)
318             {
319                 return null;
320             }
321
322             return this.SqlTypes.SqlBytesFromByteArray(binaryValue);
323         }
324
325         private object ConvertToSqlChars(string stringValue, string argumentName)
326         {
327             if (stringValue == null)
328             {
329                 return null;
330             }
331
332             return this.SqlTypes.SqlCharsFromString(stringValue);
333         }
334
335         private object ConvertToSqlString(string stringValue, string argumentName)
336         {
337             if (stringValue == null)
338             {
339                 return null;
340             }
341
342             return this.SqlTypes.SqlStringFromString(stringValue);
343         }
344
345         private object ConvertToSqlXml(string stringValue, string argumentName)
346         {
347             if (stringValue == null)
348             {
349                 return null;
350             }
351
352             return this.SqlTypes.SqlXmlFromString(stringValue);
353         }
354
355         #endregion
356
357         #region Return Value Conversion (conversion from SQL Server types)
358
359         private bool ConvertSqlBooleanToBoolean(object sqlBoolean)
360         {
361             return this.SqlTypes.SqlBooleanToBoolean(sqlBoolean);
362         }
363
364         private bool? ConvertSqlBooleanToNullableBoolean(object sqlBoolean)
365         {
366             return this.SqlTypes.SqlBooleanToNullableBoolean(sqlBoolean);
367         }
368
369         private byte[] ConvertSqlBytesToBinary(object sqlBytes)
370         {
371             return this.SqlTypes.SqlBytesToByteArray(sqlBytes);
372         }
373
374         private string ConvertSqlCharsToString(object sqlCharsValue)
375         {
376             return this.SqlTypes.SqlCharsToString(sqlCharsValue);
377         }
378
379         private string ConvertSqlStringToString(object sqlCharsValue)
380         {
381             return this.SqlTypes.SqlStringToString(sqlCharsValue);
382         }
383
384         private double ConvertSqlDoubleToDouble(object sqlDoubleValue)
385         {
386             return this.SqlTypes.SqlDoubleToDouble(sqlDoubleValue);
387         }
388
389         private double? ConvertSqlDoubleToNullableDouble(object sqlDoubleValue)
390         {
391             return this.SqlTypes.SqlDoubleToNullableDouble(sqlDoubleValue);
392         }
393
394         private int ConvertSqlInt32ToInt(object sqlInt32Value)
395         {
396             return this.SqlTypes.SqlInt32ToInt(sqlInt32Value);
397         }
398
399         private int? ConvertSqlInt32ToNullableInt(object sqlInt32Value)
400         {
401             return this.SqlTypes.SqlInt32ToNullableInt(sqlInt32Value);
402         }
403
404         private string ConvertSqlXmlToString(object sqlXmlValue)
405         {
406             return this.SqlTypes.SqlXmlToString(sqlXmlValue);
407         }
408
409         #endregion
410
411         #endregion
412     }
413 }