|
//---------------------------------------------------------------------
// <copyright file="ObjectItemCollection.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
using System.Collections.Generic;
// Using an alias for this because a lot of names in this namespace conflicts with names in metadata
using System.Data.Entity;
using System.Diagnostics;
using System.Reflection;
namespace System.Data.Metadata.Edm
{
/// <summary>
/// Class for representing a collection of items for the object layer.
/// Most of the implemetation for actual maintainance of the collection is
/// done by ItemCollection
/// </summary>
[CLSCompliant(false)]
public sealed partial class ObjectItemCollection : ItemCollection
{
#region Constructors
/// <summary>
/// The ObjectItemCollection that loads metadata from assemblies
/// </summary>
public ObjectItemCollection()
: base(DataSpace.OSpace)
{
foreach (PrimitiveType type in ClrProviderManifest.Instance.GetStoreTypes())
{
AddInternal(type);
_primitiveTypeMaps.Add(type);
}
}
#endregion
#region Fields
// Cache for primitive type maps for Edm to provider
private readonly CacheForPrimitiveTypes _primitiveTypeMaps = new CacheForPrimitiveTypes();
// Used for tracking the loading of an assembly and its referenced assemblies. Though the value of an entry is bool, the logic represented
// by an entry is tri-state, the third state represented by a "missing" entry. To summarize:
// 1. The <value> associated with an <entry> is "true" : Specified and all referenced assemblies have been loaded
// 2. The <value> associated with an <entry> is "false" : Specified assembly loaded. Its referenced assemblies may not be loaded
// 3. The <entry> is missing : Specified assembly has not been loaded
private KnownAssembliesSet _knownAssemblies = new KnownAssembliesSet();
// Dictionary which keeps tracks of oc mapping information - the key is the conceptual name of the type
// and the value is the reference to the ospace type
private Dictionary<string, EdmType> _ocMapping = new Dictionary<string, EdmType>();
private object _loaderCookie;
private object _loadAssemblyLock = new object();
internal object LoadAssemblyLock
{
get
{
return _loadAssemblyLock;
}
}
internal static IList<Assembly> ViewGenerationAssemblies
{
get
{
return AssemblyCache.ViewGenerationAssemblies;
}
}
#endregion
#region Methods
internal static bool IsCompiledViewGenAttributePresent(Assembly assembly)
{
return assembly.IsDefined(typeof(System.Data.Mapping.EntityViewGenerationAttribute), false /*inherit*/);
}
/// <summary>
/// The method loads the O-space metadata for all the referenced assemblies starting from the given assembly
/// in a recursive way.
/// The assembly should be from Assembly.GetCallingAssembly via one of our public API's.
/// </summary>
/// <param name="assembly">assembly whose dependency list we are going to traverse</param>
internal void ImplicitLoadAllReferencedAssemblies(Assembly assembly, EdmItemCollection edmItemCollection)
{
if (!MetadataAssemblyHelper.ShouldFilterAssembly(assembly))
{
bool loadAllReferencedAssemblies = true;
LoadAssemblyFromCache(this, assembly, loadAllReferencedAssemblies, edmItemCollection, null);
}
}
internal void ImplicitLoadViewsFromAllReferencedAssemblies(Assembly assembly)
{
// we filter implicit loads
if (MetadataAssemblyHelper.ShouldFilterAssembly(assembly))
{
return;
}
lock (this)
{
CollectIfViewGenAssembly(assembly);
foreach (Assembly referenceAssembly in MetadataAssemblyHelper.GetNonSystemReferencedAssemblies(assembly))
{
CollectIfViewGenAssembly(referenceAssembly);
}
}
}
/// <summary>
/// Load metadata from the given assembly
/// </summary>
/// <param name="assembly">The assembly from which to load metadata</param>
/// <exception cref="System.ArgumentNullException">thrown if assembly argument is null</exception>
public void LoadFromAssembly(Assembly assembly)
{
ExplicitLoadFromAssembly(assembly, null, null);
}
/// <summary>
/// Load metadata from the given assembly
/// </summary>
/// <param name="assembly">The assembly from which to load metadata</param>
/// <exception cref="System.ArgumentNullException">thrown if assembly argument is null</exception>
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "edm")]
public void LoadFromAssembly(Assembly assembly, EdmItemCollection edmItemCollection, Action<String> logLoadMessage)
{
EntityUtil.CheckArgumentNull(assembly, "assembly");
EntityUtil.CheckArgumentNull(edmItemCollection, "edmItemCollection");
EntityUtil.CheckArgumentNull(logLoadMessage, "logLoadMessage");
ExplicitLoadFromAssembly(assembly, edmItemCollection, logLoadMessage);
}
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "edm")]
public void LoadFromAssembly(Assembly assembly, EdmItemCollection edmItemCollection)
{
EntityUtil.CheckArgumentNull(assembly, "assembly");
EntityUtil.CheckArgumentNull(edmItemCollection, "edmItemCollection");
ExplicitLoadFromAssembly(assembly, edmItemCollection, null);
}
/// <summary>
/// Explicit loading means that the user specifically asked us to load this assembly.
/// We won't do any filtering, they "know what they are doing"
/// </summary>
internal void ExplicitLoadFromAssembly(Assembly assembly, EdmItemCollection edmItemCollection, Action<String> logLoadMessage)
{
LoadAssemblyFromCache(this, assembly, false /*loadAllReferencedAssemblies*/, edmItemCollection, logLoadMessage);
//Since User called LoadFromAssembly, so we should collect the generated views if present
//even if the schema attribute is not present
if (IsCompiledViewGenAttributePresent(assembly) && !ObjectItemAttributeAssemblyLoader.IsSchemaAttributePresent(assembly))
{
CollectIfViewGenAssembly(assembly);
}
}
/// <summary>
/// Implicit loading means that we are trying to help the user find the right
/// assembly, but they didn't explicitly ask for it. Our Implicit rules require that
/// we filter out assemblies with the Ecma or MicrosoftPublic PublicKeyToken on them
/// </summary>
internal void ImplicitLoadFromAssembly(Assembly assembly, EdmItemCollection edmItemCollection)
{
if (!MetadataAssemblyHelper.ShouldFilterAssembly(assembly))
{
// it meets the Implicit rules Load it
ExplicitLoadFromAssembly(assembly, edmItemCollection, null);
}
}
/// <summary>
/// Implicit loading means that we are trying to help the user find the right
/// assembly, but they didn't explicitly ask for it. Our Implicit rules require that
/// we filter out assemblies with the Ecma or MicrosoftPublic PublicKeyToken on them
///
/// Load metadata from the type's assembly.
/// </summary>
/// <param name="type">The type's assembly is loaded into the OSpace ItemCollection</param>
/// <returns>true if the type and all its generic arguments are filtered out (did not attempt to load assembly)</returns>
internal bool ImplicitLoadAssemblyForType(Type type, EdmItemCollection edmItemCollection)
{
bool result;
if (!MetadataAssemblyHelper.ShouldFilterAssembly(type.Assembly))
{
// InternalLoadFromAssembly will check _knownAssemblies
result = LoadAssemblyFromCache(this, type.Assembly, false /*loadAllReferencedAssemblies*/, edmItemCollection, null);
}
else
{
result = false;
}
if (type.IsGenericType)
{
// recursively load all generic types
// interesting code paths are ObjectQuery<Nullable<Int32>>, ObjectQuery<IEnumerable<Product>>
foreach (Type t in type.GetGenericArguments())
{
result |= ImplicitLoadAssemblyForType(t, edmItemCollection);
}
}
return result;
}
/// <summary>
/// internal static method to get the relationship name
/// </summary>
/// <param name="clrType"></param>
/// <param name="relationshipName"></param>
/// <returns></returns>
internal AssociationType GetRelationshipType(Type entityClrType, string relationshipName)
{
AssociationType associationType;
if (TryGetItem<AssociationType>(relationshipName, out associationType))
{
return associationType;
}
return null;
}
/// <summary>
/// Loads the OSpace types in the assembly and returns them as a dictionary
/// </summary>
/// <param name="assembly">The assembly to load</param>
/// <returns>A mapping from names to OSpace EdmTypes</returns>
internal static Dictionary<string, EdmType> LoadTypesExpensiveWay(Assembly assembly)
{
Dictionary<string, EdmType> typesInLoading = null;
List<EdmItemError> errors;
KnownAssembliesSet knownAssemblies = new KnownAssembliesSet();
AssemblyCache.LoadAssembly(assembly, false /*loadAllReferencedAssemblies*/,
knownAssemblies, out typesInLoading, out errors);
// Check for errors
if (errors.Count != 0)
{
throw EntityUtil.InvalidSchemaEncountered(Helper.CombineErrorMessage(errors));
}
return typesInLoading;
}
/// <summary>
/// internal static method to get the relationship name
/// </summary>
/// <param name="clrType"></param>
/// <param name="relationshipName"></param>
/// <returns></returns>
internal static AssociationType GetRelationshipTypeExpensiveWay(Type entityClrType, string relationshipName)
{
Dictionary<string, EdmType> typesInLoading = LoadTypesExpensiveWay(entityClrType.Assembly);
if (typesInLoading != null)
{
EdmType edmType;
// Look in typesInLoading for relationship type
if (typesInLoading.TryGetValue(relationshipName, out edmType) && Helper.IsRelationshipType(edmType))
{
return (AssociationType)edmType;
}
}
return null;
}
/// <summary>
/// internal static method to get all the AssociationTypes from an assembly
/// </summary>
/// <param name="assembly">The assembly from which to load relationship types</param>
/// <returns>An enumeration of OSpace AssociationTypes that are present in this assembly</returns>
internal static IEnumerable<AssociationType> GetAllRelationshipTypesExpensiveWay(Assembly assembly)
{
Dictionary<string, EdmType> typesInLoading = LoadTypesExpensiveWay(assembly);
if (typesInLoading != null)
{
// Iterate through the EdmTypes looking for AssociationTypes
foreach (EdmType edmType in typesInLoading.Values)
{
if (Helper.IsAssociationType(edmType))
{
yield return (AssociationType)edmType;
}
}
}
yield break;
}
private static bool LoadAssemblyFromCache(ObjectItemCollection objectItemCollection, Assembly assembly,
bool loadReferencedAssemblies, EdmItemCollection edmItemCollection, Action<String> logLoadMessage)
{
// Check if its loaded in the cache - if the call is for loading referenced assemblies, make sure that all referenced
// assemblies are also loaded
KnownAssemblyEntry entry;
if (objectItemCollection._knownAssemblies.TryGetKnownAssembly(assembly, objectItemCollection._loaderCookie, edmItemCollection, out entry))
{
// Proceed if only we need to load the referenced assemblies and they are not loaded
if (loadReferencedAssemblies == false)
{
// don't say we loaded anything, unless we actually did before
return entry.CacheEntry.TypesInAssembly.Count != 0;
}
else if (entry.ReferencedAssembliesAreLoaded == true)
{
// this assembly was part of a all hands reference search
return true;
}
}
lock (objectItemCollection.LoadAssemblyLock)
{
// Check after acquiring the lock, since the known assemblies might have got modified
// Check if the assembly is already loaded. The reason we need to check if the assembly is already loaded, is that
if (objectItemCollection._knownAssemblies.TryGetKnownAssembly(assembly, objectItemCollection._loaderCookie, edmItemCollection, out entry))
{
// Proceed if only we need to load the referenced assemblies and they are not loaded
if (loadReferencedAssemblies == false || entry.ReferencedAssembliesAreLoaded == true)
{
return true;
}
}
Dictionary<string, EdmType> typesInLoading;
List<EdmItemError> errors;
KnownAssembliesSet knownAssemblies;
if (objectItemCollection != null)
{
knownAssemblies = new KnownAssembliesSet(objectItemCollection._knownAssemblies);
}
else
{
knownAssemblies = new KnownAssembliesSet();
}
// Load the assembly from the cache
AssemblyCache.LoadAssembly(assembly, loadReferencedAssemblies, knownAssemblies, edmItemCollection, logLoadMessage, ref objectItemCollection._loaderCookie, out typesInLoading, out errors);
// Throw if we have encountered errors
if (errors.Count != 0)
{
throw EntityUtil.InvalidSchemaEncountered(Helper.CombineErrorMessage(errors));
}
// We can encounter new assemblies, but they may not have any time in them
if (typesInLoading.Count != 0)
{
// No errors, so go ahead and add the types and make them readonly
// The existence of the loading lock tells us whether we should be thread safe or not, if we need
// to be thread safe, then we need to use AtomicAddRange. We don't need to actually use the lock
// because the caller should have done it already
// Recheck the assemblies added, another list is created just to match up the collection type
// taken in by AtomicAddRange()
List<GlobalItem> globalItems = new List<GlobalItem>();
foreach (EdmType edmType in typesInLoading.Values)
{
globalItems.Add(edmType);
string cspaceTypeName = "";
try
{
// Also populate the ocmapping information
if (Helper.IsEntityType(edmType))
{
cspaceTypeName = ((ClrEntityType)edmType).CSpaceTypeName;
objectItemCollection._ocMapping.Add(cspaceTypeName, edmType);
}
else if (Helper.IsComplexType(edmType))
{
cspaceTypeName = ((ClrComplexType)edmType).CSpaceTypeName;
objectItemCollection._ocMapping.Add(cspaceTypeName, edmType);
}
else if (Helper.IsEnumType(edmType))
{
cspaceTypeName = ((ClrEnumType)edmType).CSpaceTypeName;
objectItemCollection._ocMapping.Add(cspaceTypeName, edmType);
}
// for the rest of the types like a relationship type, we do not have oc mapping,
// so we don't keep that information
}
catch (ArgumentException e)
{
throw new MappingException(Strings.Mapping_CannotMapCLRTypeMultipleTimes(cspaceTypeName), e);
}
}
// Create a new ObjectItemCollection and add all the global items to it.
// Also copy all the existing items from the existing collection
objectItemCollection.AtomicAddRange(globalItems);
}
// Update the value of known assemblies
objectItemCollection._knownAssemblies = knownAssemblies;
foreach (Assembly loadedAssembly in knownAssemblies.Assemblies)
{
CollectIfViewGenAssembly(loadedAssembly);
}
return typesInLoading.Count != 0;
}
}
/// <summary>
/// Check to see if the assembly has the custom view generation attribute AND
/// collect the assembly into the local list if it has cutom attribute.
/// </summary>
/// <param name="assembly"></param>
/// <param name="viewGenAssemblies"></param>
private static void CollectIfViewGenAssembly(Assembly assembly)
{
if (assembly.IsDefined(typeof(System.Data.Mapping.EntityViewGenerationAttribute), false /*inherit*/))
{
if (!AssemblyCache.ViewGenerationAssemblies.Contains(assembly))
{
AssemblyCache.ViewGenerationAssemblies.Add(assembly);
}
}
}
/// <summary>
/// Get the list of primitive types for the given space
/// </summary>
/// <returns></returns>
public IEnumerable<PrimitiveType> GetPrimitiveTypes()
{
return _primitiveTypeMaps.GetTypes();
}
/// <summary>
/// The method returns the underlying CLR type for the specified OSpace type argument.
/// If the DataSpace of the parameter is not OSpace, an ArgumentException is thrown.
/// </summary>
/// <param name="objectSpaceType">The OSpace type to look up</param>
/// <returns>The CLR type of the OSpace argument</returns>
public Type GetClrType(StructuralType objectSpaceType)
{
return ObjectItemCollection.GetClrType((EdmType)objectSpaceType);
}
/// <summary>
/// The method returns the underlying CLR type for the specified OSpace type argument.
/// If the DataSpace of the parameter is not OSpace, the method returns false and sets
/// the out parameter to null.
/// </summary>
/// <param name="objectSpaceType">The OSpace type to look up</param>
/// <param name="clrType">The CLR type of the OSpace argument</param>
/// <returns>true on success, false on failure</returns>
public bool TryGetClrType(StructuralType objectSpaceType, out Type clrType)
{
return ObjectItemCollection.TryGetClrType((EdmType)objectSpaceType, out clrType);
}
/// <summary>
/// The method returns the underlying CLR type for the specified OSpace type argument.
/// If the DataSpace of the parameter is not OSpace, an ArgumentException is thrown.
/// </summary>
/// <param name="objectSpaceType">The OSpace type to look up</param>
/// <returns>The CLR type of the OSpace argument</returns>
public Type GetClrType(EnumType objectSpaceType)
{
return ObjectItemCollection.GetClrType((EdmType)objectSpaceType);
}
/// <summary>
/// The method returns the underlying CLR type for the specified OSpace enum type argument.
/// If the DataSpace of the parameter is not OSpace, the method returns false and sets
/// the out parameter to null.
/// </summary>
/// <param name="objectSpaceType">The OSpace enum type to look up</param>
/// <param name="clrType">The CLR enum type of the OSpace argument</param>
/// <returns>true on success, false on failure</returns>
public bool TryGetClrType(EnumType objectSpaceType, out Type clrType)
{
return ObjectItemCollection.TryGetClrType((EdmType)objectSpaceType, out clrType);
}
/// <summary>
/// A helper method returning the underlying CLR type for the specified OSpace Enum or Structural type argument.
/// If the DataSpace of the parameter is not OSpace, an ArgumentException is thrown.
/// </summary>
/// <param name="objectSpaceType">The OSpace type to look up</param>
/// <returns>The CLR type of the OSpace argument</returns>
private static Type GetClrType(EdmType objectSpaceType)
{
Debug.Assert(objectSpaceType == null || objectSpaceType is StructuralType || objectSpaceType is EnumType,
"Only enum or structural type expected");
Type clrType;
if (!ObjectItemCollection.TryGetClrType(objectSpaceType, out clrType))
{
throw EntityUtil.Argument(Strings.FailedToFindClrTypeMapping(objectSpaceType.Identity));
}
return clrType;
}
/// <summary>
/// A helper method returning the underlying CLR type for the specified OSpace enum or structural type argument.
/// If the DataSpace of the parameter is not OSpace, the method returns false and sets
/// the out parameter to null.
/// </summary>
/// <param name="objectSpaceType">The OSpace enum type to look up</param>
/// <param name="clrType">The CLR enum type of the OSpace argument</param>
/// <returns>true on success, false on failure</returns>
private static bool TryGetClrType(EdmType objectSpaceType, out Type clrType)
{
Debug.Assert(objectSpaceType == null || objectSpaceType is StructuralType || objectSpaceType is EnumType,
"Only enum or structural type expected");
EntityUtil.CheckArgumentNull(objectSpaceType, "objectSpaceType");
if (objectSpaceType.DataSpace != DataSpace.OSpace)
{
throw EntityUtil.Argument(Strings.ArgumentMustBeOSpaceType, "objectSpaceType");
}
clrType = null;
if (Helper.IsEntityType(objectSpaceType) || Helper.IsComplexType(objectSpaceType) || Helper.IsEnumType(objectSpaceType))
{
Debug.Assert(objectSpaceType is ClrEntityType || objectSpaceType is ClrComplexType || objectSpaceType is ClrEnumType,
"Unexpected OSpace object type.");
clrType = objectSpaceType.ClrType;
Debug.Assert(clrType != null, "ClrType property of ClrEntityType/ClrComplexType/ClrEnumType objects must not be null");
}
return clrType != null;
}
/// <summary>
/// Given the canonical primitive type, get the mapping primitive type in the given dataspace
/// </summary>
/// <param name="modelType">canonical primitive type</param>
/// <returns>The mapped scalar type</returns>
internal override PrimitiveType GetMappedPrimitiveType(PrimitiveTypeKind modelType)
{
if (Helper.IsGeometricTypeKind(modelType))
{
modelType = PrimitiveTypeKind.Geometry;
}
else if (Helper.IsGeographicTypeKind(modelType))
{
modelType = PrimitiveTypeKind.Geography;
}
PrimitiveType type = null;
_primitiveTypeMaps.TryGetType(modelType, null, out type);
return type;
}
/// <summary>
/// Get the OSpace type given the CSpace typename
/// </summary>
/// <param name="cspaceTypeName"></param>
/// <param name="ignoreCase"></param>
/// <param name="edmType"></param>
/// <returns></returns>
internal bool TryGetOSpaceType(EdmType cspaceType, out EdmType edmType)
{
Debug.Assert(DataSpace.CSpace == cspaceType.DataSpace, "DataSpace should be CSpace");
// check if there is an entity, complex type or enum type mapping with this name
if (Helper.IsEntityType(cspaceType) || Helper.IsComplexType(cspaceType) || Helper.IsEnumType(cspaceType))
{
return _ocMapping.TryGetValue(cspaceType.Identity, out edmType);
}
return TryGetItem<EdmType>(cspaceType.Identity, out edmType);
}
/// <summary>
/// Given the ospace type, returns the fullname of the mapped cspace type.
/// Today, since we allow non-default mapping between entity type and complex type,
/// this is only possible for entity and complex type.
/// </summary>
/// <param name="edmType"></param>
/// <returns></returns>
internal static string TryGetMappingCSpaceTypeIdentity(EdmType edmType)
{
Debug.Assert(DataSpace.OSpace == edmType.DataSpace, "DataSpace must be OSpace");
if (Helper.IsEntityType(edmType))
{
return ((ClrEntityType)edmType).CSpaceTypeName;
}
else if (Helper.IsComplexType(edmType))
{
return ((ClrComplexType)edmType).CSpaceTypeName;
}
else if (Helper.IsEnumType(edmType))
{
return ((ClrEnumType)edmType).CSpaceTypeName;
}
return edmType.Identity;
}
public override System.Collections.ObjectModel.ReadOnlyCollection<T> GetItems<T>()
{
return base.InternalGetItems(typeof(T)) as System.Collections.ObjectModel.ReadOnlyCollection<T>;
}
#endregion
}
}
|