1 //---------------------------------------------------------------------
2 // <copyright file="ObjectItemCollection.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
8 //---------------------------------------------------------------------
10 using System.Collections.Generic;
11 // Using an alias for this because a lot of names in this namespace conflicts with names in metadata
12 using System.Data.Entity;
13 using System.Diagnostics;
14 using System.Reflection;
16 namespace System.Data.Metadata.Edm
19 /// Class for representing a collection of items for the object layer.
20 /// Most of the implemetation for actual maintainance of the collection is
21 /// done by ItemCollection
24 public sealed partial class ObjectItemCollection : ItemCollection
29 /// The ObjectItemCollection that loads metadata from assemblies
31 public ObjectItemCollection()
32 : base(DataSpace.OSpace)
34 foreach (PrimitiveType type in ClrProviderManifest.Instance.GetStoreTypes())
37 _primitiveTypeMaps.Add(type);
44 // Cache for primitive type maps for Edm to provider
45 private readonly CacheForPrimitiveTypes _primitiveTypeMaps = new CacheForPrimitiveTypes();
47 // Used for tracking the loading of an assembly and its referenced assemblies. Though the value of an entry is bool, the logic represented
48 // by an entry is tri-state, the third state represented by a "missing" entry. To summarize:
49 // 1. The <value> associated with an <entry> is "true" : Specified and all referenced assemblies have been loaded
50 // 2. The <value> associated with an <entry> is "false" : Specified assembly loaded. Its referenced assemblies may not be loaded
51 // 3. The <entry> is missing : Specified assembly has not been loaded
52 private KnownAssembliesSet _knownAssemblies = new KnownAssembliesSet();
54 // Dictionary which keeps tracks of oc mapping information - the key is the conceptual name of the type
55 // and the value is the reference to the ospace type
56 private Dictionary<string, EdmType> _ocMapping = new Dictionary<string, EdmType>();
58 private object _loaderCookie;
59 private object _loadAssemblyLock = new object();
61 internal object LoadAssemblyLock
65 return _loadAssemblyLock;
69 internal static IList<Assembly> ViewGenerationAssemblies
73 return AssemblyCache.ViewGenerationAssemblies;
82 internal static bool IsCompiledViewGenAttributePresent(Assembly assembly)
84 return assembly.IsDefined(typeof(System.Data.Mapping.EntityViewGenerationAttribute), false /*inherit*/);
88 /// The method loads the O-space metadata for all the referenced assemblies starting from the given assembly
89 /// in a recursive way.
90 /// The assembly should be from Assembly.GetCallingAssembly via one of our public API's.
92 /// <param name="assembly">assembly whose dependency list we are going to traverse</param>
93 internal void ImplicitLoadAllReferencedAssemblies(Assembly assembly, EdmItemCollection edmItemCollection)
95 if (!MetadataAssemblyHelper.ShouldFilterAssembly(assembly))
97 bool loadAllReferencedAssemblies = true;
98 LoadAssemblyFromCache(this, assembly, loadAllReferencedAssemblies, edmItemCollection, null);
102 internal void ImplicitLoadViewsFromAllReferencedAssemblies(Assembly assembly)
104 // we filter implicit loads
105 if (MetadataAssemblyHelper.ShouldFilterAssembly(assembly))
111 CollectIfViewGenAssembly(assembly);
113 foreach (Assembly referenceAssembly in MetadataAssemblyHelper.GetNonSystemReferencedAssemblies(assembly))
115 CollectIfViewGenAssembly(referenceAssembly);
121 /// Load metadata from the given assembly
123 /// <param name="assembly">The assembly from which to load metadata</param>
124 /// <exception cref="System.ArgumentNullException">thrown if assembly argument is null</exception>
125 public void LoadFromAssembly(Assembly assembly)
127 ExplicitLoadFromAssembly(assembly, null, null);
131 /// Load metadata from the given assembly
133 /// <param name="assembly">The assembly from which to load metadata</param>
134 /// <exception cref="System.ArgumentNullException">thrown if assembly argument is null</exception>
135 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "edm")]
136 public void LoadFromAssembly(Assembly assembly, EdmItemCollection edmItemCollection, Action<String> logLoadMessage)
138 EntityUtil.CheckArgumentNull(assembly, "assembly");
139 EntityUtil.CheckArgumentNull(edmItemCollection, "edmItemCollection");
140 EntityUtil.CheckArgumentNull(logLoadMessage, "logLoadMessage");
142 ExplicitLoadFromAssembly(assembly, edmItemCollection, logLoadMessage);
145 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "edm")]
146 public void LoadFromAssembly(Assembly assembly, EdmItemCollection edmItemCollection)
148 EntityUtil.CheckArgumentNull(assembly, "assembly");
149 EntityUtil.CheckArgumentNull(edmItemCollection, "edmItemCollection");
151 ExplicitLoadFromAssembly(assembly, edmItemCollection, null);
154 /// Explicit loading means that the user specifically asked us to load this assembly.
155 /// We won't do any filtering, they "know what they are doing"
157 internal void ExplicitLoadFromAssembly(Assembly assembly, EdmItemCollection edmItemCollection, Action<String> logLoadMessage)
159 LoadAssemblyFromCache(this, assembly, false /*loadAllReferencedAssemblies*/, edmItemCollection, logLoadMessage);
160 //Since User called LoadFromAssembly, so we should collect the generated views if present
161 //even if the schema attribute is not present
162 if (IsCompiledViewGenAttributePresent(assembly) && !ObjectItemAttributeAssemblyLoader.IsSchemaAttributePresent(assembly))
164 CollectIfViewGenAssembly(assembly);
169 /// Implicit loading means that we are trying to help the user find the right
170 /// assembly, but they didn't explicitly ask for it. Our Implicit rules require that
171 /// we filter out assemblies with the Ecma or MicrosoftPublic PublicKeyToken on them
173 internal void ImplicitLoadFromAssembly(Assembly assembly, EdmItemCollection edmItemCollection)
175 if (!MetadataAssemblyHelper.ShouldFilterAssembly(assembly))
177 // it meets the Implicit rules Load it
178 ExplicitLoadFromAssembly(assembly, edmItemCollection, null);
183 /// Implicit loading means that we are trying to help the user find the right
184 /// assembly, but they didn't explicitly ask for it. Our Implicit rules require that
185 /// we filter out assemblies with the Ecma or MicrosoftPublic PublicKeyToken on them
187 /// Load metadata from the type's assembly.
189 /// <param name="type">The type's assembly is loaded into the OSpace ItemCollection</param>
190 /// <returns>true if the type and all its generic arguments are filtered out (did not attempt to load assembly)</returns>
191 internal bool ImplicitLoadAssemblyForType(Type type, EdmItemCollection edmItemCollection)
195 if (!MetadataAssemblyHelper.ShouldFilterAssembly(type.Assembly))
197 // InternalLoadFromAssembly will check _knownAssemblies
198 result = LoadAssemblyFromCache(this, type.Assembly, false /*loadAllReferencedAssemblies*/, edmItemCollection, null);
205 if (type.IsGenericType)
207 // recursively load all generic types
208 // interesting code paths are ObjectQuery<Nullable<Int32>>, ObjectQuery<IEnumerable<Product>>
209 foreach (Type t in type.GetGenericArguments())
211 result |= ImplicitLoadAssemblyForType(t, edmItemCollection);
218 /// internal static method to get the relationship name
220 /// <param name="clrType"></param>
221 /// <param name="relationshipName"></param>
222 /// <returns></returns>
223 internal AssociationType GetRelationshipType(Type entityClrType, string relationshipName)
225 AssociationType associationType;
226 if (TryGetItem<AssociationType>(relationshipName, out associationType))
228 return associationType;
234 /// Loads the OSpace types in the assembly and returns them as a dictionary
236 /// <param name="assembly">The assembly to load</param>
237 /// <returns>A mapping from names to OSpace EdmTypes</returns>
238 internal static Dictionary<string, EdmType> LoadTypesExpensiveWay(Assembly assembly)
240 Dictionary<string, EdmType> typesInLoading = null;
242 List<EdmItemError> errors;
243 KnownAssembliesSet knownAssemblies = new KnownAssembliesSet();
245 AssemblyCache.LoadAssembly(assembly, false /*loadAllReferencedAssemblies*/,
246 knownAssemblies, out typesInLoading, out errors);
249 if (errors.Count != 0)
251 throw EntityUtil.InvalidSchemaEncountered(Helper.CombineErrorMessage(errors));
254 return typesInLoading;
258 /// internal static method to get the relationship name
260 /// <param name="clrType"></param>
261 /// <param name="relationshipName"></param>
262 /// <returns></returns>
263 internal static AssociationType GetRelationshipTypeExpensiveWay(Type entityClrType, string relationshipName)
265 Dictionary<string, EdmType> typesInLoading = LoadTypesExpensiveWay(entityClrType.Assembly);
266 if (typesInLoading != null)
269 // Look in typesInLoading for relationship type
270 if (typesInLoading.TryGetValue(relationshipName, out edmType) && Helper.IsRelationshipType(edmType))
272 return (AssociationType)edmType;
279 /// internal static method to get all the AssociationTypes from an assembly
281 /// <param name="assembly">The assembly from which to load relationship types</param>
282 /// <returns>An enumeration of OSpace AssociationTypes that are present in this assembly</returns>
283 internal static IEnumerable<AssociationType> GetAllRelationshipTypesExpensiveWay(Assembly assembly)
285 Dictionary<string, EdmType> typesInLoading = LoadTypesExpensiveWay(assembly);
286 if (typesInLoading != null)
288 // Iterate through the EdmTypes looking for AssociationTypes
289 foreach (EdmType edmType in typesInLoading.Values)
291 if (Helper.IsAssociationType(edmType))
293 yield return (AssociationType)edmType;
300 private static bool LoadAssemblyFromCache(ObjectItemCollection objectItemCollection, Assembly assembly,
301 bool loadReferencedAssemblies, EdmItemCollection edmItemCollection, Action<String> logLoadMessage)
303 // Check if its loaded in the cache - if the call is for loading referenced assemblies, make sure that all referenced
304 // assemblies are also loaded
305 KnownAssemblyEntry entry;
306 if (objectItemCollection._knownAssemblies.TryGetKnownAssembly(assembly, objectItemCollection._loaderCookie, edmItemCollection, out entry))
308 // Proceed if only we need to load the referenced assemblies and they are not loaded
309 if (loadReferencedAssemblies == false)
311 // don't say we loaded anything, unless we actually did before
312 return entry.CacheEntry.TypesInAssembly.Count != 0;
314 else if (entry.ReferencedAssembliesAreLoaded == true)
316 // this assembly was part of a all hands reference search
321 lock (objectItemCollection.LoadAssemblyLock)
323 // Check after acquiring the lock, since the known assemblies might have got modified
324 // Check if the assembly is already loaded. The reason we need to check if the assembly is already loaded, is that
325 if (objectItemCollection._knownAssemblies.TryGetKnownAssembly(assembly, objectItemCollection._loaderCookie, edmItemCollection, out entry))
327 // Proceed if only we need to load the referenced assemblies and they are not loaded
328 if (loadReferencedAssemblies == false || entry.ReferencedAssembliesAreLoaded == true)
334 Dictionary<string, EdmType> typesInLoading;
335 List<EdmItemError> errors;
336 KnownAssembliesSet knownAssemblies;
338 if (objectItemCollection != null)
340 knownAssemblies = new KnownAssembliesSet(objectItemCollection._knownAssemblies);
344 knownAssemblies = new KnownAssembliesSet();
347 // Load the assembly from the cache
348 AssemblyCache.LoadAssembly(assembly, loadReferencedAssemblies, knownAssemblies, edmItemCollection, logLoadMessage, ref objectItemCollection._loaderCookie, out typesInLoading, out errors);
350 // Throw if we have encountered errors
351 if (errors.Count != 0)
353 throw EntityUtil.InvalidSchemaEncountered(Helper.CombineErrorMessage(errors));
356 // We can encounter new assemblies, but they may not have any time in them
357 if (typesInLoading.Count != 0)
359 // No errors, so go ahead and add the types and make them readonly
360 // The existence of the loading lock tells us whether we should be thread safe or not, if we need
361 // to be thread safe, then we need to use AtomicAddRange. We don't need to actually use the lock
362 // because the caller should have done it already
363 // Recheck the assemblies added, another list is created just to match up the collection type
364 // taken in by AtomicAddRange()
365 List<GlobalItem> globalItems = new List<GlobalItem>();
366 foreach (EdmType edmType in typesInLoading.Values)
368 globalItems.Add(edmType);
370 string cspaceTypeName = "";
373 // Also populate the ocmapping information
374 if (Helper.IsEntityType(edmType))
376 cspaceTypeName = ((ClrEntityType)edmType).CSpaceTypeName;
377 objectItemCollection._ocMapping.Add(cspaceTypeName, edmType);
379 else if (Helper.IsComplexType(edmType))
381 cspaceTypeName = ((ClrComplexType)edmType).CSpaceTypeName;
382 objectItemCollection._ocMapping.Add(cspaceTypeName, edmType);
384 else if (Helper.IsEnumType(edmType))
386 cspaceTypeName = ((ClrEnumType)edmType).CSpaceTypeName;
387 objectItemCollection._ocMapping.Add(cspaceTypeName, edmType);
389 // for the rest of the types like a relationship type, we do not have oc mapping,
390 // so we don't keep that information
392 catch (ArgumentException e)
394 throw new MappingException(Strings.Mapping_CannotMapCLRTypeMultipleTimes(cspaceTypeName), e);
398 // Create a new ObjectItemCollection and add all the global items to it.
399 // Also copy all the existing items from the existing collection
400 objectItemCollection.AtomicAddRange(globalItems);
404 // Update the value of known assemblies
405 objectItemCollection._knownAssemblies = knownAssemblies;
407 foreach (Assembly loadedAssembly in knownAssemblies.Assemblies)
409 CollectIfViewGenAssembly(loadedAssembly);
412 return typesInLoading.Count != 0;
417 /// Check to see if the assembly has the custom view generation attribute AND
418 /// collect the assembly into the local list if it has cutom attribute.
420 /// <param name="assembly"></param>
421 /// <param name="viewGenAssemblies"></param>
422 private static void CollectIfViewGenAssembly(Assembly assembly)
424 if (assembly.IsDefined(typeof(System.Data.Mapping.EntityViewGenerationAttribute), false /*inherit*/))
426 if (!AssemblyCache.ViewGenerationAssemblies.Contains(assembly))
428 AssemblyCache.ViewGenerationAssemblies.Add(assembly);
434 /// Get the list of primitive types for the given space
436 /// <returns></returns>
437 public IEnumerable<PrimitiveType> GetPrimitiveTypes()
439 return _primitiveTypeMaps.GetTypes();
443 /// The method returns the underlying CLR type for the specified OSpace type argument.
444 /// If the DataSpace of the parameter is not OSpace, an ArgumentException is thrown.
446 /// <param name="objectSpaceType">The OSpace type to look up</param>
447 /// <returns>The CLR type of the OSpace argument</returns>
448 public Type GetClrType(StructuralType objectSpaceType)
450 return ObjectItemCollection.GetClrType((EdmType)objectSpaceType);
454 /// The method returns the underlying CLR type for the specified OSpace type argument.
455 /// If the DataSpace of the parameter is not OSpace, the method returns false and sets
456 /// the out parameter to null.
458 /// <param name="objectSpaceType">The OSpace type to look up</param>
459 /// <param name="clrType">The CLR type of the OSpace argument</param>
460 /// <returns>true on success, false on failure</returns>
461 public bool TryGetClrType(StructuralType objectSpaceType, out Type clrType)
463 return ObjectItemCollection.TryGetClrType((EdmType)objectSpaceType, out clrType);
467 /// The method returns the underlying CLR type for the specified OSpace type argument.
468 /// If the DataSpace of the parameter is not OSpace, an ArgumentException is thrown.
470 /// <param name="objectSpaceType">The OSpace type to look up</param>
471 /// <returns>The CLR type of the OSpace argument</returns>
472 public Type GetClrType(EnumType objectSpaceType)
474 return ObjectItemCollection.GetClrType((EdmType)objectSpaceType);
478 /// The method returns the underlying CLR type for the specified OSpace enum type argument.
479 /// If the DataSpace of the parameter is not OSpace, the method returns false and sets
480 /// the out parameter to null.
482 /// <param name="objectSpaceType">The OSpace enum type to look up</param>
483 /// <param name="clrType">The CLR enum type of the OSpace argument</param>
484 /// <returns>true on success, false on failure</returns>
485 public bool TryGetClrType(EnumType objectSpaceType, out Type clrType)
487 return ObjectItemCollection.TryGetClrType((EdmType)objectSpaceType, out clrType);
491 /// A helper method returning the underlying CLR type for the specified OSpace Enum or Structural type argument.
492 /// If the DataSpace of the parameter is not OSpace, an ArgumentException is thrown.
494 /// <param name="objectSpaceType">The OSpace type to look up</param>
495 /// <returns>The CLR type of the OSpace argument</returns>
496 private static Type GetClrType(EdmType objectSpaceType)
498 Debug.Assert(objectSpaceType == null || objectSpaceType is StructuralType || objectSpaceType is EnumType,
499 "Only enum or structural type expected");
502 if (!ObjectItemCollection.TryGetClrType(objectSpaceType, out clrType))
504 throw EntityUtil.Argument(Strings.FailedToFindClrTypeMapping(objectSpaceType.Identity));
511 /// A helper method returning the underlying CLR type for the specified OSpace enum or structural type argument.
512 /// If the DataSpace of the parameter is not OSpace, the method returns false and sets
513 /// the out parameter to null.
515 /// <param name="objectSpaceType">The OSpace enum type to look up</param>
516 /// <param name="clrType">The CLR enum type of the OSpace argument</param>
517 /// <returns>true on success, false on failure</returns>
518 private static bool TryGetClrType(EdmType objectSpaceType, out Type clrType)
520 Debug.Assert(objectSpaceType == null || objectSpaceType is StructuralType || objectSpaceType is EnumType,
521 "Only enum or structural type expected");
523 EntityUtil.CheckArgumentNull(objectSpaceType, "objectSpaceType");
525 if (objectSpaceType.DataSpace != DataSpace.OSpace)
527 throw EntityUtil.Argument(Strings.ArgumentMustBeOSpaceType, "objectSpaceType");
532 if (Helper.IsEntityType(objectSpaceType) || Helper.IsComplexType(objectSpaceType) || Helper.IsEnumType(objectSpaceType))
534 Debug.Assert(objectSpaceType is ClrEntityType || objectSpaceType is ClrComplexType || objectSpaceType is ClrEnumType,
535 "Unexpected OSpace object type.");
537 clrType = objectSpaceType.ClrType;
539 Debug.Assert(clrType != null, "ClrType property of ClrEntityType/ClrComplexType/ClrEnumType objects must not be null");
542 return clrType != null;
546 /// Given the canonical primitive type, get the mapping primitive type in the given dataspace
548 /// <param name="modelType">canonical primitive type</param>
549 /// <returns>The mapped scalar type</returns>
550 internal override PrimitiveType GetMappedPrimitiveType(PrimitiveTypeKind modelType)
552 if (Helper.IsGeometricTypeKind(modelType))
554 modelType = PrimitiveTypeKind.Geometry;
556 else if (Helper.IsGeographicTypeKind(modelType))
558 modelType = PrimitiveTypeKind.Geography;
561 PrimitiveType type = null;
562 _primitiveTypeMaps.TryGetType(modelType, null, out type);
567 /// Get the OSpace type given the CSpace typename
569 /// <param name="cspaceTypeName"></param>
570 /// <param name="ignoreCase"></param>
571 /// <param name="edmType"></param>
572 /// <returns></returns>
573 internal bool TryGetOSpaceType(EdmType cspaceType, out EdmType edmType)
575 Debug.Assert(DataSpace.CSpace == cspaceType.DataSpace, "DataSpace should be CSpace");
577 // check if there is an entity, complex type or enum type mapping with this name
578 if (Helper.IsEntityType(cspaceType) || Helper.IsComplexType(cspaceType) || Helper.IsEnumType(cspaceType))
580 return _ocMapping.TryGetValue(cspaceType.Identity, out edmType);
583 return TryGetItem<EdmType>(cspaceType.Identity, out edmType);
587 /// Given the ospace type, returns the fullname of the mapped cspace type.
588 /// Today, since we allow non-default mapping between entity type and complex type,
589 /// this is only possible for entity and complex type.
591 /// <param name="edmType"></param>
592 /// <returns></returns>
593 internal static string TryGetMappingCSpaceTypeIdentity(EdmType edmType)
595 Debug.Assert(DataSpace.OSpace == edmType.DataSpace, "DataSpace must be OSpace");
597 if (Helper.IsEntityType(edmType))
599 return ((ClrEntityType)edmType).CSpaceTypeName;
601 else if (Helper.IsComplexType(edmType))
603 return ((ClrComplexType)edmType).CSpaceTypeName;
605 else if (Helper.IsEnumType(edmType))
607 return ((ClrEnumType)edmType).CSpaceTypeName;
610 return edmType.Identity;
613 public override System.Collections.ObjectModel.ReadOnlyCollection<T> GetItems<T>()
615 return base.InternalGetItems(typeof(T)) as System.Collections.ObjectModel.ReadOnlyCollection<T>;