Update Reference Sources to .NET Framework 4.6.1
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Metadata / ObjectLayer / ObjectItemAttributeAssemblyLoader.cs
1 //---------------------------------------------------------------------
2 // <copyright file="ObjectItemAttributeAssemblyLoader.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner       [....]
7 // @backupOwner [....]
8 //---------------------------------------------------------------------
9
10 namespace System.Data.Metadata.Edm
11 {
12     using System.Collections.Generic;
13     using System.Data.Entity;
14     using System.Data.Objects.DataClasses;
15     using System.Diagnostics;
16     using System.Linq;
17     using System.Reflection;
18
19     /// <summary>
20     /// Class for representing a collection of items for the object layer.
21     /// Most of the implemetation for actual maintainance of the collection is
22     /// done by ItemCollection
23     /// </summary>
24     internal sealed class ObjectItemAttributeAssemblyLoader : ObjectItemAssemblyLoader
25     {
26         #region Fields
27
28         // list of unresolved navigation properties
29         private readonly List<Action> _unresolvedNavigationProperties = new List<Action>();
30         private new MutableAssemblyCacheEntry CacheEntry { get { return (MutableAssemblyCacheEntry)base.CacheEntry; } }
31         private List<Action> _referenceResolutions = new List<Action>();
32
33         #endregion
34
35         #region Constructor
36         internal ObjectItemAttributeAssemblyLoader(Assembly assembly, ObjectItemLoadingSessionData sessionData)
37             :base(assembly, new MutableAssemblyCacheEntry(), sessionData)
38         {
39             Debug.Assert(Create == sessionData.ObjectItemAssemblyLoaderFactory, "Why is there a different factory creating this class");
40
41         }
42         #endregion
43
44         #region Methods
45
46         internal override void OnLevel1SessionProcessing()
47         {
48             foreach (Action resolve in _referenceResolutions)
49             {
50                 resolve();
51             }
52         }
53
54         internal override void OnLevel2SessionProcessing()
55         {
56             foreach (Action resolve in _unresolvedNavigationProperties)
57             {
58                 resolve();
59             }
60         }
61         /// <summary>
62         /// Loads the given assembly and all the other referencd assemblies in the cache. If the assembly was already present
63         /// then it loads from the cache
64         /// </summary>
65         /// <param name="context"></param>
66         /// <returns>true if the assembly was already loaded in the cache</returns>
67         internal override void Load()
68         {
69             Debug.Assert(IsSchemaAttributePresent(SourceAssembly), "LoadAssembly shouldn't be called with assembly having no schema attribute");
70             Debug.Assert(!SessionData.KnownAssemblies.Contains(SourceAssembly, SessionData.ObjectItemAssemblyLoaderFactory, SessionData.EdmItemCollection), "InternalLoadAssemblyFromCache: This assembly must not be present in the list of known assemblies");
71
72             base.Load();
73         }
74
75         protected override void AddToAssembliesLoaded()
76         {
77             SessionData.AssembliesLoaded.Add(SourceAssembly, CacheEntry);
78         }
79         /// <summary>
80         /// Check to see if the type is already loaded - either in the typesInLoading, or ObjectItemCollection or
81         /// in the global cache
82         /// </summary>
83         /// <param name="clrType"></param>
84         /// <param name="edmType"></param>
85         /// <returns></returns>
86         private bool TryGetLoadedType(Type clrType, out EdmType edmType)
87         {
88             if (SessionData.TypesInLoading.TryGetValue(clrType.FullName, out edmType) ||
89                 TryGetCachedEdmType(clrType, out edmType))
90             {
91                 // Check to make sure the CLR type we got is the same as the given one
92                 if (edmType.ClrType != clrType)
93                 {
94                     SessionData.EdmItemErrors.Add(new EdmItemError(System.Data.Entity.Strings.NewTypeConflictsWithExistingType(
95                                                 clrType.AssemblyQualifiedName, edmType.ClrType.AssemblyQualifiedName), edmType));
96                     edmType = null;
97                     return false;
98                 }
99                 return true;
100             }
101
102
103             // Let's check to see if this type is a ref type, a nullable type, or a collection type, these are the types that
104             // we need to take special care of them
105             if (clrType.IsGenericType)
106             {
107                 Type genericType = clrType.GetGenericTypeDefinition();
108
109                 // Try to resolve the element type into a type object
110                 EdmType elementType;
111                 if (!TryGetLoadedType(clrType.GetGenericArguments()[0], out elementType))
112                     return false;
113
114                 if (typeof(System.Collections.IEnumerable).IsAssignableFrom(clrType))
115                 {
116                     EntityType entityType = elementType as EntityType;
117                     if (entityType == null)
118                     {
119                         // return null and let the caller deal with the error handling
120                         return false;
121                     }
122                     edmType = entityType.GetCollectionType();
123                 }
124                 else
125                 {
126                     edmType = elementType;
127                 }
128
129                 return true;
130             }
131
132
133             edmType = null;
134             return false;
135         }
136         
137         private bool TryGetCachedEdmType(Type clrType, out EdmType edmType)
138         {
139             Debug.Assert(!SessionData.TypesInLoading.ContainsKey(clrType.FullName), "This should be called only after looking in typesInLoading");
140             Debug.Assert(SessionData.EdmItemErrors.Count > 0 || // had an error during loading
141                         clrType.GetCustomAttributes(typeof(EdmTypeAttribute), false /*inherit*/).Length == 0 || // not a type we track
142                         SourceAssembly != clrType.Assembly, // not from this assembly
143                         "Given that we don't have any error, if the type is part of this assembly, it should not be loaded from the cache");
144
145             ImmutableAssemblyCacheEntry immutableCacheEntry;
146             if (SessionData.LockedAssemblyCache.TryGetValue(clrType.Assembly, out immutableCacheEntry))
147             {
148                 Debug.Assert(SessionData.KnownAssemblies.Contains(clrType.Assembly, SessionData.LoaderCookie, SessionData.EdmItemCollection), "We should only be loading things directly from the cache if they are already in the collection");
149                 return immutableCacheEntry.TryGetEdmType(clrType.FullName, out edmType);
150             }
151
152             edmType = null;
153             return false;
154         }
155
156         #endregion
157         /// <summary>
158         /// Loads the set of types from the given assembly and adds it to the given list of types
159         /// </summary>
160         /// <param name="context">context containing information for loading</param>
161         protected override void LoadTypesFromAssembly()
162         {
163             Debug.Assert(CacheEntry.TypesInAssembly.Count == 0);
164
165             LoadRelationshipTypes();
166
167             // Loop through each type in the assembly and process it
168             foreach (Type type in EntityUtil.GetTypesSpecial(SourceAssembly))
169             {
170                 // If the type doesn't have the same EdmTypeAttribute defined, then it's not a special type
171                 // that we care about, skip it.
172                 if (!type.IsDefined(typeof(EdmTypeAttribute), false))
173                 {
174                     continue;
175                 }
176
177                 // Generic type is not supported, if the user attributed this generic type using EdmTypeAttribute,
178                 // then the exception message can help them better understand what is going on instead of just
179                 // failing at a much later point of OC type mapping lookup with a super generic error message
180                 if (type.IsGenericType)
181                 {
182                     SessionData.EdmItemErrors.Add(new EdmItemError(System.Data.Entity.Strings.GenericTypeNotSupported(type.FullName), null));
183                     continue;
184                 }
185
186                 // Load the metadata for this type
187                 LoadType(type);
188             }
189
190             if (_referenceResolutions.Count != 0)
191             {
192                 SessionData.RegisterForLevel1PostSessionProcessing(this);
193             }
194
195             if (_unresolvedNavigationProperties.Count != 0)
196             {
197                 SessionData.RegisterForLevel2PostSessionProcessing(this);
198             }
199         }
200
201         /// <summary>
202         /// This method loads all the relationship type that this entity takes part in
203         /// </summary>
204         /// <param name="entityType"></param>
205         /// <param name="context"></param>
206         private void LoadRelationshipTypes()
207         {
208             foreach (EdmRelationshipAttribute roleAttribute in SourceAssembly.GetCustomAttributes(typeof(EdmRelationshipAttribute), false /*inherit*/))
209             {
210                 // Check if there is an entry already with this name
211                 if (TryFindNullParametersInRelationshipAttribute(roleAttribute))
212                 {
213                     // don't give more errors for these same bad parameters
214                     continue;
215                 }
216
217                 bool errorEncountered = false;
218
219                 // return error if the role names are the same
220                 if (roleAttribute.Role1Name == roleAttribute.Role2Name)
221                 {
222                     SessionData.EdmItemErrors.Add(new EdmItemError(System.Data.Entity.Strings.SameRoleNameOnRelationshipAttribute(roleAttribute.RelationshipName, roleAttribute.Role2Name),
223                                null));
224                     errorEncountered = true;
225                 }
226
227
228                 if (!errorEncountered)
229                 {
230                     AssociationType associationType = new AssociationType(roleAttribute.RelationshipName, roleAttribute.RelationshipNamespaceName, roleAttribute.IsForeignKey, DataSpace.OSpace);
231                     SessionData.TypesInLoading.Add(associationType.FullName, associationType);
232                     TrackClosure(roleAttribute.Role1Type);
233                     TrackClosure(roleAttribute.Role2Type);
234
235                     // prevent lifting of loop vars
236                     string r1Name = roleAttribute.Role1Name;
237                     Type r1Type = roleAttribute.Role1Type;
238                     RelationshipMultiplicity r1Multiplicity = roleAttribute.Role1Multiplicity;
239                     AddTypeResolver(() =>
240                         ResolveAssociationEnd(associationType, r1Name, r1Type, r1Multiplicity));
241
242                     // prevent lifting of loop vars
243                     string r2Name = roleAttribute.Role2Name;
244                     Type r2Type = roleAttribute.Role2Type;
245                     RelationshipMultiplicity r2Multiplicity = roleAttribute.Role2Multiplicity;
246                     AddTypeResolver(() =>
247                         ResolveAssociationEnd(associationType, r2Name, r2Type, r2Multiplicity));
248
249                     // get assembly entry and add association type to the list of types in the assembly
250                     Debug.Assert(!CacheEntry.ContainsType(associationType.FullName), "Relationship type must not be present in the list of types");
251                     CacheEntry.TypesInAssembly.Add(associationType);
252                 }
253             }
254         }
255
256         private void ResolveAssociationEnd(AssociationType associationType, string roleName, Type clrType, RelationshipMultiplicity multiplicity)
257         {
258             EntityType entityType;
259             if (!TryGetRelationshipEndEntityType(clrType, out entityType))
260             {
261                 SessionData.EdmItemErrors.Add(new EdmItemError(System.Data.Entity.Strings.RoleTypeInEdmRelationshipAttributeIsInvalidType(associationType.Name, roleName, clrType),
262                            null));
263                 return;
264             }
265             associationType.AddKeyMember(new AssociationEndMember(roleName, entityType.GetReferenceType(), multiplicity));
266         }
267         /// <summary>
268         /// Load metadata of the given type - when you call this method, you should check and make sure that the type has
269         /// edm attribute. If it doesn't,we won't load the type and it will be returned as null
270         /// </summary>
271         /// <param name="clrType"></param>
272         /// <param name="context"></param>
273         /// <returns></returns>
274         private void LoadType(Type clrType)
275         {
276             Debug.Assert(clrType.Assembly == SourceAssembly, "Why are we loading a type that is not in our assembly?");
277             Debug.Assert(!SessionData.TypesInLoading.ContainsKey(clrType.FullName), "Trying to load a type that is already loaded???");
278             Debug.Assert(!clrType.IsGenericType, "Generic type is not supported");
279
280             EdmType edmType = null;
281
282             EdmTypeAttribute[] typeAttributes = (EdmTypeAttribute[])clrType.GetCustomAttributes(typeof(EdmTypeAttribute), false /*inherit*/);
283
284             // the CLR doesn't allow types to have duplicate/multiple attribute declarations
285
286             if (typeAttributes.Length != 0)
287             {
288                 if (clrType.IsNested)
289                 {
290                     SessionData.EdmItemErrors.Add(new EdmItemError(System.Data.Entity.Strings.NestedClassNotSupported(clrType.FullName, clrType.Assembly.FullName), null));
291                     return;
292                 }
293                 EdmTypeAttribute typeAttribute = typeAttributes[0];
294                 string cspaceTypeName = String.IsNullOrEmpty(typeAttribute.Name) ? clrType.Name : typeAttribute.Name;
295                 if (String.IsNullOrEmpty(typeAttribute.NamespaceName) && clrType.Namespace == null)
296                 {
297                     SessionData.EdmItemErrors.Add(new EdmItemError(Strings.Validator_TypeHasNoNamespace, edmType));
298                     return;
299                 }
300
301                 string cspaceNamespaceName = String.IsNullOrEmpty(typeAttribute.NamespaceName) ? clrType.Namespace : typeAttribute.NamespaceName;
302
303                 if (typeAttribute.GetType() == typeof(EdmEntityTypeAttribute))
304                 {
305                     edmType = new ClrEntityType(clrType, cspaceNamespaceName, cspaceTypeName);
306                 }
307                 else if(typeAttribute.GetType() == typeof(EdmComplexTypeAttribute))
308                 {
309                     edmType = new ClrComplexType(clrType, cspaceNamespaceName, cspaceTypeName);
310                 }
311                 else 
312                 {
313                     Debug.Assert(typeAttribute is EdmEnumTypeAttribute, "Invalid type attribute encountered");
314
315                     // Note that TryGetPrimitiveType() will return false not only for types that are not primitive 
316                     // but also for CLR primitive types that are valid underlying enum types in CLR but are not 
317                     // a valid Edm primitive types (e.g. ulong) 
318                     PrimitiveType underlyingEnumType;
319                     if (!ClrProviderManifest.Instance.TryGetPrimitiveType(clrType.GetEnumUnderlyingType(), out underlyingEnumType))
320                     {
321                         SessionData.EdmItemErrors.Add(
322                             new EdmItemError(
323                                 Strings.Validator_UnsupportedEnumUnderlyingType(clrType.GetEnumUnderlyingType().FullName),
324                                 edmType));
325
326                         return;
327                     }
328
329                     edmType = new ClrEnumType(clrType, cspaceNamespaceName, cspaceTypeName);
330                 }
331             }
332             else
333             {
334                 // not a type we are interested
335                 return;
336             }
337
338             Debug.Assert(!CacheEntry.ContainsType(edmType.Identity), "This type must not be already present in the list of types for this assembly");
339             // Also add this to the list of the types for this assembly
340             CacheEntry.TypesInAssembly.Add(edmType);
341
342             // Add this to the known type map so we won't try to load it again
343             SessionData.TypesInLoading.Add(clrType.FullName, edmType);
344
345             // Load properties for structural type
346             if (Helper.IsStructuralType(edmType))
347             {
348                 //Load base type only for entity type - not sure if we will allow complex type inheritance
349                 if (Helper.IsEntityType(edmType))
350                 {
351                     TrackClosure(clrType.BaseType);
352                     AddTypeResolver(
353                         () => edmType.BaseType = ResolveBaseType(clrType.BaseType));
354                 }
355
356                 // Load the properties for this type
357                 LoadPropertiesFromType((StructuralType)edmType);
358             }
359
360             return;
361         }
362
363         private void AddTypeResolver(Action resolver)
364         {
365             _referenceResolutions.Add(resolver);
366         }
367
368         private EdmType ResolveBaseType(Type type)
369         {
370             EdmType edmType;
371             if (type.GetCustomAttributes(typeof(EdmEntityTypeAttribute), false).Length > 0 && TryGetLoadedType(type, out edmType))
372             {
373                 return edmType;
374             }
375             return null;            
376         }
377
378         private bool TryFindNullParametersInRelationshipAttribute(EdmRelationshipAttribute roleAttribute)
379         {
380             if (roleAttribute.RelationshipName == null)
381             {
382                 SessionData.EdmItemErrors.Add(new EdmItemError(System.Data.Entity.Strings.NullRelationshipNameforEdmRelationshipAttribute(SourceAssembly.FullName), null));
383                 return true;
384             }
385
386             bool nullsFound = false;
387
388             if (roleAttribute.RelationshipNamespaceName == null)
389             {
390                 SessionData.EdmItemErrors.Add(new EdmItemError(System.Data.Entity.Strings.NullParameterForEdmRelationshipAttribute(
391                     "RelationshipNamespaceName", roleAttribute.RelationshipName), null));
392                 nullsFound = true;
393             }
394
395             if (roleAttribute.Role1Name == null)
396             {
397                 SessionData.EdmItemErrors.Add(new EdmItemError(System.Data.Entity.Strings.NullParameterForEdmRelationshipAttribute(
398                     "Role1Name", roleAttribute.RelationshipName), null));
399                 nullsFound = true;
400             }
401
402             if (roleAttribute.Role1Type == null)
403             {
404                 SessionData.EdmItemErrors.Add(new EdmItemError(System.Data.Entity.Strings.NullParameterForEdmRelationshipAttribute(
405                     "Role1Type", roleAttribute.RelationshipName), null));
406                 nullsFound = true;
407             }
408
409             if (roleAttribute.Role2Name == null)
410             {
411                 SessionData.EdmItemErrors.Add(new EdmItemError(System.Data.Entity.Strings.NullParameterForEdmRelationshipAttribute(
412                     "Role2Name", roleAttribute.RelationshipName), null));
413                 nullsFound = true;
414             }
415
416             if (roleAttribute.Role2Type == null)
417             {
418                 SessionData.EdmItemErrors.Add(new EdmItemError(System.Data.Entity.Strings.NullParameterForEdmRelationshipAttribute(
419                     "Role2Type", roleAttribute.RelationshipName), null));
420                 nullsFound = true;
421             }
422
423             return nullsFound;
424         }
425
426
427         private bool TryGetRelationshipEndEntityType(Type type, out EntityType entityType)
428         {
429             if (type == null)
430             {
431                 entityType = null;
432                 return false;
433             }
434
435             EdmType edmType;
436             if (!TryGetLoadedType(type, out edmType) || !Helper.IsEntityType(edmType))
437             {
438                 entityType = null;
439                 return false;
440             }
441             entityType = (EntityType)edmType;
442             return true;
443         }
444
445         /// <summary>
446         /// Load all the property metadata of the given type
447         /// </summary>
448         /// <param name="type">The CLR entity type</param>
449         /// <param name="structuralType">The type where properties are loaded</param>
450         /// <param name="context"></param>
451         private void LoadPropertiesFromType(StructuralType structuralType)
452         {
453             // Look at both public, internal, and private instanced properties declared at this type, inherited members
454             // are not looked at.  Internal and private properties are also looked at because they are also schematized fields
455             PropertyInfo[] properties = structuralType.ClrType.GetProperties(PropertyReflectionBindingFlags);
456
457             foreach (PropertyInfo property in properties)
458             {
459                 EdmMember newMember = null;
460                 bool isEntityKeyProperty = false; //used for EdmScalarProperties only
461
462                 // EdmScalarPropertyAttribute, EdmComplexPropertyAttribute and EdmRelationshipNavigationPropertyAttribute
463                 // are all EdmPropertyAttributes that we need to process. If the current property is not an EdmPropertyAttribute
464                 // we will just ignore it and skip to the next property.
465                 if (property.IsDefined(typeof(EdmRelationshipNavigationPropertyAttribute), false))
466                 {
467                     // keep the loop var from being lifted
468                     PropertyInfo pi = property;
469                     _unresolvedNavigationProperties.Add(() =>
470                             ResolveNavigationProperty(structuralType, pi));
471                 }
472                 else if (property.IsDefined(typeof(EdmScalarPropertyAttribute), false))
473                 {
474                     if ((Nullable.GetUnderlyingType(property.PropertyType) ?? property.PropertyType).IsEnum)
475                     {
476                         TrackClosure(property.PropertyType);
477                         PropertyInfo local = property;
478                         AddTypeResolver(() => ResolveEnumTypeProperty(structuralType, local));
479                     }
480                     else
481                     {
482                         newMember = LoadScalarProperty(structuralType.ClrType, property, out isEntityKeyProperty);
483                     }
484                 }
485                 else if (property.IsDefined(typeof(EdmComplexPropertyAttribute), false))
486                 {
487                     TrackClosure(property.PropertyType);
488                     // keep loop var from being lifted
489                     PropertyInfo local = property;
490                     AddTypeResolver(() => ResolveComplexTypeProperty(structuralType, local));
491                 }
492
493                 if (newMember == null)
494                 {
495                     // Property does not have one of the following attributes:
496                     //     EdmScalarPropertyAttribute, EdmComplexPropertyAttribute, EdmRelationshipNavigationPropertyAttribute
497                     // This means its an unmapped property and can be ignored.
498                     // Or there were error encountered while loading the properties
499                     continue;
500                 }
501
502                 // Add the property object to the type
503                 structuralType.AddMember(newMember);
504
505                 // Add to the entity's collection of key members
506                 // Do this here instead of in the if condition above for scalar properties because
507                 // we want to make sure the AddMember call above did not fail before updating the key members
508                 if (Helper.IsEntityType(structuralType) && isEntityKeyProperty)
509                 {
510                     ((EntityType)structuralType).AddKeyMember(newMember);
511                 }
512             }
513         }
514
515         internal void ResolveNavigationProperty(StructuralType declaringType, PropertyInfo propertyInfo)
516         {
517             Debug.Assert(propertyInfo.IsDefined(typeof(EdmRelationshipNavigationPropertyAttribute), false), "The property must have navigation property defined");
518
519             // EdmScalarPropertyAttribute, EdmComplexPropertyAttribute and EdmRelationshipNavigationPropertyAttribute
520             // are all EdmPropertyAttributes that we need to process. If the current property is not an EdmPropertyAttribute
521             // we will just ignore it and skip to the next property.
522             object[] relationshipPropertyAttributes = propertyInfo.GetCustomAttributes(typeof(EdmRelationshipNavigationPropertyAttribute), false);
523
524             Debug.Assert(relationshipPropertyAttributes.Length == 1, "There should be exactly one property for every navigation property");
525
526             // The only valid return types from navigation properties are:
527             //     (1) EntityType
528             //     (2) CollectionType containing valid EntityType
529
530             // If TryGetLoadedType returned false, it could mean that we couldn't validate any part of the type, or it could mean that it's a generic
531             // where the main generic type was validated, but the generic type parameter was not. We can't tell the difference, so just fail
532             // with the same error message in both cases. The user will have to figure out which part of the type is wrong.
533             // We can't just rely on checking for a generic because it can lead to a scenario where we report that the type parameter is invalid
534             // when really it's the main generic type. That is more confusing than reporting the full name and letting the user determine the problem.
535             EdmType propertyType;
536             if (!TryGetLoadedType(propertyInfo.PropertyType, out propertyType) || !(propertyType.BuiltInTypeKind == BuiltInTypeKind.EntityType || propertyType.BuiltInTypeKind == BuiltInTypeKind.CollectionType))
537             {
538                 // Once an error is detected the property does not need to be validated further, just add to the errors
539                 // collection and continue with the next property. The failure will cause an exception to be thrown later during validation of all of the types.
540                 SessionData.EdmItemErrors.Add(new EdmItemError(System.Data.Entity.Strings.Validator_OSpace_InvalidNavPropReturnType(propertyInfo.Name, propertyInfo.DeclaringType.FullName, propertyInfo.PropertyType.FullName), null));
541                 return;
542             }
543             // else we have a valid EntityType or CollectionType that contains EntityType. ResolveNonSchemaType enforces that a collection type
544             // must contain an EntityType, and if it doesn't, propertyType will be null here. If propertyType is EntityType or CollectionType we know it is valid
545
546             // Expecting EdmRelationshipNavigationPropertyAttribute to have AllowMultiple=False, so only look at first element in the attribute array
547
548             EdmRelationshipNavigationPropertyAttribute attribute = (EdmRelationshipNavigationPropertyAttribute)relationshipPropertyAttributes[0];
549
550             EdmMember member = null;
551             EdmType type;
552             if (SessionData.TypesInLoading.TryGetValue(attribute.RelationshipNamespaceName + "." + attribute.RelationshipName, out type) &&
553                 Helper.IsAssociationType(type))
554             {
555                 AssociationType relationshipType = (AssociationType)type;
556                 if (relationshipType != null)
557                 {
558                     // The return value of this property has been verified, so create the property now
559                     NavigationProperty navigationProperty = new NavigationProperty(propertyInfo.Name, TypeUsage.Create(propertyType), propertyInfo);
560                     navigationProperty.RelationshipType = relationshipType;
561                     member = navigationProperty;
562
563                     if (relationshipType.Members[0].Name == attribute.TargetRoleName)
564                     {
565                         navigationProperty.ToEndMember = (RelationshipEndMember)relationshipType.Members[0];
566                         navigationProperty.FromEndMember = (RelationshipEndMember)relationshipType.Members[1];
567                     }
568                     else if (relationshipType.Members[1].Name == attribute.TargetRoleName)
569                     {
570                         navigationProperty.ToEndMember = (RelationshipEndMember)relationshipType.Members[1];
571                         navigationProperty.FromEndMember = (RelationshipEndMember)relationshipType.Members[0];
572                     }
573                     else
574                     {
575                         SessionData.EdmItemErrors.Add(new EdmItemError(System.Data.Entity.Strings.TargetRoleNameInNavigationPropertyNotValid(
576                                                     propertyInfo.Name, propertyInfo.DeclaringType.FullName, attribute.TargetRoleName, attribute.RelationshipName), navigationProperty));
577                         member = null;
578                     }
579
580                     if (member != null &&
581                         ((RefType)navigationProperty.FromEndMember.TypeUsage.EdmType).ElementType.ClrType != declaringType.ClrType)
582                     {
583                         SessionData.EdmItemErrors.Add(new EdmItemError(System.Data.Entity.Strings.NavigationPropertyRelationshipEndTypeMismatch(
584                                                     declaringType.FullName,
585                                                     navigationProperty.Name,
586                                                     relationshipType.FullName,
587                                                     navigationProperty.FromEndMember.Name,
588                                                     ((RefType)navigationProperty.FromEndMember.TypeUsage.EdmType).ElementType.ClrType), navigationProperty));
589                         member = null;
590                     }
591                 }
592             }
593             else
594             {
595                 SessionData.EdmItemErrors.Add(new EdmItemError(System.Data.Entity.Strings.RelationshipNameInNavigationPropertyNotValid(
596                                             propertyInfo.Name, propertyInfo.DeclaringType.FullName, attribute.RelationshipName), declaringType));
597             }
598
599             if (member != null)
600             {
601                 declaringType.AddMember(member);
602             }
603         }
604
605
606         /// <summary>
607         /// Load the property with scalar property attribute.
608         /// Note that we pass the CLR type in because in the case where the property is declared on a generic
609         /// base class the DeclaringType of propert won't work for us and we need the real entity type instead.
610         /// </summary>
611         /// <param name="type">The CLR type of the entity</param>
612         /// <param name="property">Metadata representing the property</param>
613         /// <param name="isEntityKeyProperty">True if the property forms part of the entity's key</param>
614         /// <returns></returns>
615         private EdmMember LoadScalarProperty(Type clrType, PropertyInfo property, out bool isEntityKeyProperty)
616         {
617             Debug.Assert(property.IsDefined(typeof(EdmScalarPropertyAttribute), false), "The property must have a scalar attribute");
618             EdmMember member = null;
619             isEntityKeyProperty = false;
620
621             // Load the property type and create a new property object
622             PrimitiveType primitiveType;
623
624             // If the type could not be loaded it's definitely not a primitive type, so that's an error
625             // If it could be loaded but is not a primitive that's an error as well
626             if (!TryGetPrimitiveType(property.PropertyType, out primitiveType))
627             {
628                 // This property does not need to be validated further, just add to the errors collection and continue with the next property
629                 // This failure will cause an exception to be thrown later during validation of all of the types
630                 SessionData.EdmItemErrors.Add(new EdmItemError(System.Data.Entity.Strings.Validator_OSpace_ScalarPropertyNotPrimitive(property.Name, property.DeclaringType.FullName, property.PropertyType.FullName), null));
631             }
632             else
633             {
634                 object[] attrs = property.GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false);
635
636                 Debug.Assert(attrs.Length == 1, "Every property can exactly have one ScalarProperty Attribute");
637                 // Expecting EdmScalarPropertyAttribute to have AllowMultiple=False, so only look at first element in the attribute array
638                 isEntityKeyProperty = ((EdmScalarPropertyAttribute)attrs[0]).EntityKeyProperty;
639                 bool isNullable = ((EdmScalarPropertyAttribute)attrs[0]).IsNullable;
640
641                 member = new EdmProperty(property.Name,
642                     TypeUsage.Create(primitiveType, new FacetValues { Nullable = isNullable }),
643                     property, clrType.TypeHandle);
644
645             }
646             return member;
647         }
648
649         /// <summary>
650         /// Resolves enum type property.
651         /// </summary>
652         /// <param name="declaringType">The type to add the declared property to.</param>
653         /// <param name="clrProperty">Property to resolve.</param>
654         private void ResolveEnumTypeProperty(StructuralType declaringType, PropertyInfo clrProperty)
655         {
656             Debug.Assert(declaringType != null, "type != null");
657             Debug.Assert(clrProperty != null, "clrProperty != null");
658             Debug.Assert(
659                 (Nullable.GetUnderlyingType(clrProperty.PropertyType) ?? clrProperty.PropertyType).IsEnum, 
660                 "This method should be called for enums only");
661
662             EdmType propertyType;
663
664             if (!TryGetLoadedType(clrProperty.PropertyType, out propertyType) || !Helper.IsEnumType(propertyType))
665             {
666                 SessionData.EdmItemErrors.Add(
667                     new EdmItemError(
668                         System.Data.Entity.Strings.Validator_OSpace_ScalarPropertyNotPrimitive(
669                             clrProperty.Name,
670                             clrProperty.DeclaringType.FullName,
671                             clrProperty.PropertyType.FullName), null));
672             }
673             else
674             {
675                 var edmScalarPropertyAttribute = (EdmScalarPropertyAttribute)clrProperty.GetCustomAttributes(typeof(EdmScalarPropertyAttribute), false).Single();
676
677                 EdmProperty enumProperty = new EdmProperty(
678                     clrProperty.Name,
679                     TypeUsage.Create(propertyType, new FacetValues() { Nullable = edmScalarPropertyAttribute.IsNullable }),
680                     clrProperty,
681                     declaringType.ClrType.TypeHandle);
682
683                 declaringType.AddMember(enumProperty);
684
685                 if (declaringType.BuiltInTypeKind == BuiltInTypeKind.EntityType && edmScalarPropertyAttribute.EntityKeyProperty)
686                 {
687                     ((EntityType)declaringType).AddKeyMember(enumProperty);
688                 }
689             }
690         }
691
692         private void ResolveComplexTypeProperty(StructuralType type, PropertyInfo clrProperty)
693         {
694
695             // Load the property type and create a new property object
696             EdmType propertyType;
697             // If the type could not be loaded it's definitely not a complex type, so that's an error
698             // If it could be loaded but is not a complex type that's an error as well
699             if (!TryGetLoadedType(clrProperty.PropertyType, out propertyType) || propertyType.BuiltInTypeKind != BuiltInTypeKind.ComplexType)
700             {
701                 // This property does not need to be validated further, just add to the errors collection and continue with the next property
702                 // This failure will cause an exception to be thrown later during validation of all of the types
703                 SessionData.EdmItemErrors.Add(new EdmItemError(System.Data.Entity.Strings.Validator_OSpace_ComplexPropertyNotComplex(clrProperty.Name, clrProperty.DeclaringType.FullName, clrProperty.PropertyType.FullName), null));
704             }
705             else
706             {
707                 EdmProperty newProperty = new EdmProperty(clrProperty.Name,
708                     TypeUsage.Create(propertyType, new FacetValues { Nullable = false }),
709                     clrProperty, type.ClrType.TypeHandle);
710
711                 type.AddMember(newProperty);
712             }
713
714         }
715
716         private void TrackClosure(Type type)
717         {
718
719             if (SourceAssembly != type.Assembly &&
720                 !CacheEntry.ClosureAssemblies.Contains(type.Assembly) &&
721                 IsSchemaAttributePresent(type.Assembly) &&
722                 !(type.IsGenericType &&
723                   (
724                     EntityUtil.IsAnICollection(type) || // EntityCollection<>, List<>, ICollection<>
725                     type.GetGenericTypeDefinition() == typeof(System.Data.Objects.DataClasses.EntityReference<>) ||
726                     type.GetGenericTypeDefinition() == typeof(System.Nullable<>)
727                   )
728                  )
729                 )
730             {
731                 CacheEntry.ClosureAssemblies.Add(type.Assembly);
732             }
733
734             if (type.IsGenericType)
735             {
736                 foreach (Type genericArgument in type.GetGenericArguments())
737                 {
738                     TrackClosure(genericArgument);
739                 }
740             }
741         }
742
743         internal static bool IsSchemaAttributePresent(Assembly assembly)
744         {
745             return assembly.IsDefined(typeof(EdmSchemaAttribute), false /*inherit*/);
746         }
747
748         internal static ObjectItemAssemblyLoader Create(Assembly assembly, ObjectItemLoadingSessionData sessionData)
749         {
750             if (ObjectItemAttributeAssemblyLoader.IsSchemaAttributePresent(assembly))
751             {
752                 return new ObjectItemAttributeAssemblyLoader(assembly, sessionData);
753             }
754             else
755             {
756                 return new ObjectItemNoOpAssemblyLoader(assembly, sessionData);
757             }
758         }
759
760     }
761 }