//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner [....] // @backupOwner [....] //--------------------------------------------------------------------- using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Data; using System.Data.Metadata.Edm; using System.Data.Spatial; using System.Diagnostics; using System.Globalization; using System.Linq; using System.Reflection; namespace System.Web.UI.WebControls { internal static class EntityDataSourceUtil { internal static readonly string EntitySqlElementAlias = "it"; internal static T CheckArgumentNull(T value, string parameterName) where T : class { if (null == value) { ThrowArgumentNullException(parameterName); } return value; } /// /// Indicates whether the given property name exists on the result. /// The result could be indicated by a wrapperCollection, an entitySet or a typeUsage, /// any of which could be null. /// /// /// /// /// /// internal static bool PropertyIsOnEntity(string propertyName, EntityDataSourceWrapperCollection wrapperCollection, EntitySet entitySet, TypeUsage tu) { bool propertyIsOnEntity = false; if (null != wrapperCollection) { // check for descriptor if (null != wrapperCollection.GetItemProperties(null).Find(propertyName, /*ignoreCase*/ false)) { propertyIsOnEntity = true; } } if (null != tu) { ReadOnlyMetadataCollection members = null; switch (tu.EdmType.BuiltInTypeKind) { case BuiltInTypeKind.RowType: members = ((RowType)(tu.EdmType)).Members; break; case BuiltInTypeKind.EntityType: members = ((EntityType)(tu.EdmType)).Members; break; } if (null != members && members.Contains(propertyName)) { propertyIsOnEntity = true; } } if (null != entitySet) { if ( ((EntityType)(entitySet.ElementType)).Members.Contains(propertyName) ) { propertyIsOnEntity = true; } } return propertyIsOnEntity; } /// /// Returns the value set onto the Parameter named by propertyName. /// If the Paramter does not have a value, it returns null. /// /// /// /// /// internal static object GetParameterValue(string propertyName, ParameterCollection parameterCollection, EntityDataSource entityDataSource) { if (null == parameterCollection) // ParameterCollection undefined { return null; } System.Collections.Specialized.IOrderedDictionary values = parameterCollection.GetValues(entityDataSource.HttpContext, entityDataSource); foreach (object key in values.Keys) { string parameterName = key as string; if (null != parameterName && String.Equals(propertyName, parameterName, StringComparison.Ordinal)) { return values[parameterName]; } } return null; } /// /// Get the System.Web.UI.WebControls.Parameter that matches the name in the given ParameterCollection /// /// /// /// internal static Parameter GetParameter(string propertyName, ParameterCollection parameterCollection) { if (null == parameterCollection) { return null; } foreach (Parameter p in parameterCollection) { if (String.Equals(p.Name, propertyName, StringComparison.Ordinal)) { return p; } } return null; } /// /// Validates that the keys in the update parameters all match property names on the entityWrapper. /// /// /// internal static void ValidateWebControlParameterNames(EntityDataSourceWrapper entityWrapper, ParameterCollection parameters, EntityDataSource owner) { Debug.Assert(null != entityWrapper, "entityWrapper should not be null"); if (null != parameters) { PropertyDescriptorCollection entityProperties = entityWrapper.GetProperties(); System.Collections.Specialized.IOrderedDictionary parmVals = parameters.GetValues(owner.HttpContext, owner); foreach (DictionaryEntry de in parmVals) { string key = de.Key as string; if (null == key || null == entityProperties.Find(key, false)) { throw new InvalidOperationException(Strings.EntityDataSourceUtil_InsertUpdateParametersDontMatchPropertyNameOnEntity(key, entityWrapper.WrappedEntity.GetType().ToString())); } } } } /// /// Verifies that the query's typeusage will not result in a polymorphic result. /// If the query would be restricted "is of only" using entityTypeFilter, then /// this check assumes the result will not be polymorphic. /// /// This method is only called if the user specifies EntitySetName and updates are enabled. /// /// Does nothing for RowTypes. /// /// The TypeUsage from the query /// /// internal static void CheckNonPolymorphicTypeUsage(EntityType entityType, ItemCollection ocItemCollection, string entityTypeFilter) { CheckArgumentNull(ocItemCollection, "ocItemCollection"); if (String.IsNullOrEmpty(entityTypeFilter)) { List types = new List(EntityDataSourceUtil.GetTypeAndSubtypesOf(entityType, ocItemCollection, /*includeAbstractTypes*/true)); if (entityType.BaseType != null || types.Count() > 1 || entityType.Abstract) { throw new InvalidOperationException(Strings.EntityDataSourceUtil_EntityQueryCannotReturnPolymorphicTypes); } } return; } internal static IEnumerable GetTypeAndSubtypesOf(EntityType type, ReadOnlyCollection itemCollection, bool includeAbstractTypes) { if (includeAbstractTypes || !type.Abstract) { yield return type; } // Get entity sub-types foreach (EdmType subType in GetTypeAndSubtypesOf(type, itemCollection, includeAbstractTypes)) { yield return subType; } // Get complex sub-types foreach (EdmType subType in GetTypeAndSubtypesOf(type, itemCollection, includeAbstractTypes)) { yield return subType; } } internal static bool IsTypeOrSubtypeOf(EntityType superType, EntityType derivedType, ReadOnlyCollection itemCollection) { IEnumerable types = GetTypeAndSubtypesOf(superType, itemCollection, false); foreach(EdmType type in types) { if (type == derivedType) { return true; } } return false; } internal static Type GetClrType(MetadataWorkspace ocWorkspace, StructuralType edmType) { var oSpaceType = (StructuralType)ocWorkspace.GetObjectSpaceType(edmType); var objectItemCollection = (ObjectItemCollection)(ocWorkspace.GetItemCollection(DataSpace.OSpace)); return objectItemCollection.GetClrType(oSpaceType); } internal static Type GetClrType(MetadataWorkspace ocWorkspace, EnumType edmType) { var oSpaceType = (EnumType)ocWorkspace.GetObjectSpaceType(edmType); var objectItemCollection = (ObjectItemCollection)(ocWorkspace.GetItemCollection(DataSpace.OSpace)); return objectItemCollection.GetClrType(oSpaceType); } internal static ConstructorInfo GetConstructorInfo(Type type) { Debug.Assert(null != type, "type required"); ConstructorInfo constructorInfo = type.GetConstructor(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.CreateInstance, null, System.Type.EmptyTypes, null); if (null == constructorInfo) { throw new InvalidOperationException(Strings.DefaultConstructorNotFound(type)); } return constructorInfo; } internal static PropertyInfo GetPropertyInfo(Type type, string name) { Debug.Assert(null != type, "type required"); Debug.Assert(null != name, "name required"); PropertyInfo propertyInfo = type.GetProperty(name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance, null, null, Type.EmptyTypes, null); if (null == propertyInfo) { throw new InvalidOperationException(Strings.PropertyNotFound(name, type)); } return propertyInfo; } internal static object InitializeType(Type type) { ConstructorInfo constructorInfo = GetConstructorInfo(type); return constructorInfo.Invoke(new object[] { }); } /// /// Given a data source column name, returns the corresponding Entity-SQL. If /// we are using the wrapper, we defer to the property descriptor to get /// the string. If there is no wrapper (or no corresponding property descriptor) /// we use the column name directly. /// /// Column name for which we produce a value expression. /// Entity-SQL for column. internal static string GetEntitySqlValueForColumnName(string columnName, EntityDataSourceWrapperCollection wrapperCollection) { Debug.Assert(!String.IsNullOrEmpty(columnName), "columnName must be given"); string result = null; if (wrapperCollection != null) { // use wrapper definition if it is available EntityDataSourceWrapperPropertyDescriptor descriptor = wrapperCollection.GetItemProperties(null).Find(columnName, false) as EntityDataSourceWrapperPropertyDescriptor; if (null != descriptor) { result = descriptor.Column.GetEntitySqlValue(); } } // if descriptor does not provide SQL, create the default: it._columnName_ if (null == result) { result = EntitySqlElementAlias + "." + QuoteEntitySqlIdentifier(columnName); } return result; } internal static Type ConvertTypeCodeToType(TypeCode typeCode) { switch (typeCode) { case TypeCode.Boolean: return typeof(Boolean); case TypeCode.Byte: return typeof(Byte); case TypeCode.Char: return typeof(Char); case TypeCode.DateTime: return typeof(DateTime); case TypeCode.DBNull: return typeof(DBNull); case TypeCode.Decimal: return typeof(Decimal); case TypeCode.Double: return typeof(Double); case TypeCode.Empty: return null; case TypeCode.Int16: return typeof(Int16); case TypeCode.Int32: return typeof(Int32); case TypeCode.Int64: return typeof(Int64); case TypeCode.Object: return typeof(Object); case TypeCode.SByte: return typeof(SByte); case TypeCode.Single: return typeof(Single); case TypeCode.String: return typeof(String); case TypeCode.UInt16: return typeof(UInt16); case TypeCode.UInt32: return typeof(UInt32); case TypeCode.UInt64: return typeof(UInt64); default: throw new InvalidOperationException(Strings.EntityDataSourceUtil_UnableToConvertTypeCodeToType(typeCode.ToString())); } } /// /// Converts a DB type code to a CLR type, bypassing CLR type codes since there /// is not a sufficient mapping. /// /// The DB type to convert /// The mapped CLR type internal static Type ConvertDbTypeToType(DbType dbType) { switch (dbType) { case DbType.AnsiString: case DbType.AnsiStringFixedLength: case DbType.String: case DbType.StringFixedLength: return typeof(String); case DbType.Boolean: return typeof(Boolean); case DbType.Byte: return typeof(Byte); case DbType.VarNumeric: // case DbType.Currency: case DbType.Decimal: return typeof(Decimal); case DbType.Date: case DbType.DateTime: case DbType.DateTime2: // new Katmai type return typeof(DateTime); case DbType.Time: // new Katmai type return typeof(TimeSpan); case DbType.Double: return typeof(Double); case DbType.Int16: return typeof(Int16); case DbType.Int32: return typeof(Int32); case DbType.Int64: return typeof(Int64); case DbType.SByte: return typeof(SByte); case DbType.Single: return typeof(Single); case DbType.UInt16: return typeof(UInt16); case DbType.UInt32: return typeof(UInt32); case DbType.UInt64: return typeof(UInt64); case DbType.Guid: return typeof(Guid); case DbType.DateTimeOffset: // new Katmai type return typeof(DateTimeOffset); case DbType.Binary: return typeof(byte[]); case DbType.Object: default: return typeof(Object); } } private static IEnumerable GetTypeAndSubtypesOf(EdmType type, ReadOnlyCollection itemCollection, bool includeAbstractTypes) where T_EdmType : EdmType { // Get the subtypes of the type from the WorkSpace T_EdmType specificType = type as T_EdmType; if (specificType != null) { IEnumerable typesInWorkSpace = itemCollection.OfType(); foreach (T_EdmType typeInWorkSpace in typesInWorkSpace) { if (specificType.Equals(typeInWorkSpace) == false && IsStrictSubtypeOf(typeInWorkSpace, specificType)) { if (includeAbstractTypes || !typeInWorkSpace.Abstract) { yield return typeInWorkSpace; } } } } yield break; } // requires: firstType is not null // effects: if otherType is among the base types, return true, // otherwise returns false. // when othertype is same as the current type, return false. private static bool IsStrictSubtypeOf(EdmType firstType, EdmType secondType) { Debug.Assert(firstType != null, "firstType should not be not null"); if (secondType == null) { return false; } // walk up my type hierarchy list for (EdmType t = firstType.BaseType; t != null; t = t.BaseType) { if (t == secondType) return true; } return false; } internal static bool NullCanBeAssignedTo(Type type) { Debug.Assert(null != type, "type required"); return !type.IsValueType || IsNullableType(type, out type); } internal static bool IsNullableType(Type type, out Type underlyingType) { Debug.Assert(null != type, "type required"); if (type.IsGenericType && type.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) { underlyingType = type.GetGenericArguments()[0]; return true; } underlyingType = null; return false; } internal static void ThrowArgumentNullException(string parameterName) { throw ArgumentNull(parameterName); } internal static ArgumentNullException ArgumentNull(string parameter) { ArgumentNullException e = new ArgumentNullException(parameter); return e; } internal static object ConvertType(object value, Type type, string paramName) { // NOTE: This method came from ObjectDataSource via LinqDataSource. // It has been changed to support better parsing of decimal values. string s = value as string; if (s != null) { // Get the type converter for the destination type TypeConverter converter = TypeDescriptor.GetConverter(type); if (converter != null) { // Perform the conversion try { // If the requested type is decimal or a spatial type, then first try to parse the string value. // For decimal values we use the Decimal parsing which is able to handle comma thousands separators // For spatial types we understand the string value returned in the format the .ToString() method // on DbGeometry or DbGeography would return it. If this doesn't work, or the requested value // is not decimal/DbGeometry/DbGeography, then we fall back on the type converter mechanism. decimal decimalResult; DbGeography geographyResult; DbGeometry geometryResult; if (type.IsAssignableFrom(typeof(Decimal)) && Decimal.TryParse(s, out decimalResult)) { value = decimalResult; } else if (type.IsAssignableFrom(typeof(DbGeography)) && TryParseGeography(s, out geographyResult)) { value = geographyResult; } else if (type.IsAssignableFrom(typeof(DbGeometry)) && TryParseGeometry(s, out geometryResult)) { value = geometryResult; } else { value = converter.ConvertFromString(s); } } catch (Exception) // ConvertFromString sometimes throws exceptions of actual type Exception! { // For Nullable types, we just get the type parameter since that makes a more readable exception message string typeName; if (type.IsGenericType && typeof(Nullable<>).IsAssignableFrom(type.GetGenericTypeDefinition()) && !type.ContainsGenericParameters) { Type[] types = type.GetGenericArguments(); Debug.Assert(types != null && types.Length == 1, "Nullable did not have a single generic type."); typeName = types[0].FullName; } else { typeName = type.FullName; } throw new InvalidOperationException(Strings.EntityDataSourceUtil_UnableToConvertStringToType(paramName, typeName)); } } } // values for enum properties need to be cast to make sure nullable enums will work Type underlyingType = null; if (value != null && (type.IsEnum || (IsNullableType(type, out underlyingType) && underlyingType.IsEnum))) { value = Enum.ToObject(underlyingType ?? type, value); } return value; } /// /// Converts the string representation to DbGeography instance. A return value indicates if the conversion succeeded. /// /// A geography string to convert. /// If the conversion succeeds an instance of DbGeometry type created from ; otherwise null. /// true if was converted successfully; otherwise false; /// The must be in the format returned by method. private static bool TryParseGeography(string stringValue, out DbGeography result) { return TryParseGeo(stringValue, (geometryText, srid) => DbGeography.FromText(geometryText, srid), out result); } /// /// Converts the string representation to DbGeometry instance. A return value indicates if the conversion succeeded. /// /// A geometry string to convert. /// If the conversion succeeds an instance of DbGeometry type created from ; otherwise null. /// true if was converted successfully; otherwise false; /// The must be in the format returned by method. private static bool TryParseGeometry(string stringValue, out DbGeometry result) { return TryParseGeo(stringValue, (geometryText, srid) => DbGeometry.FromText(geometryText, srid), out result); } /// /// Converts the string representation to DbGeometry or DbGeography instance. A return value indicates if the conversion succeeded. /// /// Type to convert the . Must be either DbGeometry or DbGeography. /// A geometry string to convert. /// Function invoked to create an instance of type T given SRID and geo text. /// If the conversion succeeds an instance of DbGeometry or DbGeography type created from ; otherwise null. /// true if was converted successfully; otherwise false; /// The must be in the format returned by .ToString() method of T. private static bool TryParseGeo(string stringValue, Func createSpatialTypeInstanceFunc, out T result) where T : class { Debug.Assert(typeof(DbGeography).IsAssignableFrom(typeof(T)) || typeof(DbGeometry).IsAssignableFrom(typeof(T)), "This method should be called only for spatial type"); Debug.Assert(createSpatialTypeInstanceFunc != null, "createSpatialTypeInstanceFunc != null"); Debug.Assert(stringValue != null, "stringValue != null"); int srid; string geometryText; if (TryParseSpatialString(stringValue, out srid, out geometryText)) { try { result = createSpatialTypeInstanceFunc(geometryText, srid) as T; return true; } catch(Exception ex) { if(!IsCatchableExceptionType(ex)) { throw; } } } result = null; return false; } /// /// Retrieves SRID and geo text from in format "SRID=4326;POINT (100 100)" . /// /// String to parse. /// SRID retrieved from . /// Geo text retrieved from . /// true if it was possible to retrieve both SRID and geo text; otherwise false. private static bool TryParseSpatialString(string stringValue, out int srid, out string geoText) { Debug.Assert(stringValue != null, "stringValue != null"); string[] components = stringValue.Split(';'); // expected 2 semicolon separated components - SRID and well known text if (components.Length == 2) { if (components[0].StartsWith("SRID=", StringComparison.Ordinal)) { if (int.TryParse(components[0].Substring("SRID=".Length), NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out srid)) { geoText = components[1]; return true; } } } srid = int.MinValue; geoText = null; return false; } internal static void SetAllPropertiesWithVerification(EntityDataSourceWrapper entityWrapper, Dictionary changedProperties, bool overwrite) { Dictionary exceptions = null; entityWrapper.SetAllProperties(changedProperties, /*overwriteSameValue*/true, ref exceptions); if (null != exceptions) { // The EntityDataSourceValidationException has a property "InnerExceptions" that encapsulates // all of the failed property setters. The message from one of those errors is surfaced so that it // appears on the web page as a human-readable error like: // "Error while setting property 'PropertyName': 'The value cannot be null.'." string key = exceptions.Keys.First(); throw new EntityDataSourceValidationException( Strings.EntityDataSourceView_DataConversionError( key, exceptions[key].Message), exceptions); } } /// /// Get the Clr type for the primitive enum or complex type member. The member must not be null. /// internal static Type GetMemberClrType(MetadataWorkspace ocWorkspace, EdmMember member) { EntityDataSourceUtil.CheckArgumentNull(member, "member"); EdmType memberType = member.TypeUsage.EdmType; Debug.Assert(EntityDataSourceUtil.IsScalar(memberType) || memberType.BuiltInTypeKind == BuiltInTypeKind.ComplexType || memberType.BuiltInTypeKind == BuiltInTypeKind.EntityType, "member type must be primitive, enum, entity or complex type"); Type clrType; if (EntityDataSourceUtil.IsScalar(memberType)) { clrType = memberType.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType ? ((PrimitiveType)memberType).ClrEquivalentType : GetClrType(ocWorkspace, (EnumType)memberType); if (!NullCanBeAssignedTo(clrType)) { Facet facet; if (member.TypeUsage.Facets.TryGetValue("Nullable", true, out facet)) { if ((bool)facet.Value) { clrType = MakeNullable(clrType); } } } } else { Debug.Assert( memberType.BuiltInTypeKind == BuiltInTypeKind.EntityType || memberType.BuiltInTypeKind == BuiltInTypeKind.ComplexType, "Complex or Entity type expected"); clrType = GetClrType(ocWorkspace, (StructuralType)memberType); } return clrType; } internal static Type MakeNullable(Type type) { if (!NullCanBeAssignedTo(type)) { type = typeof(Nullable<>).MakeGenericType(type); } return type; } /// /// Returns the collection of AssociationSetEnds for the relationships for this entity /// /// /// /// If true, returns only the other ends with multiplicity 1. Ignores 1:0..1 relationships. /// internal static IEnumerable GetReferenceEnds(EntitySet entitySet, EntityType entityType, bool forKey) { foreach (AssociationSet associationSet in entitySet.EntityContainer.BaseEntitySets.OfType()) { Debug.Assert(associationSet.AssociationSetEnds.Count == 2, "non binary association?"); AssociationSetEnd firstEnd = associationSet.AssociationSetEnds[0]; AssociationSetEnd secondEnd = associationSet.AssociationSetEnds[1]; // If both ends match, then we will return both ends if (IsReferenceEnd(entitySet, entityType, firstEnd, secondEnd, forKey)) { yield return secondEnd; } if (IsReferenceEnd(entitySet, entityType, secondEnd, firstEnd, forKey)) { yield return firstEnd; } } } /// /// Determine if the end is 'contained' in the source entity via a referential integrity constraint (e.g., /// in a relationship from OrderDetail to Order where OrderDetail has the OrderId property, the association set end /// is contained in the order detail entity) /// private static bool IsContained(AssociationSetEnd end, out ReferentialConstraint constraint) { CheckArgumentNull(end, "end"); AssociationEndMember endMember = end.CorrespondingAssociationEndMember; AssociationType associationType = (AssociationType)endMember.DeclaringType; constraint = null; bool result = false; if (null != associationType.ReferentialConstraints) { foreach (ReferentialConstraint candidate in associationType.ReferentialConstraints) { if (candidate.FromRole.Name == endMember.Name) { constraint = candidate; result = true; break; } } } return result; } internal static bool TryGetCorrespondingNavigationProperty(AssociationEndMember end, out NavigationProperty navigationProperty) { EntityType entityType = GetEntityType(GetOppositeEnd(end)); // if there is a corresponding navigation property, use its name as the prefix navigationProperty = entityType.NavigationProperties .Where(np => np.ToEndMember == end) .SingleOrDefault(); // metadata is supposed to ensure this is non-ambiguous return null != navigationProperty; } internal static AssociationEndMember GetOppositeEnd(AssociationEndMember end) { return (AssociationEndMember)end.DeclaringType.Members.Where(m => m != end).Single(); } /// /// A navigation ('fromEnd' -> 'toEnd') defines a reference end for 'entitySet' and 'entityType' if it /// has multiplicity 0..1 or 1..1, is bound to the set, and has the appropriate type. /// /// We omit 1..1:0..1 navigations assuming that the opposite end owns the relationship (since the foreign /// key would need to point in the opposite direction.) /// private static bool IsReferenceEnd(EntitySet entitySet, EntityType entityType, AssociationSetEnd fromEnd, AssociationSetEnd toEnd, bool forKey) { EntityType fromType = GetEntityType(fromEnd); if (fromEnd.EntitySet == entitySet && (IsStrictSubtypeOf(entityType, fromType) || entityType == fromType)) { RelationshipMultiplicity fromMult = fromEnd.CorrespondingAssociationEndMember.RelationshipMultiplicity; RelationshipMultiplicity toMult = toEnd.CorrespondingAssociationEndMember.RelationshipMultiplicity; // If forKey is false (we are testing to see if this is a far end for a reference, not a key) // then fromMult is ignored and all far-end 1 or 0..1 multiplicity ends are exposed. // If forKey is true, then we are asking about a reference end for the purpose of flattening. // We do not flatten 1:0..1 relationships because of a limitation in the EDM. if (toMult == RelationshipMultiplicity.One || (toMult == RelationshipMultiplicity.ZeroOrOne && (!forKey || fromMult != RelationshipMultiplicity.One) )) { return true; } } return false; } internal static bool IsScalar(EdmType type) { return type.BuiltInTypeKind == BuiltInTypeKind.PrimitiveType || type.BuiltInTypeKind == BuiltInTypeKind.EnumType; } internal static EntityType GetEntityType(AssociationSetEnd end) { return GetEntityType(end.CorrespondingAssociationEndMember); } internal static EntityType GetEntityType(AssociationEndMember end) { EntityType entityType = (EntityType)((RefType)end.TypeUsage.EdmType).ElementType; return entityType; } internal static string GetQualifiedEntitySetName(EntitySet entitySet) { EntityDataSourceUtil.CheckArgumentNull(entitySet, "entitySet"); // ContainerName.EntitySetName return entitySet.EntityContainer.Name + "." + entitySet.Name; } internal static string QuoteEntitySqlIdentifier(string identifier) { return "[" + (identifier ?? string.Empty).Replace("]", "]]") + "]"; } internal static string CreateEntitySqlTypeIdentifier(EdmType type) { // [_schema_namespace_name_].[_type_name_] // if the [_schema_namespace_name_] is null or empty, omit this part of the identifier // this can happen when the CLR type is defined outside of a namespace return (String.IsNullOrEmpty(type.NamespaceName) ? String.Empty : (QuoteEntitySqlIdentifier(type.NamespaceName) + ".")) + QuoteEntitySqlIdentifier(type.Name); } internal static string CreateEntitySqlSetIdentifier(EntitySetBase set) { // [_container_name_].[_set_name_] return QuoteEntitySqlIdentifier(set.EntityContainer.Name) + "." + QuoteEntitySqlIdentifier(set.Name); } /// /// Determines which columns to expose for the given set and type. Includes /// flattened complex properties and 'reference' keys. /// /// Used to determine 'interesting' members, or /// members whose values need to be maintained in ControlState /// Used to get CLR mapping information for EDM /// types /// The set. /// The type. /// A map from display names to columns. internal static ReadOnlyCollection GetNamedColumns(MetadataWorkspace csWorkspace, MetadataWorkspace ocWorkspace, EntitySet entitySet, EntityType entityType) { CheckArgumentNull(csWorkspace, "csWorkspace"); CheckArgumentNull(ocWorkspace, "ocWorkspace"); CheckArgumentNull(entitySet, "entitySet"); CheckArgumentNull(entityType, "entityType"); ReadOnlyCollection interestingMembers = GetInterestingMembers(csWorkspace, entitySet, entityType); IEnumerable columns = GetColumns(entitySet, entityType, ocWorkspace, interestingMembers); List result = new List(); // give precedence to simple named columns ( HashSet usedNames = new HashSet(); foreach (EntityDataSourceColumn column in columns) { if (!column.IsHidden) { // check that the column name has not been used if (!usedNames.Add(column.DisplayName)) { throw new InvalidOperationException(Strings.DisplayNameCollision(column.DisplayName)); } } result.Add(column); } return result.AsReadOnly(); } private static ReadOnlyCollection GetInterestingMembers(MetadataWorkspace csWorkspace, EntitySet entitySet, EntityType entityType) { // Note that this delegate is not used to determine whether reference columns are interesting. They // are intrinsically interesting and do not appear in this set. HashSet interestingMembers = new HashSet( csWorkspace.GetRelevantMembersForUpdate(entitySet, entityType, true)); // keys are also interesting... foreach (EdmMember keyMember in entityType.KeyMembers) { interestingMembers.Add(keyMember); } ReadOnlyCollection result = interestingMembers.ToList().AsReadOnly(); return result; } private static IEnumerable GetColumns(EntitySet entitySet, EntityType entityType, MetadataWorkspace ocWorkspace, ReadOnlyCollection interestingMembers) { List columns = new List(); // Primitive and complex properties EntityDataSourceMemberPath parent = null; // top-level properties are not qualified Dictionary entityProperties = AddPropertyColumns(columns, ocWorkspace, parent, entityType.Properties, interestingMembers); // Navigation reference properties AddReferenceNavigationColumns(columns, ocWorkspace, entitySet, entityType); // Reference key properties AddReferenceKeyColumns(columns, ocWorkspace, entitySet, entityType, entityProperties); return columns; } // Adds element to 'columns' for every element of 'properties'. Also returns a map from properties // at this level to the corresponding columns. private static Dictionary AddPropertyColumns(List columns, MetadataWorkspace ocWorkspace, EntityDataSourceMemberPath parent, IEnumerable properties, ReadOnlyCollection interestingMembers) { Dictionary result = new Dictionary(); foreach (EdmProperty property in properties) { bool isLocallyInteresting = interestingMembers.Contains(property); EntityDataSourceMemberPath prefix = new EntityDataSourceMemberPath(ocWorkspace, parent, property, isLocallyInteresting); EdmType propertyType = property.TypeUsage.EdmType; // add column for this entity property EntityDataSourcePropertyColumn propertyColumn = new EntityDataSourcePropertyColumn(prefix); columns.Add(propertyColumn); result.Add(property, propertyColumn); if (propertyType.BuiltInTypeKind == BuiltInTypeKind.ComplexType) { // add nested properties // prepend the property name to the members of the complex type AddPropertyColumns(columns, ocWorkspace, prefix, ((ComplexType)propertyType).Properties, interestingMembers); } // other property types are not currently supported (or possible in EF V1 for that matter) } return result; } private static void AddReferenceNavigationColumns(List columns, MetadataWorkspace ocWorkspace, EntitySet entitySet, EntityType entityType) { foreach (AssociationSetEnd toEnd in GetReferenceEnds(entitySet, entityType, /*forKey*/false)) { // Check for a navigation property NavigationProperty navigationProperty; if (TryGetCorrespondingNavigationProperty(toEnd.CorrespondingAssociationEndMember, out navigationProperty)) { Type clrToType = EntityDataSourceUtil.GetMemberClrType(ocWorkspace, navigationProperty); EntityDataSourceReferenceValueColumn column = EntityDataSourceReferenceValueColumn.Create(clrToType, ocWorkspace, navigationProperty); columns.Add(column); } } } private static void AddReferenceKeyColumns(List columns, MetadataWorkspace ocWorkspace, EntitySet entitySet, EntityType entityType, Dictionary entityProperties) { foreach (AssociationSetEnd toEnd in GetReferenceEnds(entitySet, entityType, /*forKey*/true)) { ReferentialConstraint constraint; bool isContained = EntityDataSourceUtil.IsContained(toEnd, out constraint); // Create a group for the end columns EntityType toType = EntityDataSourceUtil.GetEntityType(toEnd); Type clrToType = EntityDataSourceUtil.GetClrType(ocWorkspace, toType); EntityDataSourceReferenceGroup group = EntityDataSourceReferenceGroup.Create(clrToType, toEnd); // Create a column for every key foreach (EdmProperty keyMember in GetEntityType(toEnd).KeyMembers) { EntityDataSourceColumn controllingColumn = null; if (isContained) { // if this key is 'contained' in the entity, make the referential constrained // property the principal for the column int ordinalInConstraint = constraint.FromProperties.IndexOf(keyMember); // find corresponding member in the current (dependent) entity EdmProperty correspondingProperty = constraint.ToProperties[ordinalInConstraint]; controllingColumn = entityProperties[correspondingProperty]; } columns.Add(new EntityDataSourceReferenceKeyColumn(ocWorkspace, group, keyMember, controllingColumn)); } } } internal static void ValidateKeyPropertyValuesExist(EntityDataSourceWrapper entityWrapper, Dictionary propertyValues) { foreach (var keyProperty in entityWrapper.Collection.AllPropertyDescriptors.Select(d => d.Column).OfType().Where(c => c.IsKey)) { if (!propertyValues.ContainsKey(keyProperty.DisplayName)) { throw new EntityDataSourceValidationException(Strings.EntityDataSourceView_NoKeyProperty); } } } static private readonly Type StackOverflowType = typeof(System.StackOverflowException); static private readonly Type OutOfMemoryType = typeof(System.OutOfMemoryException); static private readonly Type ThreadAbortType = typeof(System.Threading.ThreadAbortException); static private readonly Type NullReferenceType = typeof(System.NullReferenceException); static private readonly Type AccessViolationType = typeof(System.AccessViolationException); static private readonly Type SecurityType = typeof(System.Security.SecurityException); static private readonly Type AppDomainUnloadedType = typeof(System.AppDomainUnloadedException); static private readonly Type CannotUnloadAppDomainType = typeof(CannotUnloadAppDomainException); static private readonly Type BadImageFormatType = typeof(BadImageFormatException); static private readonly Type InvalidProgramType = typeof(InvalidProgramException); static private bool IsCatchableExceptionType(Exception e) { // a 'catchable' exception is defined by what it is not. Debug.Assert(e != null, "Unexpected null exception!"); Type type = e.GetType(); return ((type != StackOverflowType) && (type != OutOfMemoryType) && (type != ThreadAbortType) && (type != NullReferenceType) && (type != AccessViolationType) && (type != AppDomainUnloadedType) && (type != CannotUnloadAppDomainType) && (type != BadImageFormatType) && (type != InvalidProgramType) && !SecurityType.IsAssignableFrom(type)); } } }