Updates referencesource to .NET 4.7
[mono.git] / mcs / class / referencesource / System.Web.Entity / System / Data / WebControls / EntityDataSourceUtil.cs
1 //---------------------------------------------------------------------
2 // <copyright file="EntityDataSourceUtil.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner       Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9 using System.Collections;
10 using System.Collections.Generic;
11 using System.Collections.ObjectModel;
12 using System.ComponentModel;
13 using System.Data;
14 using System.Data.Metadata.Edm;
15 using System.Data.Spatial;
16 using System.Diagnostics;
17 using System.Globalization;
18 using System.Linq;
19 using System.Reflection;
20
21 namespace System.Web.UI.WebControls
22 {
23     internal static class EntityDataSourceUtil
24     {
25         internal static readonly string EntitySqlElementAlias = "it";
26
27         internal static T CheckArgumentNull<T>(T value, string parameterName) where T : class
28         {
29             if (null == value)
30             {
31                 ThrowArgumentNullException(parameterName);
32             }
33             return value;
34         }
35
36
37         /// <summary>
38         /// Indicates whether the given property name exists on the result.
39         /// The result could be indicated by a wrapperCollection, an entitySet or a typeUsage,
40         /// any of which could be null.
41         /// </summary>
42         /// <param name="propertyName"></param>
43         /// <param name="wrapperCollection"></param>
44         /// <param name="entitySet"></param>
45         /// <param name="tu"></param>
46         /// <returns></returns>
47         internal static bool PropertyIsOnEntity(string propertyName, EntityDataSourceWrapperCollection wrapperCollection, EntitySet entitySet, TypeUsage tu)
48         {
49             bool propertyIsOnEntity = false;
50             if (null != wrapperCollection)
51             {
52                 // check for descriptor
53                 if (null != wrapperCollection.GetItemProperties(null).Find(propertyName, /*ignoreCase*/ false))
54                 {
55                     propertyIsOnEntity = true;
56                 }
57             }
58             if (null != tu)
59             {
60                 ReadOnlyMetadataCollection<EdmMember> members = null;
61                 switch (tu.EdmType.BuiltInTypeKind)
62                 {
63                     case BuiltInTypeKind.RowType:
64                         members = ((RowType)(tu.EdmType)).Members;
65                         break;
66                     case BuiltInTypeKind.EntityType:
67                         members = ((EntityType)(tu.EdmType)).Members;
68                         break;
69                 }
70                 if (null != members && members.Contains(propertyName))
71                 {
72                     propertyIsOnEntity = true;
73                 }
74             }
75             if (null != entitySet)
76             {
77                 if ( ((EntityType)(entitySet.ElementType)).Members.Contains(propertyName) )
78                 {
79                     propertyIsOnEntity = true;
80                 }
81             }
82             return propertyIsOnEntity;
83         }
84
85
86         /// <summary>
87         /// Returns the value set onto the Parameter named by propertyName.
88         /// If the Paramter does not have a value, it returns null.
89         /// </summary>
90         /// <param name="propertyName"></param>
91         /// <param name="parameterCollection"></param>
92         /// <param name="entityDataSource"></param>
93         /// <returns></returns>
94         internal static object GetParameterValue(string propertyName, ParameterCollection parameterCollection,
95                                                  EntityDataSource entityDataSource)
96         {
97             if (null == parameterCollection) // ParameterCollection undefined
98             {
99                 return null;
100             }
101
102             System.Collections.Specialized.IOrderedDictionary values = 
103                 parameterCollection.GetValues(entityDataSource.HttpContext, entityDataSource);
104
105             foreach (object key in values.Keys)
106             {
107                 string parameterName = key as string;
108                 if (null != parameterName && String.Equals(propertyName, parameterName, StringComparison.Ordinal))
109                 {
110                     return values[parameterName];
111                 }
112             }
113
114             return null;
115         }
116         
117
118         /// <summary>
119         /// Get the System.Web.UI.WebControls.Parameter that matches the name in the given ParameterCollection
120         /// </summary>
121         /// <param name="propertyName"></param>
122         /// <param name="parameterCollection"></param>
123         /// <returns></returns>
124         internal static Parameter GetParameter(string propertyName, ParameterCollection parameterCollection)
125         {
126             if (null == parameterCollection)
127             {
128                 return null;
129             }
130
131             foreach (Parameter p in parameterCollection)
132             {
133                 if (String.Equals(p.Name, propertyName, StringComparison.Ordinal))
134                 {
135                     return p;
136                 }
137             }
138             return null;
139         }
140
141         
142         /// <summary>
143         /// Validates that the keys in the update parameters all match property names on the entityWrapper.
144         /// </summary>
145         /// <param name="entityWrapper"></param>
146         /// <param name="parameters"></param>
147         internal static void ValidateWebControlParameterNames(EntityDataSourceWrapper entityWrapper, 
148                                                      ParameterCollection parameters,
149                                                      EntityDataSource owner)
150         {
151             Debug.Assert(null != entityWrapper, "entityWrapper should not be null");
152             if (null != parameters)
153             {
154                 PropertyDescriptorCollection entityProperties = entityWrapper.GetProperties();
155                 System.Collections.Specialized.IOrderedDictionary parmVals = parameters.GetValues(owner.HttpContext, owner);
156                 foreach (DictionaryEntry de in parmVals)
157                 {
158                     string key = de.Key as string;
159                     if (null == key || null == entityProperties.Find(key, false))
160                     {
161                         throw new InvalidOperationException(Strings.EntityDataSourceUtil_InsertUpdateParametersDontMatchPropertyNameOnEntity(key, entityWrapper.WrappedEntity.GetType().ToString()));
162                     }
163                 }
164             }
165         }
166
167
168         /// <summary>
169         /// Verifies that the query's typeusage will not result in a polymorphic result.
170         /// If the query would be restricted "is of only" using entityTypeFilter, then
171         /// this check assumes the result will not be polymorphic.
172         /// 
173         /// This method is only called if the user specifies EntitySetName and updates are enabled.
174         /// 
175         /// Does nothing for RowTypes.
176         /// </summary>
177         /// <param name="typeUsage">The TypeUsage from the query</param>
178         /// <param name="itemCollection"></param>
179         /// <returns></returns>
180         internal static void CheckNonPolymorphicTypeUsage(EntityType entityType,
181                                                           ItemCollection ocItemCollection,
182                                                           string entityTypeFilter)
183         {
184             CheckArgumentNull<ItemCollection>(ocItemCollection, "ocItemCollection");
185
186             if (String.IsNullOrEmpty(entityTypeFilter))
187             {
188                 List<EdmType> types = new List<EdmType>(EntityDataSourceUtil.GetTypeAndSubtypesOf(entityType, ocItemCollection, /*includeAbstractTypes*/true));
189                 if (entityType.BaseType != null ||
190                     types.Count() > 1 || entityType.Abstract)
191                 {
192                     throw new InvalidOperationException(Strings.EntityDataSourceUtil_EntityQueryCannotReturnPolymorphicTypes);
193                 }
194             }
195
196             return;
197         }
198
199         internal static IEnumerable<EdmType> GetTypeAndSubtypesOf(EntityType type, ReadOnlyCollection<GlobalItem> itemCollection, bool includeAbstractTypes)
200         {
201             if (includeAbstractTypes || !type.Abstract)
202             {
203                 yield return type;
204             }
205
206             // Get entity sub-types
207             foreach (EdmType subType in GetTypeAndSubtypesOf<EntityType>(type, itemCollection, includeAbstractTypes))
208             {
209                 yield return subType;
210             }
211
212             // Get complex sub-types
213             foreach (EdmType subType in GetTypeAndSubtypesOf<ComplexType>(type, itemCollection, includeAbstractTypes))
214             {
215                 yield return subType;
216             }
217         }
218
219         internal static bool IsTypeOrSubtypeOf(EntityType superType, EntityType derivedType, ReadOnlyCollection<GlobalItem> itemCollection)
220         {
221             IEnumerable types = GetTypeAndSubtypesOf(superType, itemCollection, false);
222             foreach(EdmType type in types)
223             {
224                 if (type == derivedType)
225                 {
226                     return true;
227                 }
228             }
229             return false;
230         }
231
232         internal static Type GetClrType(MetadataWorkspace ocWorkspace, StructuralType edmType)
233         {
234             var oSpaceType = (StructuralType)ocWorkspace.GetObjectSpaceType(edmType);
235             var objectItemCollection = (ObjectItemCollection)(ocWorkspace.GetItemCollection(DataSpace.OSpace));
236             return objectItemCollection.GetClrType(oSpaceType);
237         }
238
239         internal static Type GetClrType(MetadataWorkspace ocWorkspace, EnumType edmType)
240         {
241             var oSpaceType = (EnumType)ocWorkspace.GetObjectSpaceType(edmType);
242             var objectItemCollection = (ObjectItemCollection)(ocWorkspace.GetItemCollection(DataSpace.OSpace));
243             return objectItemCollection.GetClrType(oSpaceType);
244         }
245
246         internal static ConstructorInfo GetConstructorInfo(Type type)
247         {
248             Debug.Assert(null != type, "type required");
249             ConstructorInfo constructorInfo = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.CreateInstance, null, System.Type.EmptyTypes, null);
250
251             if (null == constructorInfo)
252             {
253                 throw new InvalidOperationException(Strings.DefaultConstructorNotFound(type));
254             }
255
256             return constructorInfo;
257         }
258
259         internal static PropertyInfo GetPropertyInfo(Type type, string name)
260         {
261             Debug.Assert(null != type, "type required");
262             Debug.Assert(null != name, "name required");
263
264             PropertyInfo propertyInfo = type.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, null, Type.EmptyTypes, null);
265
266             if (null == propertyInfo)
267             {
268                 throw new InvalidOperationException(Strings.PropertyNotFound(name, type));
269             }
270
271             return propertyInfo;
272         }
273
274         internal static object InitializeType(Type type)
275         {
276             ConstructorInfo constructorInfo = GetConstructorInfo(type);
277             return constructorInfo.Invoke(new object[] { });
278         }
279
280         /// <summary>
281         /// Given a data source column name, returns the corresponding Entity-SQL. If
282         /// we are using the wrapper, we defer to the property descriptor to get
283         /// the string. If there is no wrapper (or no corresponding property descriptor)
284         /// we use the column name directly.
285         /// </summary>
286         /// <param name="columnName">Column name for which we produce a value expression.</param>
287         /// <returns>Entity-SQL for column.</returns>
288         internal static string GetEntitySqlValueForColumnName(string columnName, EntityDataSourceWrapperCollection wrapperCollection)
289         {
290             Debug.Assert(!String.IsNullOrEmpty(columnName), "columnName must be given");
291
292             string result = null;
293
294             if (wrapperCollection != null)
295             {
296                 // use wrapper definition if it is available
297                 EntityDataSourceWrapperPropertyDescriptor descriptor =
298                     wrapperCollection.GetItemProperties(null).Find(columnName, false) as EntityDataSourceWrapperPropertyDescriptor;
299                 if (null != descriptor)
300                 {
301                     result = descriptor.Column.GetEntitySqlValue();
302                 }
303             }
304
305             // if descriptor does not provide SQL, create the default: it._columnName_
306             if (null == result)
307             {
308                 result = EntitySqlElementAlias + "." + QuoteEntitySqlIdentifier(columnName);
309             }
310
311             return result;
312         }
313
314         internal static Type ConvertTypeCodeToType(TypeCode typeCode)
315         {
316             switch (typeCode)
317             {
318                 case TypeCode.Boolean: 
319                     return typeof(Boolean); 
320
321                 case TypeCode.Byte:
322                     return typeof(Byte);
323
324                 case TypeCode.Char:
325                     return typeof(Char);
326
327                 case TypeCode.DateTime:
328                     return typeof(DateTime);
329
330                 case TypeCode.DBNull:
331                     return typeof(DBNull);
332
333                 case TypeCode.Decimal:
334                     return typeof(Decimal);
335
336                 case TypeCode.Double:
337                     return typeof(Double);
338
339                 case TypeCode.Empty:
340                     return null;
341
342                 case TypeCode.Int16:
343                     return typeof(Int16);
344
345                 case TypeCode.Int32:
346                     return typeof(Int32);
347
348                 case TypeCode.Int64:
349                     return typeof(Int64);
350
351                 case TypeCode.Object:
352                     return typeof(Object);
353
354                 case TypeCode.SByte:
355                     return typeof(SByte);
356
357                 case TypeCode.Single:
358                     return typeof(Single);
359
360                 case TypeCode.String:
361                     return typeof(String);
362
363                 case TypeCode.UInt16:
364                     return typeof(UInt16);
365
366                 case TypeCode.UInt32:
367                     return typeof(UInt32);
368
369                 case TypeCode.UInt64:
370                     return typeof(UInt64);
371
372                 default:
373                     throw new InvalidOperationException(Strings.EntityDataSourceUtil_UnableToConvertTypeCodeToType(typeCode.ToString()));
374             }
375         }
376
377         /// <summary>
378         /// Converts a DB type code to a CLR type, bypassing CLR type codes since there
379         /// is not a sufficient mapping.
380         /// </summary>
381         /// <param name="dbType">The DB type to convert</param>
382         /// <returns>The mapped CLR type</returns>
383         internal static Type ConvertDbTypeToType(DbType dbType)
384         {
385             switch (dbType)
386             {
387                 case DbType.AnsiString:
388                 case DbType.AnsiStringFixedLength:
389                 case DbType.String:
390                 case DbType.StringFixedLength:
391                     return typeof(String);
392                 case DbType.Boolean:
393                     return typeof(Boolean);
394                 case DbType.Byte:
395                     return typeof(Byte);
396                 case DbType.VarNumeric:     // 
397                 case DbType.Currency:
398                 case DbType.Decimal:
399                     return typeof(Decimal);
400                 case DbType.Date:
401                 case DbType.DateTime:
402                 case DbType.DateTime2: // new Katmai type
403                     return typeof(DateTime);
404                 case DbType.Time:      // new Katmai type
405                     return typeof(TimeSpan);
406                 case DbType.Double:
407                     return typeof(Double);
408                 case DbType.Int16:
409                     return typeof(Int16);
410                 case DbType.Int32:
411                     return typeof(Int32);
412                 case DbType.Int64:
413                     return typeof(Int64);
414                 case DbType.SByte:
415                     return typeof(SByte);
416                 case DbType.Single:
417                     return typeof(Single);
418                 case DbType.UInt16:
419                     return typeof(UInt16);
420                 case DbType.UInt32:
421                     return typeof(UInt32);
422                 case DbType.UInt64:
423                     return typeof(UInt64);
424                 case DbType.Guid:
425                     return typeof(Guid);
426                 case DbType.DateTimeOffset: // new Katmai type
427                     return typeof(DateTimeOffset);
428                 case DbType.Binary:
429                     return typeof(byte[]);
430                 case DbType.Object:
431                 default:
432                     return typeof(Object);
433             }
434         }
435
436         private static IEnumerable<EdmType> GetTypeAndSubtypesOf<T_EdmType>(EdmType type, ReadOnlyCollection<GlobalItem> itemCollection, bool includeAbstractTypes)
437             where T_EdmType : EdmType
438         {
439             // Get the subtypes of the type from the WorkSpace
440             T_EdmType specificType = type as T_EdmType;
441             if (specificType != null)
442             {
443
444                 IEnumerable<T_EdmType> typesInWorkSpace = itemCollection.OfType<T_EdmType>();
445                 foreach (T_EdmType typeInWorkSpace in typesInWorkSpace)
446                 {
447                     if (specificType.Equals(typeInWorkSpace) == false && IsStrictSubtypeOf(typeInWorkSpace, specificType))
448                     {
449                         if (includeAbstractTypes || !typeInWorkSpace.Abstract)
450                         {
451                             yield return typeInWorkSpace;
452                         }
453
454                     }
455                 }
456             }
457             yield break;
458         }
459
460         // requires: firstType is not null
461         // effects: if otherType is among the base types, return true, 
462         // otherwise returns false.
463         // when othertype is same as the current type, return false.
464         private static bool IsStrictSubtypeOf(EdmType firstType, EdmType secondType)
465         {
466             Debug.Assert(firstType != null, "firstType should not be not null");
467             if (secondType == null)
468             {
469                 return false;
470             }
471
472             // walk up my type hierarchy list
473             for (EdmType t = firstType.BaseType; t != null; t = t.BaseType)
474             {
475                 if (t == secondType)
476                     return true;
477             }
478             return false;
479         }
480
481         internal static bool NullCanBeAssignedTo(Type type)
482         {
483             Debug.Assert(null != type, "type required");
484             return !type.IsValueType || IsNullableType(type, out type);
485         }
486
487         internal static bool IsNullableType(Type type, out Type underlyingType)
488         {
489             Debug.Assert(null != type, "type required");
490             if (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
491             {
492                 underlyingType = type.GetGenericArguments()[0];
493                 return true;
494             }
495             underlyingType = null;
496             return false;
497         }
498
499         internal static void ThrowArgumentNullException(string parameterName)
500         {
501             throw ArgumentNull(parameterName);
502         }
503
504         internal static ArgumentNullException ArgumentNull(string parameter)
505         {
506             ArgumentNullException e = new ArgumentNullException(parameter);
507             return e;
508         }
509
510         internal static object ConvertType(object value, Type type, string paramName)
511         {
512             // NOTE: This method came from ObjectDataSource via LinqDataSource.
513             // It has been changed to support better parsing of decimal values.
514             string s = value as string;
515             if (s != null)
516             {
517                 // Get the type converter for the destination type
518                 TypeConverter converter = TypeDescriptor.GetConverter(type);
519                 if (converter != null)
520                 {
521                     // Perform the conversion
522                     try
523                     {
524                         // If the requested type is decimal or a spatial type, then first try to parse the string value.
525                         // For decimal values we use the Decimal parsing which is able to handle comma thousands separators
526                         // For spatial types we understand the string value returned in the format the .ToString() method 
527                         // on DbGeometry or DbGeography would return it. If this doesn't work, or the requested value 
528                         // is not decimal/DbGeometry/DbGeography, then we fall back on the type converter mechanism.
529                         decimal decimalResult;
530                         DbGeography geographyResult;
531                         DbGeometry geometryResult;
532                         if (type.IsAssignableFrom(typeof(Decimal)) && Decimal.TryParse(s, out decimalResult))
533                         {
534                             value = decimalResult;
535                         }
536                         else if (type.IsAssignableFrom(typeof(DbGeography)) && TryParseGeography(s, out geographyResult))
537                         {
538                             value = geographyResult;
539                         }
540                         else if (type.IsAssignableFrom(typeof(DbGeometry)) && TryParseGeometry(s, out geometryResult))
541                         {
542                             value = geometryResult;
543                         }
544                         else
545                         {
546                             value = converter.ConvertFromString(s);
547                         }
548                     }
549                     catch (Exception) // ConvertFromString sometimes throws exceptions of actual type Exception!
550                     {
551                         // For Nullable types, we just get the type parameter since that makes a more readable exception message
552                         string typeName;
553                         if (type.IsGenericType && typeof(Nullable<>).IsAssignableFrom(type.GetGenericTypeDefinition()) && !type.ContainsGenericParameters)
554                         {
555                             Type[] types = type.GetGenericArguments();
556                             Debug.Assert(types != null && types.Length == 1, "Nullable did not have a single generic type.");
557                             typeName = types[0].FullName;
558                         }
559                         else
560                         {
561                             typeName = type.FullName;
562                         }
563                         throw new InvalidOperationException(Strings.EntityDataSourceUtil_UnableToConvertStringToType(paramName, typeName));
564                     }
565                 }
566             }
567
568             // values for enum properties need to be cast to make sure nullable enums will work
569             Type underlyingType = null;
570             if (value != null && (type.IsEnum || (IsNullableType(type, out underlyingType) && underlyingType.IsEnum)))
571             {
572                 value = Enum.ToObject(underlyingType ?? type, value);
573             }
574
575             return value;
576         }
577
578         /// <summary>
579         /// Converts the string representation to DbGeography instance. A return value indicates if the conversion succeeded.
580         /// </summary>
581         /// <param name="stringValue">A geography string to convert.</param>
582         /// <param name="result">If the conversion succeeds an instance of DbGeometry type created from <paramref name="result"/>; otherwise null.</param>
583         /// <returns>true if <paramref name="stringValue"/> was converted successfully; otherwise false;</returns>
584         /// <remarks>The <paramref name="stringValue"/> must be in the format returned by <see cref="DbGeometry.AsText()"/> method.</remarks>
585         private static bool TryParseGeography(string stringValue, out DbGeography result)
586         {
587             return TryParseGeo<DbGeography>(stringValue, (geometryText, srid) => DbGeography.FromText(geometryText, srid), out result);
588         }
589
590         /// <summary>
591         /// Converts the string representation to DbGeometry instance. A return value indicates if the conversion succeeded.
592         /// </summary>
593         /// <param name="stringValue">A geometry string to convert.</param>
594         /// <param name="result">If the conversion succeeds an instance of DbGeometry type created from <paramref name="result"/>; otherwise null.</param>
595         /// <returns>true if <paramref name="stringValue"/> was converted successfully; otherwise false;</returns>
596         /// <remarks>The <paramref name="stringValue"/> must be in the format returned by <see cref="DbGeometry.ToString()"/> method.</remarks>
597         private static bool TryParseGeometry(string stringValue, out DbGeometry result)
598         {
599             return TryParseGeo<DbGeometry>(stringValue, (geometryText, srid) => DbGeometry.FromText(geometryText, srid), out result);
600         }
601
602         /// <summary>
603         /// Converts the string representation to DbGeometry or DbGeography instance. A return value indicates if the conversion succeeded.
604         /// </summary>
605         /// <typeparam name="T">Type to convert the <paramref name="stringValue"/>. Must be either DbGeometry or DbGeography.</typeparam>
606         /// <param name="stringValue">A geometry string to convert.</param>
607         /// <param name="createSpatialTypeInstanceFunc">Function invoked to create an instance of type T given SRID and geo text.</param>
608         /// <param name="result">If the conversion succeeds an instance of DbGeometry or DbGeography type created from <paramref name="result"/>; otherwise null.</param>
609         /// <returns>true if <paramref name="stringValue"/> was converted successfully; otherwise false;</returns>
610         /// <remarks>The <paramref name="stringValue"/> must be in the format returned by .ToString() method of T.</remarks>
611         private static bool TryParseGeo<T>(string stringValue, Func<string, int, T> createSpatialTypeInstanceFunc, out T result)
612             where T : class
613         {
614             Debug.Assert(typeof(DbGeography).IsAssignableFrom(typeof(T)) || typeof(DbGeometry).IsAssignableFrom(typeof(T)), "This method should be called only for spatial type");
615             Debug.Assert(createSpatialTypeInstanceFunc != null, "createSpatialTypeInstanceFunc != null");
616             Debug.Assert(stringValue != null, "stringValue != null");
617
618             int srid;
619             string geometryText;
620
621             if (TryParseSpatialString(stringValue, out srid, out geometryText))
622             {
623                 try
624                 {
625                     result = createSpatialTypeInstanceFunc(geometryText, srid) as T;                    
626                     return true;
627                 }
628                 catch(Exception ex)
629                 { 
630                     if(!IsCatchableExceptionType(ex))
631                     {
632                         throw;
633                     }
634                 }
635             }
636
637             result = null;
638             return false;
639         }
640
641         /// <summary>
642         /// Retrieves SRID and geo text from <paramref name="stringValue"/> in format "SRID=4326;POINT (100 100)" .
643         /// </summary>
644         /// <param name="stringValue">String to parse.</param>
645         /// <param name="srid">SRID retrieved from <paramref name="stringValue"/>.</param>
646         /// <param name="geoText">Geo text retrieved from <paramref name="stringValue"/>.</param>
647         /// <returns>true if it was possible to retrieve both SRID and geo text; otherwise false.</returns>
648         private static bool TryParseSpatialString(string stringValue, out int srid, out string geoText)
649         {
650             Debug.Assert(stringValue != null, "stringValue != null");
651
652             string[] components = stringValue.Split(';');
653
654             // expected 2 semicolon separated components - SRID and well known text
655             if (components.Length == 2)
656             {
657                 if (components[0].StartsWith("SRID=", StringComparison.Ordinal))
658                 {
659                     if (int.TryParse(components[0].Substring("SRID=".Length), NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out srid))
660                     {
661                         geoText = components[1];
662                         return true;
663                     }
664                 }
665             }
666
667             srid = int.MinValue;
668             geoText = null;
669
670             return false;
671         }
672
673         internal static void SetAllPropertiesWithVerification(EntityDataSourceWrapper entityWrapper, 
674                                                               Dictionary<string, object> changedProperties, 
675                                                               bool overwrite)
676         {
677             Dictionary<string, Exception> exceptions = null;
678             entityWrapper.SetAllProperties(changedProperties, /*overwriteSameValue*/true, ref exceptions);
679
680             if (null != exceptions)
681             {
682                 // The EntityDataSourceValidationException has a property "InnerExceptions" that encapsulates
683                 // all of the failed property setters. The message from one of those errors is surfaced so that it
684                 // appears on the web page as a human-readable error like:
685                 //   "Error while setting property 'PropertyName': 'The value cannot be null.'."
686                 string key = exceptions.Keys.First();
687                 throw new EntityDataSourceValidationException(
688                     Strings.EntityDataSourceView_DataConversionError(
689                         key, exceptions[key].Message), exceptions);
690             }
691         }
692
693         /// <summary>
694         /// Get the Clr type for the primitive enum or complex type member. The member must not be null.
695         /// </summary>
696         internal static Type GetMemberClrType(MetadataWorkspace ocWorkspace, EdmMember member)
697         {
698             EntityDataSourceUtil.CheckArgumentNull(member, "member");
699
700             EdmType memberType = member.TypeUsage.EdmType;
701
702             Debug.Assert(EntityDataSourceUtil.IsScalar(memberType) ||
703                 memberType.BuiltInTypeKind == BuiltInTypeKind.ComplexType ||
704                 memberType.BuiltInTypeKind == BuiltInTypeKind.EntityType, "member type must be primitive, enum, entity or complex type");
705
706             Type clrType;
707
708             if (EntityDataSourceUtil.IsScalar(memberType))
709             {
710                 clrType = memberType.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType ? 
711                             ((PrimitiveType)memberType).ClrEquivalentType : 
712                             GetClrType(ocWorkspace, (EnumType)memberType);
713
714                 if (!NullCanBeAssignedTo(clrType))
715                 {
716                     Facet facet;
717                     if (member.TypeUsage.Facets.TryGetValue("Nullable", true, out facet))
718                     {
719                         if ((bool)facet.Value)
720                         {
721                             clrType = MakeNullable(clrType);
722                         }
723                     }
724                 }
725             }
726             else
727             {
728                 Debug.Assert(
729                     memberType.BuiltInTypeKind == BuiltInTypeKind.EntityType || memberType.BuiltInTypeKind == BuiltInTypeKind.ComplexType,
730                     "Complex or Entity type expected");
731
732                 clrType = GetClrType(ocWorkspace, (StructuralType)memberType);
733             }
734
735             return clrType;
736         }
737
738         internal static Type MakeNullable(Type type)
739         {
740             if (!NullCanBeAssignedTo(type))
741             {
742                 type = typeof(Nullable<>).MakeGenericType(type);
743             }
744             return type;
745         }
746         
747         /// <summary>
748         /// Returns the collection of AssociationSetEnds for the relationships for this entity
749         /// </summary>
750         /// <param name="entitySet"></param>
751         /// <param name="entityType"></param>
752         /// <param name="forKey">If true, returns only the other ends with multiplicity 1. Ignores 1:0..1 relationships.</param>
753         /// <returns></returns>
754         internal static IEnumerable<AssociationSetEnd> GetReferenceEnds(EntitySet entitySet, EntityType entityType, bool forKey)
755         {
756             foreach (AssociationSet associationSet in entitySet.EntityContainer.BaseEntitySets.OfType<AssociationSet>())
757             {
758                 Debug.Assert(associationSet.AssociationSetEnds.Count == 2, "non binary association?");
759                 AssociationSetEnd firstEnd = associationSet.AssociationSetEnds[0];
760                 AssociationSetEnd secondEnd = associationSet.AssociationSetEnds[1];
761
762                 // If both ends match, then we will return both ends
763                 if (IsReferenceEnd(entitySet, entityType, firstEnd, secondEnd, forKey))
764                 {
765                     yield return secondEnd;
766                 }
767                 if (IsReferenceEnd(entitySet, entityType, secondEnd, firstEnd, forKey))
768                 {
769                     yield return firstEnd;
770                 }
771             }
772         }
773
774         /// <summary>
775         /// Determine if the end is 'contained' in the source entity via a referential integrity constraint (e.g.,
776         /// in a relationship from OrderDetail to Order where OrderDetail has the OrderId property, the association set end
777         /// is contained in the order detail entity)
778         /// </summary>
779         private static bool IsContained(AssociationSetEnd end, out ReferentialConstraint constraint)
780         {
781             CheckArgumentNull(end, "end");
782
783             AssociationEndMember endMember = end.CorrespondingAssociationEndMember;
784             AssociationType associationType = (AssociationType)endMember.DeclaringType;
785
786             constraint = null;
787             bool result = false;
788
789             if (null != associationType.ReferentialConstraints)
790             {
791                 foreach (ReferentialConstraint candidate in associationType.ReferentialConstraints)
792                 {
793                     if (candidate.FromRole.Name == endMember.Name)
794                     {
795                         constraint = candidate;
796                         result = true;
797                         break;
798                     }
799                 }
800             }
801
802             return result;
803         }
804
805         internal static bool TryGetCorrespondingNavigationProperty(AssociationEndMember end, out NavigationProperty navigationProperty)
806         {
807             EntityType entityType = GetEntityType(GetOppositeEnd(end));
808
809             // if there is a corresponding navigation property, use its name as the prefix
810             navigationProperty = entityType.NavigationProperties
811                 .Where(np => np.ToEndMember == end)
812                 .SingleOrDefault(); // metadata is supposed to ensure this is non-ambiguous
813             return null != navigationProperty;
814         }
815
816         internal static AssociationEndMember GetOppositeEnd(AssociationEndMember end)
817         {
818             return (AssociationEndMember)end.DeclaringType.Members.Where(m => m != end).Single();
819         }
820
821         /// <summary>
822         /// A navigation ('fromEnd' -> 'toEnd') defines a reference end for 'entitySet' and 'entityType' if it 
823         /// has multiplicity 0..1 or 1..1, is bound to the set, and has the appropriate type.
824         /// 
825         /// We omit 1..1:0..1 navigations assuming that the opposite end owns the relationship (since the foreign 
826         /// key would need to point in the opposite direction.)
827         /// </summary>
828         private static bool IsReferenceEnd(EntitySet entitySet, EntityType entityType, AssociationSetEnd fromEnd, AssociationSetEnd toEnd, bool forKey)
829         {
830             EntityType fromType = GetEntityType(fromEnd);
831
832             if (fromEnd.EntitySet == entitySet && (IsStrictSubtypeOf(entityType, fromType) || entityType == fromType))
833             {
834                 RelationshipMultiplicity fromMult = fromEnd.CorrespondingAssociationEndMember.RelationshipMultiplicity;
835                 RelationshipMultiplicity toMult = toEnd.CorrespondingAssociationEndMember.RelationshipMultiplicity;
836
837                 // If forKey is false (we are testing to see if this is a far end for a reference, not a key)
838                 //   then fromMult is ignored and all far-end 1 or 0..1 multiplicity ends are exposed.
839                 // If forKey is true, then we are asking about a reference end for the purpose of flattening.
840                 //   We do not flatten 1:0..1 relationships because of a limitation in the EDM.
841                 if (toMult == RelationshipMultiplicity.One ||
842                     (toMult == RelationshipMultiplicity.ZeroOrOne && (!forKey || fromMult != RelationshipMultiplicity.One) ))
843                 {
844                     return true;
845                 }
846             }
847
848             return false;
849         }
850
851         internal static bool IsScalar(EdmType type)
852         {
853             return type.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType ||
854                     type.BuiltInTypeKind == BuiltInTypeKind.EnumType;
855         }
856
857         internal static EntityType GetEntityType(AssociationSetEnd end)
858         {
859             return GetEntityType(end.CorrespondingAssociationEndMember);
860         }
861
862         internal static EntityType GetEntityType(AssociationEndMember end)
863         {
864             EntityType entityType = (EntityType)((RefType)end.TypeUsage.EdmType).ElementType;
865             return entityType;
866         }
867
868
869         internal static string GetQualifiedEntitySetName(EntitySet entitySet)
870         {
871             EntityDataSourceUtil.CheckArgumentNull(entitySet, "entitySet");
872             // ContainerName.EntitySetName
873             return entitySet.EntityContainer.Name + "." + entitySet.Name;
874         }
875
876         internal static string QuoteEntitySqlIdentifier(string identifier)
877         {
878             return "[" + (identifier ?? string.Empty).Replace("]", "]]") + "]";
879         }
880
881         internal static string CreateEntitySqlTypeIdentifier(EdmType type)
882         {
883             // [_schema_namespace_name_].[_type_name_]
884             // if the [_schema_namespace_name_] is null or empty, omit this part of the identifier
885             // this can happen when the CLR type is defined outside of a namespace
886             return (String.IsNullOrEmpty(type.NamespaceName) ? String.Empty : (QuoteEntitySqlIdentifier(type.NamespaceName) + "."))
887                 + QuoteEntitySqlIdentifier(type.Name);
888         }
889
890         internal static string CreateEntitySqlSetIdentifier(EntitySetBase set)
891         {
892             // [_container_name_].[_set_name_]
893             return QuoteEntitySqlIdentifier(set.EntityContainer.Name) + "." + QuoteEntitySqlIdentifier(set.Name);
894         }
895
896         /// <summary>
897         /// Determines which columns to expose for the given set and type. Includes
898         /// flattened complex properties and 'reference' keys.
899         /// </summary>
900         /// <param name="csWorkspace">Used to determine 'interesting' members, or
901         /// members whose values need to be maintained in ControlState</param>
902         /// <param name="ocWorkspace">Used to get CLR mapping information for EDM
903         /// types</param>
904         /// <param name="entitySet">The set.</param>
905         /// <param name="entityType">The type.</param>
906         /// <returns>A map from display names to columns.</returns>
907         internal static ReadOnlyCollection<EntityDataSourceColumn> GetNamedColumns(MetadataWorkspace csWorkspace, MetadataWorkspace ocWorkspace,
908             EntitySet entitySet, EntityType entityType)
909         {
910             CheckArgumentNull(csWorkspace, "csWorkspace");
911             CheckArgumentNull(ocWorkspace, "ocWorkspace");
912             CheckArgumentNull(entitySet, "entitySet");
913             CheckArgumentNull(entityType, "entityType");
914
915             ReadOnlyCollection<EdmMember> interestingMembers = GetInterestingMembers(csWorkspace, entitySet, entityType);
916
917             IEnumerable<EntityDataSourceColumn> columns = GetColumns(entitySet, entityType, ocWorkspace, interestingMembers);
918             List<EntityDataSourceColumn> result = new List<EntityDataSourceColumn>();
919
920             // give precedence to simple named columns (
921
922             HashSet<string> usedNames = new HashSet<string>();
923             foreach (EntityDataSourceColumn column in columns)
924             {
925                 if (!column.IsHidden)
926                 {
927                     // check that the column name has not been used
928                     if (!usedNames.Add(column.DisplayName))
929                     {
930                         throw new InvalidOperationException(Strings.DisplayNameCollision(column.DisplayName));
931                     }
932                 }
933                 result.Add(column);
934             }
935
936             return result.AsReadOnly();
937         }
938
939         private static ReadOnlyCollection<EdmMember> GetInterestingMembers(MetadataWorkspace csWorkspace, EntitySet entitySet, EntityType entityType)
940         {
941             // Note that this delegate is not used to determine whether reference columns are interesting. They
942             // are intrinsically interesting and do not appear in this set.
943             HashSet<EdmMember> interestingMembers = new HashSet<EdmMember>(
944                 csWorkspace.GetRelevantMembersForUpdate(entitySet, entityType, true));
945
946             // keys are also interesting...
947             foreach (EdmMember keyMember in entityType.KeyMembers)
948             {
949                 interestingMembers.Add(keyMember);
950             }
951
952             ReadOnlyCollection<EdmMember> result = interestingMembers.ToList().AsReadOnly();
953
954             return result;
955         }
956
957         private static IEnumerable<EntityDataSourceColumn> GetColumns(EntitySet entitySet, EntityType entityType,
958             MetadataWorkspace ocWorkspace, ReadOnlyCollection<EdmMember> interestingMembers)
959         {
960             List<EntityDataSourceColumn> columns = new List<EntityDataSourceColumn>();
961
962             // Primitive and complex properties
963             EntityDataSourceMemberPath parent = null; // top-level properties are not qualified
964             Dictionary<EdmProperty, EntityDataSourcePropertyColumn> entityProperties = AddPropertyColumns(columns, ocWorkspace, parent, entityType.Properties, interestingMembers);
965
966             // Navigation reference properties
967             AddReferenceNavigationColumns(columns, ocWorkspace, entitySet, entityType);
968
969             // Reference key properties
970             AddReferenceKeyColumns(columns, ocWorkspace, entitySet, entityType, entityProperties);
971
972             return columns;
973         }
974
975         // Adds element to 'columns' for every element of 'properties'. Also returns a map from properties
976         // at this level to the corresponding columns.
977         private static Dictionary<EdmProperty, EntityDataSourcePropertyColumn> AddPropertyColumns(List<EntityDataSourceColumn> columns, MetadataWorkspace ocWorkspace, EntityDataSourceMemberPath parent, IEnumerable<EdmProperty> properties, ReadOnlyCollection<EdmMember> interestingMembers)
978         {
979             Dictionary<EdmProperty, EntityDataSourcePropertyColumn> result = new Dictionary<EdmProperty, EntityDataSourcePropertyColumn>();
980
981             foreach (EdmProperty property in properties)
982             {
983                 bool isLocallyInteresting = interestingMembers.Contains(property);
984
985                 EntityDataSourceMemberPath prefix = new EntityDataSourceMemberPath(ocWorkspace, parent, property, isLocallyInteresting);
986                 EdmType propertyType = property.TypeUsage.EdmType;
987
988                 // add column for this entity property
989                 EntityDataSourcePropertyColumn propertyColumn = new EntityDataSourcePropertyColumn(prefix);
990                 columns.Add(propertyColumn);
991                 result.Add(property, propertyColumn);
992
993                 if (propertyType.BuiltInTypeKind == BuiltInTypeKind.ComplexType)
994                 {
995                     // add nested properties
996                     // prepend the property name to the members of the complex type
997                     AddPropertyColumns(columns, ocWorkspace, prefix, ((ComplexType)propertyType).Properties, interestingMembers);
998                 }
999                 // other property types are not currently supported (or possible in EF V1 for that matter)
1000             }
1001
1002             return result;
1003         }
1004
1005         private static void AddReferenceNavigationColumns(List<EntityDataSourceColumn> columns, MetadataWorkspace ocWorkspace, EntitySet entitySet, EntityType entityType)
1006         {
1007             foreach (AssociationSetEnd toEnd in GetReferenceEnds(entitySet, entityType, /*forKey*/false))
1008             {
1009                 // Check for a navigation property
1010                 NavigationProperty navigationProperty;
1011                 if (TryGetCorrespondingNavigationProperty(toEnd.CorrespondingAssociationEndMember, out navigationProperty))
1012                 {
1013                     Type clrToType = EntityDataSourceUtil.GetMemberClrType(ocWorkspace, navigationProperty);
1014                     EntityDataSourceReferenceValueColumn column = EntityDataSourceReferenceValueColumn.Create(clrToType, ocWorkspace, navigationProperty);
1015                     columns.Add(column);
1016                 }
1017             }
1018         }
1019
1020         private static void AddReferenceKeyColumns(List<EntityDataSourceColumn> columns, MetadataWorkspace ocWorkspace, EntitySet entitySet, EntityType entityType, Dictionary<EdmProperty, EntityDataSourcePropertyColumn> entityProperties)
1021         {
1022             foreach (AssociationSetEnd toEnd in GetReferenceEnds(entitySet, entityType, /*forKey*/true))
1023             {
1024                 ReferentialConstraint constraint;
1025                 bool isContained = EntityDataSourceUtil.IsContained(toEnd, out constraint);
1026
1027                 // Create a group for the end columns
1028                 EntityType toType = EntityDataSourceUtil.GetEntityType(toEnd);
1029                 Type clrToType = EntityDataSourceUtil.GetClrType(ocWorkspace, toType);
1030                 
1031                 EntityDataSourceReferenceGroup group = EntityDataSourceReferenceGroup.Create(clrToType, toEnd);
1032
1033                 // Create a column for every key
1034                 foreach (EdmProperty keyMember in GetEntityType(toEnd).KeyMembers)
1035                 {
1036                     EntityDataSourceColumn controllingColumn = null;
1037                     if (isContained)
1038                     {
1039                         // if this key is 'contained' in the entity, make the referential constrained
1040                         // property the principal for the column
1041                         int ordinalInConstraint = constraint.FromProperties.IndexOf(keyMember);
1042
1043                         // find corresponding member in the current (dependent) entity
1044                         EdmProperty correspondingProperty = constraint.ToProperties[ordinalInConstraint];
1045
1046                         controllingColumn = entityProperties[correspondingProperty];
1047                     }
1048                     columns.Add(new EntityDataSourceReferenceKeyColumn(ocWorkspace, group, keyMember, controllingColumn));
1049                 }
1050             }
1051         }
1052
1053         internal static void ValidateKeyPropertyValuesExist(EntityDataSourceWrapper entityWrapper, Dictionary<string, object> propertyValues)
1054         {
1055             foreach (var keyProperty in entityWrapper.Collection.AllPropertyDescriptors.Select(d => d.Column).OfType<EntityDataSourcePropertyColumn>().Where(c => c.IsKey))
1056             {
1057                 if (!propertyValues.ContainsKey(keyProperty.DisplayName))
1058                 {
1059                     throw new EntityDataSourceValidationException(Strings.EntityDataSourceView_NoKeyProperty);
1060                 }
1061             }
1062         }
1063
1064         static private readonly Type StackOverflowType = typeof(System.StackOverflowException);
1065         static private readonly Type OutOfMemoryType = typeof(System.OutOfMemoryException);
1066         static private readonly Type ThreadAbortType = typeof(System.Threading.ThreadAbortException);
1067         static private readonly Type NullReferenceType = typeof(System.NullReferenceException);
1068         static private readonly Type AccessViolationType = typeof(System.AccessViolationException);
1069         static private readonly Type SecurityType = typeof(System.Security.SecurityException);
1070         static private readonly Type AppDomainUnloadedType = typeof(System.AppDomainUnloadedException);
1071         static private readonly Type CannotUnloadAppDomainType = typeof(CannotUnloadAppDomainException);
1072         static private readonly Type BadImageFormatType = typeof(BadImageFormatException);
1073         static private readonly Type InvalidProgramType = typeof(InvalidProgramException);
1074         
1075         static private bool IsCatchableExceptionType(Exception e)
1076         {
1077             // a 'catchable' exception is defined by what it is not.
1078             Debug.Assert(e != null, "Unexpected null exception!");
1079             Type type = e.GetType();
1080
1081             return ((type != StackOverflowType) &&
1082                      (type != OutOfMemoryType) &&
1083                      (type != ThreadAbortType) &&
1084                      (type != NullReferenceType) &&
1085                      (type != AccessViolationType) &&
1086                      (type != AppDomainUnloadedType) &&
1087                      (type != CannotUnloadAppDomainType) &&
1088                      (type != BadImageFormatType) &&
1089                      (type != InvalidProgramType) &&
1090                      !SecurityType.IsAssignableFrom(type));
1091         }
1092     }
1093 }