//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @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 { /// /// 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 /// [CLSCompliant(false)] public sealed partial class ObjectItemCollection : ItemCollection { #region Constructors /// /// The ObjectItemCollection that loads metadata from assemblies /// 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 associated with an is "true" : Specified and all referenced assemblies have been loaded // 2. The associated with an is "false" : Specified assembly loaded. Its referenced assemblies may not be loaded // 3. The 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 _ocMapping = new Dictionary(); private object _loaderCookie; private object _loadAssemblyLock = new object(); internal object LoadAssemblyLock { get { return _loadAssemblyLock; } } internal static IList ViewGenerationAssemblies { get { return AssemblyCache.ViewGenerationAssemblies; } } #endregion #region Methods internal static bool IsCompiledViewGenAttributePresent(Assembly assembly) { return assembly.IsDefined(typeof(System.Data.Mapping.EntityViewGenerationAttribute), false /*inherit*/); } /// /// 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. /// /// assembly whose dependency list we are going to traverse 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); } } } /// /// Load metadata from the given assembly /// /// The assembly from which to load metadata /// thrown if assembly argument is null public void LoadFromAssembly(Assembly assembly) { ExplicitLoadFromAssembly(assembly, null, null); } /// /// Load metadata from the given assembly /// /// The assembly from which to load metadata /// thrown if assembly argument is null [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "edm")] public void LoadFromAssembly(Assembly assembly, EdmItemCollection edmItemCollection, Action 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); } /// /// 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" /// internal void ExplicitLoadFromAssembly(Assembly assembly, EdmItemCollection edmItemCollection, Action 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); } } /// /// 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 /// internal void ImplicitLoadFromAssembly(Assembly assembly, EdmItemCollection edmItemCollection) { if (!MetadataAssemblyHelper.ShouldFilterAssembly(assembly)) { // it meets the Implicit rules Load it ExplicitLoadFromAssembly(assembly, edmItemCollection, null); } } /// /// 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. /// /// The type's assembly is loaded into the OSpace ItemCollection /// true if the type and all its generic arguments are filtered out (did not attempt to load assembly) 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>, ObjectQuery> foreach (Type t in type.GetGenericArguments()) { result |= ImplicitLoadAssemblyForType(t, edmItemCollection); } } return result; } /// /// internal static method to get the relationship name /// /// /// /// internal AssociationType GetRelationshipType(Type entityClrType, string relationshipName) { AssociationType associationType; if (TryGetItem(relationshipName, out associationType)) { return associationType; } return null; } /// /// Loads the OSpace types in the assembly and returns them as a dictionary /// /// The assembly to load /// A mapping from names to OSpace EdmTypes internal static Dictionary LoadTypesExpensiveWay(Assembly assembly) { Dictionary typesInLoading = null; List 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; } /// /// internal static method to get the relationship name /// /// /// /// internal static AssociationType GetRelationshipTypeExpensiveWay(Type entityClrType, string relationshipName) { Dictionary 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; } /// /// internal static method to get all the AssociationTypes from an assembly /// /// The assembly from which to load relationship types /// An enumeration of OSpace AssociationTypes that are present in this assembly internal static IEnumerable GetAllRelationshipTypesExpensiveWay(Assembly assembly) { Dictionary 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 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 typesInLoading; List 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 globalItems = new List(); 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; } } /// /// 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. /// /// /// 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); } } } /// /// Get the list of primitive types for the given space /// /// public IEnumerable GetPrimitiveTypes() { return _primitiveTypeMaps.GetTypes(); } /// /// 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. /// /// The OSpace type to look up /// The CLR type of the OSpace argument public Type GetClrType(StructuralType objectSpaceType) { return ObjectItemCollection.GetClrType((EdmType)objectSpaceType); } /// /// 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. /// /// The OSpace type to look up /// The CLR type of the OSpace argument /// true on success, false on failure public bool TryGetClrType(StructuralType objectSpaceType, out Type clrType) { return ObjectItemCollection.TryGetClrType((EdmType)objectSpaceType, out clrType); } /// /// 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. /// /// The OSpace type to look up /// The CLR type of the OSpace argument public Type GetClrType(EnumType objectSpaceType) { return ObjectItemCollection.GetClrType((EdmType)objectSpaceType); } /// /// 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. /// /// The OSpace enum type to look up /// The CLR enum type of the OSpace argument /// true on success, false on failure public bool TryGetClrType(EnumType objectSpaceType, out Type clrType) { return ObjectItemCollection.TryGetClrType((EdmType)objectSpaceType, out clrType); } /// /// 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. /// /// The OSpace type to look up /// The CLR type of the OSpace argument 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; } /// /// 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. /// /// The OSpace enum type to look up /// The CLR enum type of the OSpace argument /// true on success, false on failure 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; } /// /// Given the canonical primitive type, get the mapping primitive type in the given dataspace /// /// canonical primitive type /// The mapped scalar type 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; } /// /// Get the OSpace type given the CSpace typename /// /// /// /// /// 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(cspaceType.Identity, out edmType); } /// /// 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. /// /// /// 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 GetItems() { return base.InternalGetItems(typeof(T)) as System.Collections.ObjectModel.ReadOnlyCollection; } #endregion } }