1 //---------------------------------------------------------------------
2 // <copyright file="EntityDataSourceUtil.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9 using System.Collections;
10 using System.Collections.Generic;
11 using System.Collections.ObjectModel;
12 using System.ComponentModel;
14 using System.Data.Metadata.Edm;
15 using System.Data.Spatial;
16 using System.Diagnostics;
17 using System.Globalization;
19 using System.Reflection;
21 namespace System.Web.UI.WebControls
23 internal static class EntityDataSourceUtil
25 internal static readonly string EntitySqlElementAlias = "it";
27 internal static T CheckArgumentNull<T>(T value, string parameterName) where T : class
31 ThrowArgumentNullException(parameterName);
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.
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)
49 bool propertyIsOnEntity = false;
50 if (null != wrapperCollection)
52 // check for descriptor
53 if (null != wrapperCollection.GetItemProperties(null).Find(propertyName, /*ignoreCase*/ false))
55 propertyIsOnEntity = true;
60 ReadOnlyMetadataCollection<EdmMember> members = null;
61 switch (tu.EdmType.BuiltInTypeKind)
63 case BuiltInTypeKind.RowType:
64 members = ((RowType)(tu.EdmType)).Members;
66 case BuiltInTypeKind.EntityType:
67 members = ((EntityType)(tu.EdmType)).Members;
70 if (null != members && members.Contains(propertyName))
72 propertyIsOnEntity = true;
75 if (null != entitySet)
77 if ( ((EntityType)(entitySet.ElementType)).Members.Contains(propertyName) )
79 propertyIsOnEntity = true;
82 return propertyIsOnEntity;
87 /// Returns the value set onto the Parameter named by propertyName.
88 /// If the Paramter does not have a value, it returns null.
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)
97 if (null == parameterCollection) // ParameterCollection undefined
102 System.Collections.Specialized.IOrderedDictionary values =
103 parameterCollection.GetValues(entityDataSource.HttpContext, entityDataSource);
105 foreach (object key in values.Keys)
107 string parameterName = key as string;
108 if (null != parameterName && String.Equals(propertyName, parameterName, StringComparison.Ordinal))
110 return values[parameterName];
119 /// Get the System.Web.UI.WebControls.Parameter that matches the name in the given ParameterCollection
121 /// <param name="propertyName"></param>
122 /// <param name="parameterCollection"></param>
123 /// <returns></returns>
124 internal static Parameter GetParameter(string propertyName, ParameterCollection parameterCollection)
126 if (null == parameterCollection)
131 foreach (Parameter p in parameterCollection)
133 if (String.Equals(p.Name, propertyName, StringComparison.Ordinal))
143 /// Validates that the keys in the update parameters all match property names on the entityWrapper.
145 /// <param name="entityWrapper"></param>
146 /// <param name="parameters"></param>
147 internal static void ValidateWebControlParameterNames(EntityDataSourceWrapper entityWrapper,
148 ParameterCollection parameters,
149 EntityDataSource owner)
151 Debug.Assert(null != entityWrapper, "entityWrapper should not be null");
152 if (null != parameters)
154 PropertyDescriptorCollection entityProperties = entityWrapper.GetProperties();
155 System.Collections.Specialized.IOrderedDictionary parmVals = parameters.GetValues(owner.HttpContext, owner);
156 foreach (DictionaryEntry de in parmVals)
158 string key = de.Key as string;
159 if (null == key || null == entityProperties.Find(key, false))
161 throw new InvalidOperationException(Strings.EntityDataSourceUtil_InsertUpdateParametersDontMatchPropertyNameOnEntity(key, entityWrapper.WrappedEntity.GetType().ToString()));
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.
173 /// This method is only called if the user specifies EntitySetName and updates are enabled.
175 /// Does nothing for RowTypes.
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)
184 CheckArgumentNull<ItemCollection>(ocItemCollection, "ocItemCollection");
186 if (String.IsNullOrEmpty(entityTypeFilter))
188 List<EdmType> types = new List<EdmType>(EntityDataSourceUtil.GetTypeAndSubtypesOf(entityType, ocItemCollection, /*includeAbstractTypes*/true));
189 if (entityType.BaseType != null ||
190 types.Count() > 1 || entityType.Abstract)
192 throw new InvalidOperationException(Strings.EntityDataSourceUtil_EntityQueryCannotReturnPolymorphicTypes);
199 internal static IEnumerable<EdmType> GetTypeAndSubtypesOf(EntityType type, ReadOnlyCollection<GlobalItem> itemCollection, bool includeAbstractTypes)
201 if (includeAbstractTypes || !type.Abstract)
206 // Get entity sub-types
207 foreach (EdmType subType in GetTypeAndSubtypesOf<EntityType>(type, itemCollection, includeAbstractTypes))
209 yield return subType;
212 // Get complex sub-types
213 foreach (EdmType subType in GetTypeAndSubtypesOf<ComplexType>(type, itemCollection, includeAbstractTypes))
215 yield return subType;
219 internal static bool IsTypeOrSubtypeOf(EntityType superType, EntityType derivedType, ReadOnlyCollection<GlobalItem> itemCollection)
221 IEnumerable types = GetTypeAndSubtypesOf(superType, itemCollection, false);
222 foreach(EdmType type in types)
224 if (type == derivedType)
232 internal static Type GetClrType(MetadataWorkspace ocWorkspace, StructuralType edmType)
234 var oSpaceType = (StructuralType)ocWorkspace.GetObjectSpaceType(edmType);
235 var objectItemCollection = (ObjectItemCollection)(ocWorkspace.GetItemCollection(DataSpace.OSpace));
236 return objectItemCollection.GetClrType(oSpaceType);
239 internal static Type GetClrType(MetadataWorkspace ocWorkspace, EnumType edmType)
241 var oSpaceType = (EnumType)ocWorkspace.GetObjectSpaceType(edmType);
242 var objectItemCollection = (ObjectItemCollection)(ocWorkspace.GetItemCollection(DataSpace.OSpace));
243 return objectItemCollection.GetClrType(oSpaceType);
246 internal static ConstructorInfo GetConstructorInfo(Type type)
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);
251 if (null == constructorInfo)
253 throw new InvalidOperationException(Strings.DefaultConstructorNotFound(type));
256 return constructorInfo;
259 internal static PropertyInfo GetPropertyInfo(Type type, string name)
261 Debug.Assert(null != type, "type required");
262 Debug.Assert(null != name, "name required");
264 PropertyInfo propertyInfo = type.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, null, Type.EmptyTypes, null);
266 if (null == propertyInfo)
268 throw new InvalidOperationException(Strings.PropertyNotFound(name, type));
274 internal static object InitializeType(Type type)
276 ConstructorInfo constructorInfo = GetConstructorInfo(type);
277 return constructorInfo.Invoke(new object[] { });
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.
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)
290 Debug.Assert(!String.IsNullOrEmpty(columnName), "columnName must be given");
292 string result = null;
294 if (wrapperCollection != null)
296 // use wrapper definition if it is available
297 EntityDataSourceWrapperPropertyDescriptor descriptor =
298 wrapperCollection.GetItemProperties(null).Find(columnName, false) as EntityDataSourceWrapperPropertyDescriptor;
299 if (null != descriptor)
301 result = descriptor.Column.GetEntitySqlValue();
305 // if descriptor does not provide SQL, create the default: it._columnName_
308 result = EntitySqlElementAlias + "." + QuoteEntitySqlIdentifier(columnName);
314 internal static Type ConvertTypeCodeToType(TypeCode typeCode)
318 case TypeCode.Boolean:
319 return typeof(Boolean);
327 case TypeCode.DateTime:
328 return typeof(DateTime);
330 case TypeCode.DBNull:
331 return typeof(DBNull);
333 case TypeCode.Decimal:
334 return typeof(Decimal);
336 case TypeCode.Double:
337 return typeof(Double);
343 return typeof(Int16);
346 return typeof(Int32);
349 return typeof(Int64);
351 case TypeCode.Object:
352 return typeof(Object);
355 return typeof(SByte);
357 case TypeCode.Single:
358 return typeof(Single);
360 case TypeCode.String:
361 return typeof(String);
363 case TypeCode.UInt16:
364 return typeof(UInt16);
366 case TypeCode.UInt32:
367 return typeof(UInt32);
369 case TypeCode.UInt64:
370 return typeof(UInt64);
373 throw new InvalidOperationException(Strings.EntityDataSourceUtil_UnableToConvertTypeCodeToType(typeCode.ToString()));
378 /// Converts a DB type code to a CLR type, bypassing CLR type codes since there
379 /// is not a sufficient mapping.
381 /// <param name="dbType">The DB type to convert</param>
382 /// <returns>The mapped CLR type</returns>
383 internal static Type ConvertDbTypeToType(DbType dbType)
387 case DbType.AnsiString:
388 case DbType.AnsiStringFixedLength:
390 case DbType.StringFixedLength:
391 return typeof(String);
393 return typeof(Boolean);
396 case DbType.VarNumeric: //
397 case DbType.Currency:
399 return typeof(Decimal);
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);
407 return typeof(Double);
409 return typeof(Int16);
411 return typeof(Int32);
413 return typeof(Int64);
415 return typeof(SByte);
417 return typeof(Single);
419 return typeof(UInt16);
421 return typeof(UInt32);
423 return typeof(UInt64);
426 case DbType.DateTimeOffset: // new Katmai type
427 return typeof(DateTimeOffset);
429 return typeof(byte[]);
432 return typeof(Object);
436 private static IEnumerable<EdmType> GetTypeAndSubtypesOf<T_EdmType>(EdmType type, ReadOnlyCollection<GlobalItem> itemCollection, bool includeAbstractTypes)
437 where T_EdmType : EdmType
439 // Get the subtypes of the type from the WorkSpace
440 T_EdmType specificType = type as T_EdmType;
441 if (specificType != null)
444 IEnumerable<T_EdmType> typesInWorkSpace = itemCollection.OfType<T_EdmType>();
445 foreach (T_EdmType typeInWorkSpace in typesInWorkSpace)
447 if (specificType.Equals(typeInWorkSpace) == false && IsStrictSubtypeOf(typeInWorkSpace, specificType))
449 if (includeAbstractTypes || !typeInWorkSpace.Abstract)
451 yield return typeInWorkSpace;
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)
466 Debug.Assert(firstType != null, "firstType should not be not null");
467 if (secondType == null)
472 // walk up my type hierarchy list
473 for (EdmType t = firstType.BaseType; t != null; t = t.BaseType)
481 internal static bool NullCanBeAssignedTo(Type type)
483 Debug.Assert(null != type, "type required");
484 return !type.IsValueType || IsNullableType(type, out type);
487 internal static bool IsNullableType(Type type, out Type underlyingType)
489 Debug.Assert(null != type, "type required");
490 if (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>)))
492 underlyingType = type.GetGenericArguments()[0];
495 underlyingType = null;
499 internal static void ThrowArgumentNullException(string parameterName)
501 throw ArgumentNull(parameterName);
504 internal static ArgumentNullException ArgumentNull(string parameter)
506 ArgumentNullException e = new ArgumentNullException(parameter);
510 internal static object ConvertType(object value, Type type, string paramName)
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;
517 // Get the type converter for the destination type
518 TypeConverter converter = TypeDescriptor.GetConverter(type);
519 if (converter != null)
521 // Perform the conversion
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))
534 value = decimalResult;
536 else if (type.IsAssignableFrom(typeof(DbGeography)) && TryParseGeography(s, out geographyResult))
538 value = geographyResult;
540 else if (type.IsAssignableFrom(typeof(DbGeometry)) && TryParseGeometry(s, out geometryResult))
542 value = geometryResult;
546 value = converter.ConvertFromString(s);
549 catch (Exception) // ConvertFromString sometimes throws exceptions of actual type Exception!
551 // For Nullable types, we just get the type parameter since that makes a more readable exception message
553 if (type.IsGenericType && typeof(Nullable<>).IsAssignableFrom(type.GetGenericTypeDefinition()) && !type.ContainsGenericParameters)
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;
561 typeName = type.FullName;
563 throw new InvalidOperationException(Strings.EntityDataSourceUtil_UnableToConvertStringToType(paramName, typeName));
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)))
572 value = Enum.ToObject(underlyingType ?? type, value);
579 /// Converts the string representation to DbGeography instance. A return value indicates if the conversion succeeded.
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)
587 return TryParseGeo<DbGeography>(stringValue, (geometryText, srid) => DbGeography.FromText(geometryText, srid), out result);
591 /// Converts the string representation to DbGeometry instance. A return value indicates if the conversion succeeded.
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)
599 return TryParseGeo<DbGeometry>(stringValue, (geometryText, srid) => DbGeometry.FromText(geometryText, srid), out result);
603 /// Converts the string representation to DbGeometry or DbGeography instance. A return value indicates if the conversion succeeded.
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)
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");
621 if (TryParseSpatialString(stringValue, out srid, out geometryText))
625 result = createSpatialTypeInstanceFunc(geometryText, srid) as T;
630 if(!IsCatchableExceptionType(ex))
642 /// Retrieves SRID and geo text from <paramref name="stringValue"/> in format "SRID=4326;POINT (100 100)" .
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)
650 Debug.Assert(stringValue != null, "stringValue != null");
652 string[] components = stringValue.Split(';');
654 // expected 2 semicolon separated components - SRID and well known text
655 if (components.Length == 2)
657 if (components[0].StartsWith("SRID=", StringComparison.Ordinal))
659 if (int.TryParse(components[0].Substring("SRID=".Length), NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out srid))
661 geoText = components[1];
673 internal static void SetAllPropertiesWithVerification(EntityDataSourceWrapper entityWrapper,
674 Dictionary<string, object> changedProperties,
677 Dictionary<string, Exception> exceptions = null;
678 entityWrapper.SetAllProperties(changedProperties, /*overwriteSameValue*/true, ref exceptions);
680 if (null != exceptions)
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);
694 /// Get the Clr type for the primitive enum or complex type member. The member must not be null.
696 internal static Type GetMemberClrType(MetadataWorkspace ocWorkspace, EdmMember member)
698 EntityDataSourceUtil.CheckArgumentNull(member, "member");
700 EdmType memberType = member.TypeUsage.EdmType;
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");
708 if (EntityDataSourceUtil.IsScalar(memberType))
710 clrType = memberType.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType ?
711 ((PrimitiveType)memberType).ClrEquivalentType :
712 GetClrType(ocWorkspace, (EnumType)memberType);
714 if (!NullCanBeAssignedTo(clrType))
717 if (member.TypeUsage.Facets.TryGetValue("Nullable", true, out facet))
719 if ((bool)facet.Value)
721 clrType = MakeNullable(clrType);
729 memberType.BuiltInTypeKind == BuiltInTypeKind.EntityType || memberType.BuiltInTypeKind == BuiltInTypeKind.ComplexType,
730 "Complex or Entity type expected");
732 clrType = GetClrType(ocWorkspace, (StructuralType)memberType);
738 internal static Type MakeNullable(Type type)
740 if (!NullCanBeAssignedTo(type))
742 type = typeof(Nullable<>).MakeGenericType(type);
748 /// Returns the collection of AssociationSetEnds for the relationships for this entity
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)
756 foreach (AssociationSet associationSet in entitySet.EntityContainer.BaseEntitySets.OfType<AssociationSet>())
758 Debug.Assert(associationSet.AssociationSetEnds.Count == 2, "non binary association?");
759 AssociationSetEnd firstEnd = associationSet.AssociationSetEnds[0];
760 AssociationSetEnd secondEnd = associationSet.AssociationSetEnds[1];
762 // If both ends match, then we will return both ends
763 if (IsReferenceEnd(entitySet, entityType, firstEnd, secondEnd, forKey))
765 yield return secondEnd;
767 if (IsReferenceEnd(entitySet, entityType, secondEnd, firstEnd, forKey))
769 yield return firstEnd;
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)
779 private static bool IsContained(AssociationSetEnd end, out ReferentialConstraint constraint)
781 CheckArgumentNull(end, "end");
783 AssociationEndMember endMember = end.CorrespondingAssociationEndMember;
784 AssociationType associationType = (AssociationType)endMember.DeclaringType;
789 if (null != associationType.ReferentialConstraints)
791 foreach (ReferentialConstraint candidate in associationType.ReferentialConstraints)
793 if (candidate.FromRole.Name == endMember.Name)
795 constraint = candidate;
805 internal static bool TryGetCorrespondingNavigationProperty(AssociationEndMember end, out NavigationProperty navigationProperty)
807 EntityType entityType = GetEntityType(GetOppositeEnd(end));
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;
816 internal static AssociationEndMember GetOppositeEnd(AssociationEndMember end)
818 return (AssociationEndMember)end.DeclaringType.Members.Where(m => m != end).Single();
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.
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.)
828 private static bool IsReferenceEnd(EntitySet entitySet, EntityType entityType, AssociationSetEnd fromEnd, AssociationSetEnd toEnd, bool forKey)
830 EntityType fromType = GetEntityType(fromEnd);
832 if (fromEnd.EntitySet == entitySet && (IsStrictSubtypeOf(entityType, fromType) || entityType == fromType))
834 RelationshipMultiplicity fromMult = fromEnd.CorrespondingAssociationEndMember.RelationshipMultiplicity;
835 RelationshipMultiplicity toMult = toEnd.CorrespondingAssociationEndMember.RelationshipMultiplicity;
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) ))
851 internal static bool IsScalar(EdmType type)
853 return type.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType ||
854 type.BuiltInTypeKind == BuiltInTypeKind.EnumType;
857 internal static EntityType GetEntityType(AssociationSetEnd end)
859 return GetEntityType(end.CorrespondingAssociationEndMember);
862 internal static EntityType GetEntityType(AssociationEndMember end)
864 EntityType entityType = (EntityType)((RefType)end.TypeUsage.EdmType).ElementType;
869 internal static string GetQualifiedEntitySetName(EntitySet entitySet)
871 EntityDataSourceUtil.CheckArgumentNull(entitySet, "entitySet");
872 // ContainerName.EntitySetName
873 return entitySet.EntityContainer.Name + "." + entitySet.Name;
876 internal static string QuoteEntitySqlIdentifier(string identifier)
878 return "[" + (identifier ?? string.Empty).Replace("]", "]]") + "]";
881 internal static string CreateEntitySqlTypeIdentifier(EdmType type)
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);
890 internal static string CreateEntitySqlSetIdentifier(EntitySetBase set)
892 // [_container_name_].[_set_name_]
893 return QuoteEntitySqlIdentifier(set.EntityContainer.Name) + "." + QuoteEntitySqlIdentifier(set.Name);
897 /// Determines which columns to expose for the given set and type. Includes
898 /// flattened complex properties and 'reference' keys.
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
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)
910 CheckArgumentNull(csWorkspace, "csWorkspace");
911 CheckArgumentNull(ocWorkspace, "ocWorkspace");
912 CheckArgumentNull(entitySet, "entitySet");
913 CheckArgumentNull(entityType, "entityType");
915 ReadOnlyCollection<EdmMember> interestingMembers = GetInterestingMembers(csWorkspace, entitySet, entityType);
917 IEnumerable<EntityDataSourceColumn> columns = GetColumns(entitySet, entityType, ocWorkspace, interestingMembers);
918 List<EntityDataSourceColumn> result = new List<EntityDataSourceColumn>();
920 // give precedence to simple named columns (
922 HashSet<string> usedNames = new HashSet<string>();
923 foreach (EntityDataSourceColumn column in columns)
925 if (!column.IsHidden)
927 // check that the column name has not been used
928 if (!usedNames.Add(column.DisplayName))
930 throw new InvalidOperationException(Strings.DisplayNameCollision(column.DisplayName));
936 return result.AsReadOnly();
939 private static ReadOnlyCollection<EdmMember> GetInterestingMembers(MetadataWorkspace csWorkspace, EntitySet entitySet, EntityType entityType)
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));
946 // keys are also interesting...
947 foreach (EdmMember keyMember in entityType.KeyMembers)
949 interestingMembers.Add(keyMember);
952 ReadOnlyCollection<EdmMember> result = interestingMembers.ToList().AsReadOnly();
957 private static IEnumerable<EntityDataSourceColumn> GetColumns(EntitySet entitySet, EntityType entityType,
958 MetadataWorkspace ocWorkspace, ReadOnlyCollection<EdmMember> interestingMembers)
960 List<EntityDataSourceColumn> columns = new List<EntityDataSourceColumn>();
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);
966 // Navigation reference properties
967 AddReferenceNavigationColumns(columns, ocWorkspace, entitySet, entityType);
969 // Reference key properties
970 AddReferenceKeyColumns(columns, ocWorkspace, entitySet, entityType, entityProperties);
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)
979 Dictionary<EdmProperty, EntityDataSourcePropertyColumn> result = new Dictionary<EdmProperty, EntityDataSourcePropertyColumn>();
981 foreach (EdmProperty property in properties)
983 bool isLocallyInteresting = interestingMembers.Contains(property);
985 EntityDataSourceMemberPath prefix = new EntityDataSourceMemberPath(ocWorkspace, parent, property, isLocallyInteresting);
986 EdmType propertyType = property.TypeUsage.EdmType;
988 // add column for this entity property
989 EntityDataSourcePropertyColumn propertyColumn = new EntityDataSourcePropertyColumn(prefix);
990 columns.Add(propertyColumn);
991 result.Add(property, propertyColumn);
993 if (propertyType.BuiltInTypeKind == BuiltInTypeKind.ComplexType)
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);
999 // other property types are not currently supported (or possible in EF V1 for that matter)
1005 private static void AddReferenceNavigationColumns(List<EntityDataSourceColumn> columns, MetadataWorkspace ocWorkspace, EntitySet entitySet, EntityType entityType)
1007 foreach (AssociationSetEnd toEnd in GetReferenceEnds(entitySet, entityType, /*forKey*/false))
1009 // Check for a navigation property
1010 NavigationProperty navigationProperty;
1011 if (TryGetCorrespondingNavigationProperty(toEnd.CorrespondingAssociationEndMember, out navigationProperty))
1013 Type clrToType = EntityDataSourceUtil.GetMemberClrType(ocWorkspace, navigationProperty);
1014 EntityDataSourceReferenceValueColumn column = EntityDataSourceReferenceValueColumn.Create(clrToType, ocWorkspace, navigationProperty);
1015 columns.Add(column);
1020 private static void AddReferenceKeyColumns(List<EntityDataSourceColumn> columns, MetadataWorkspace ocWorkspace, EntitySet entitySet, EntityType entityType, Dictionary<EdmProperty, EntityDataSourcePropertyColumn> entityProperties)
1022 foreach (AssociationSetEnd toEnd in GetReferenceEnds(entitySet, entityType, /*forKey*/true))
1024 ReferentialConstraint constraint;
1025 bool isContained = EntityDataSourceUtil.IsContained(toEnd, out constraint);
1027 // Create a group for the end columns
1028 EntityType toType = EntityDataSourceUtil.GetEntityType(toEnd);
1029 Type clrToType = EntityDataSourceUtil.GetClrType(ocWorkspace, toType);
1031 EntityDataSourceReferenceGroup group = EntityDataSourceReferenceGroup.Create(clrToType, toEnd);
1033 // Create a column for every key
1034 foreach (EdmProperty keyMember in GetEntityType(toEnd).KeyMembers)
1036 EntityDataSourceColumn controllingColumn = null;
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);
1043 // find corresponding member in the current (dependent) entity
1044 EdmProperty correspondingProperty = constraint.ToProperties[ordinalInConstraint];
1046 controllingColumn = entityProperties[correspondingProperty];
1048 columns.Add(new EntityDataSourceReferenceKeyColumn(ocWorkspace, group, keyMember, controllingColumn));
1053 internal static void ValidateKeyPropertyValuesExist(EntityDataSourceWrapper entityWrapper, Dictionary<string, object> propertyValues)
1055 foreach (var keyProperty in entityWrapper.Collection.AllPropertyDescriptors.Select(d => d.Column).OfType<EntityDataSourcePropertyColumn>().Where(c => c.IsKey))
1057 if (!propertyValues.ContainsKey(keyProperty.DisplayName))
1059 throw new EntityDataSourceValidationException(Strings.EntityDataSourceView_NoKeyProperty);
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);
1075 static private bool IsCatchableExceptionType(Exception e)
1077 // a 'catchable' exception is defined by what it is not.
1078 Debug.Assert(e != null, "Unexpected null exception!");
1079 Type type = e.GetType();
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));