|
//---------------------------------------------------------------------
// <copyright file="EntityDataSourceUtil.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
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>(T value, string parameterName) where T : class
{
if (null == value)
{
ThrowArgumentNullException(parameterName);
}
return value;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="propertyName"></param>
/// <param name="wrapperCollection"></param>
/// <param name="entitySet"></param>
/// <param name="tu"></param>
/// <returns></returns>
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<EdmMember> 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;
}
/// <summary>
/// Returns the value set onto the Parameter named by propertyName.
/// If the Paramter does not have a value, it returns null.
/// </summary>
/// <param name="propertyName"></param>
/// <param name="parameterCollection"></param>
/// <param name="entityDataSource"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Get the System.Web.UI.WebControls.Parameter that matches the name in the given ParameterCollection
/// </summary>
/// <param name="propertyName"></param>
/// <param name="parameterCollection"></param>
/// <returns></returns>
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;
}
/// <summary>
/// Validates that the keys in the update parameters all match property names on the entityWrapper.
/// </summary>
/// <param name="entityWrapper"></param>
/// <param name="parameters"></param>
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()));
}
}
}
}
/// <summary>
/// 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.
/// </summary>
/// <param name="typeUsage">The TypeUsage from the query</param>
/// <param name="itemCollection"></param>
/// <returns></returns>
internal static void CheckNonPolymorphicTypeUsage(EntityType entityType,
ItemCollection ocItemCollection,
string entityTypeFilter)
{
CheckArgumentNull<ItemCollection>(ocItemCollection, "ocItemCollection");
if (String.IsNullOrEmpty(entityTypeFilter))
{
List<EdmType> types = new List<EdmType>(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<EdmType> GetTypeAndSubtypesOf(EntityType type, ReadOnlyCollection<GlobalItem> itemCollection, bool includeAbstractTypes)
{
if (includeAbstractTypes || !type.Abstract)
{
yield return type;
}
// Get entity sub-types
foreach (EdmType subType in GetTypeAndSubtypesOf<EntityType>(type, itemCollection, includeAbstractTypes))
{
yield return subType;
}
// Get complex sub-types
foreach (EdmType subType in GetTypeAndSubtypesOf<ComplexType>(type, itemCollection, includeAbstractTypes))
{
yield return subType;
}
}
internal static bool IsTypeOrSubtypeOf(EntityType superType, EntityType derivedType, ReadOnlyCollection<GlobalItem> 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[] { });
}
/// <summary>
/// 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.
/// </summary>
/// <param name="columnName">Column name for which we produce a value expression.</param>
/// <returns>Entity-SQL for column.</returns>
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()));
}
}
/// <summary>
/// Converts a DB type code to a CLR type, bypassing CLR type codes since there
/// is not a sufficient mapping.
/// </summary>
/// <param name="dbType">The DB type to convert</param>
/// <returns>The mapped CLR type</returns>
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<EdmType> GetTypeAndSubtypesOf<T_EdmType>(EdmType type, ReadOnlyCollection<GlobalItem> 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<T_EdmType> typesInWorkSpace = itemCollection.OfType<T_EdmType>();
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;
}
/// <summary>
/// Converts the string representation to DbGeography instance. A return value indicates if the conversion succeeded.
/// </summary>
/// <param name="stringValue">A geography string to convert.</param>
/// <param name="result">If the conversion succeeds an instance of DbGeometry type created from <paramref name="result"/>; otherwise null.</param>
/// <returns>true if <paramref name="stringValue"/> was converted successfully; otherwise false;</returns>
/// <remarks>The <paramref name="stringValue"/> must be in the format returned by <see cref="DbGeometry.AsText()"/> method.</remarks>
private static bool TryParseGeography(string stringValue, out DbGeography result)
{
return TryParseGeo<DbGeography>(stringValue, (geometryText, srid) => DbGeography.FromText(geometryText, srid), out result);
}
/// <summary>
/// Converts the string representation to DbGeometry instance. A return value indicates if the conversion succeeded.
/// </summary>
/// <param name="stringValue">A geometry string to convert.</param>
/// <param name="result">If the conversion succeeds an instance of DbGeometry type created from <paramref name="result"/>; otherwise null.</param>
/// <returns>true if <paramref name="stringValue"/> was converted successfully; otherwise false;</returns>
/// <remarks>The <paramref name="stringValue"/> must be in the format returned by <see cref="DbGeometry.ToString()"/> method.</remarks>
private static bool TryParseGeometry(string stringValue, out DbGeometry result)
{
return TryParseGeo<DbGeometry>(stringValue, (geometryText, srid) => DbGeometry.FromText(geometryText, srid), out result);
}
/// <summary>
/// Converts the string representation to DbGeometry or DbGeography instance. A return value indicates if the conversion succeeded.
/// </summary>
/// <typeparam name="T">Type to convert the <paramref name="stringValue"/>. Must be either DbGeometry or DbGeography.</typeparam>
/// <param name="stringValue">A geometry string to convert.</param>
/// <param name="createSpatialTypeInstanceFunc">Function invoked to create an instance of type T given SRID and geo text.</param>
/// <param name="result">If the conversion succeeds an instance of DbGeometry or DbGeography type created from <paramref name="result"/>; otherwise null.</param>
/// <returns>true if <paramref name="stringValue"/> was converted successfully; otherwise false;</returns>
/// <remarks>The <paramref name="stringValue"/> must be in the format returned by .ToString() method of T.</remarks>
private static bool TryParseGeo<T>(string stringValue, Func<string, int, T> 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;
}
/// <summary>
/// Retrieves SRID and geo text from <paramref name="stringValue"/> in format "SRID=4326;POINT (100 100)" .
/// </summary>
/// <param name="stringValue">String to parse.</param>
/// <param name="srid">SRID retrieved from <paramref name="stringValue"/>.</param>
/// <param name="geoText">Geo text retrieved from <paramref name="stringValue"/>.</param>
/// <returns>true if it was possible to retrieve both SRID and geo text; otherwise false.</returns>
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<string, object> changedProperties,
bool overwrite)
{
Dictionary<string, Exception> 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);
}
}
/// <summary>
/// Get the Clr type for the primitive enum or complex type member. The member must not be null.
/// </summary>
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;
}
/// <summary>
/// Returns the collection of AssociationSetEnds for the relationships for this entity
/// </summary>
/// <param name="entitySet"></param>
/// <param name="entityType"></param>
/// <param name="forKey">If true, returns only the other ends with multiplicity 1. Ignores 1:0..1 relationships.</param>
/// <returns></returns>
internal static IEnumerable<AssociationSetEnd> GetReferenceEnds(EntitySet entitySet, EntityType entityType, bool forKey)
{
foreach (AssociationSet associationSet in entitySet.EntityContainer.BaseEntitySets.OfType<AssociationSet>())
{
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;
}
}
}
/// <summary>
/// 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)
/// </summary>
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();
}
/// <summary>
/// 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.)
/// </summary>
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);
}
/// <summary>
/// Determines which columns to expose for the given set and type. Includes
/// flattened complex properties and 'reference' keys.
/// </summary>
/// <param name="csWorkspace">Used to determine 'interesting' members, or
/// members whose values need to be maintained in ControlState</param>
/// <param name="ocWorkspace">Used to get CLR mapping information for EDM
/// types</param>
/// <param name="entitySet">The set.</param>
/// <param name="entityType">The type.</param>
/// <returns>A map from display names to columns.</returns>
internal static ReadOnlyCollection<EntityDataSourceColumn> GetNamedColumns(MetadataWorkspace csWorkspace, MetadataWorkspace ocWorkspace,
EntitySet entitySet, EntityType entityType)
{
CheckArgumentNull(csWorkspace, "csWorkspace");
CheckArgumentNull(ocWorkspace, "ocWorkspace");
CheckArgumentNull(entitySet, "entitySet");
CheckArgumentNull(entityType, "entityType");
ReadOnlyCollection<EdmMember> interestingMembers = GetInterestingMembers(csWorkspace, entitySet, entityType);
IEnumerable<EntityDataSourceColumn> columns = GetColumns(entitySet, entityType, ocWorkspace, interestingMembers);
List<EntityDataSourceColumn> result = new List<EntityDataSourceColumn>();
// give precedence to simple named columns (
HashSet<string> usedNames = new HashSet<string>();
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<EdmMember> 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<EdmMember> interestingMembers = new HashSet<EdmMember>(
csWorkspace.GetRelevantMembersForUpdate(entitySet, entityType, true));
// keys are also interesting...
foreach (EdmMember keyMember in entityType.KeyMembers)
{
interestingMembers.Add(keyMember);
}
ReadOnlyCollection<EdmMember> result = interestingMembers.ToList().AsReadOnly();
return result;
}
private static IEnumerable<EntityDataSourceColumn> GetColumns(EntitySet entitySet, EntityType entityType,
MetadataWorkspace ocWorkspace, ReadOnlyCollection<EdmMember> interestingMembers)
{
List<EntityDataSourceColumn> columns = new List<EntityDataSourceColumn>();
// Primitive and complex properties
EntityDataSourceMemberPath parent = null; // top-level properties are not qualified
Dictionary<EdmProperty, EntityDataSourcePropertyColumn> 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<EdmProperty, EntityDataSourcePropertyColumn> AddPropertyColumns(List<EntityDataSourceColumn> columns, MetadataWorkspace ocWorkspace, EntityDataSourceMemberPath parent, IEnumerable<EdmProperty> properties, ReadOnlyCollection<EdmMember> interestingMembers)
{
Dictionary<EdmProperty, EntityDataSourcePropertyColumn> result = new Dictionary<EdmProperty, EntityDataSourcePropertyColumn>();
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<EntityDataSourceColumn> 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<EntityDataSourceColumn> columns, MetadataWorkspace ocWorkspace, EntitySet entitySet, EntityType entityType, Dictionary<EdmProperty, EntityDataSourcePropertyColumn> 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<string, object> propertyValues)
{
foreach (var keyProperty in entityWrapper.Collection.AllPropertyDescriptors.Select(d => d.Column).OfType<EntityDataSourcePropertyColumn>().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));
}
}
}
|