536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
1094 lines
48 KiB
C#
1094 lines
48 KiB
C#
//---------------------------------------------------------------------
|
|
// <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));
|
|
}
|
|
}
|
|
}
|