Update Reference Sources to .NET Framework 4.6
[mono.git] / mcs / class / referencesource / System.Data.Entity / System / Data / Metadata / ObjectLayer / ObjectItemConventionAssemblyLoader.cs
1 //---------------------------------------------------------------------
2 // <copyright file="vs.cs" company="Microsoft">
3 //      Copyright (c) Microsoft Corporation.  All rights reserved.
4 // </copyright>
5 //
6 // @owner       Microsoft
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
9
10 namespace System.Data.Metadata.Edm
11 {
12     using System.Collections.Generic;
13     using System.Data.Entity;
14     using System.Diagnostics;
15     using System.Globalization;
16     using System.Linq;
17     using System.Reflection;
18
19     internal sealed class ObjectItemConventionAssemblyLoader : ObjectItemAssemblyLoader
20     {
21         // for root entities, entities with no base type, we will additionally look 
22         // at properties on the clr base hierarchy.
23         private const BindingFlags RootEntityPropertyReflectionBindingFlags = PropertyReflectionBindingFlags & ~BindingFlags.DeclaredOnly | BindingFlags.FlattenHierarchy;
24
25         private new MutableAssemblyCacheEntry CacheEntry { get { return (MutableAssemblyCacheEntry)base.CacheEntry; } }
26         private List<Action> _referenceResolutions = new List<Action>();
27
28         internal ObjectItemConventionAssemblyLoader(Assembly assembly, ObjectItemLoadingSessionData sessionData)
29             : base(assembly, new MutableAssemblyCacheEntry(), sessionData)
30         {
31             Debug.Assert(Create == sessionData.ObjectItemAssemblyLoaderFactory, "Why is there a different factory creating this class");
32             SessionData.RegisterForLevel1PostSessionProcessing(this);
33         }
34
35         protected override void LoadTypesFromAssembly()
36         {
37             foreach (Type type in EntityUtil.GetTypesSpecial(SourceAssembly))
38             {
39                 EdmType cspaceType;
40                 if (TryGetCSpaceTypeMatch(type, out cspaceType))
41                 {
42                     if (type.IsValueType && !type.IsEnum)
43                     {
44                         SessionData.LoadMessageLogger.LogLoadMessage(Strings.Validator_OSpace_Convention_Struct(cspaceType.FullName, type.FullName), cspaceType);
45                         continue;
46                     }
47
48                     EdmType ospaceType;
49                     if (TryCreateType(type, cspaceType, out ospaceType))
50                     {
51                         Debug.Assert(ospaceType is StructuralType || Helper.IsEnumType(ospaceType), "Only StructuralType or EnumType expected.");
52
53                         CacheEntry.TypesInAssembly.Add(ospaceType);
54                         // check for duplicates so we don't cause an ArgumentException, 
55                         // Mapping will do the actual error for the duplicate type later
56                         if (!SessionData.CspaceToOspace.ContainsKey(cspaceType))
57                         {
58                             SessionData.CspaceToOspace.Add(cspaceType, ospaceType);
59                         }
60                         else
61                         {
62                             // at this point there is already a Clr Type that is structurally matched to this CSpace type, we throw exception
63                             EdmType previousOSpaceType = SessionData.CspaceToOspace[cspaceType];
64                             SessionData.EdmItemErrors.Add(
65                                 new EdmItemError(Strings.Validator_OSpace_Convention_AmbiguousClrType(cspaceType.Name, previousOSpaceType.ClrType.FullName, type.FullName), previousOSpaceType));
66                         }
67                     }
68                 }
69             }
70
71             if (SessionData.TypesInLoading.Count == 0)
72             {
73                 Debug.Assert(CacheEntry.ClosureAssemblies.Count == 0, "How did we get closure assemblies?");
74
75                 // since we didn't find any types, don't lock into convention based
76                 SessionData.ObjectItemAssemblyLoaderFactory = null;
77             }
78         }
79
80
81         protected override void AddToAssembliesLoaded()
82         {
83             SessionData.AssembliesLoaded.Add(SourceAssembly, CacheEntry);
84         }
85
86         private bool TryGetCSpaceTypeMatch(Type type, out EdmType cspaceType)
87         {
88             // brute force try and find a matching name
89             KeyValuePair<EdmType, int> pair;
90             if (SessionData.ConventionCSpaceTypeNames.TryGetValue(type.Name, out pair))
91             {
92                 if (pair.Value == 1)
93                 {
94                     // we found a type match
95                     cspaceType = pair.Key;
96                     return true;
97                 }
98                 else
99                 {
100                     Debug.Assert(pair.Value > 1, "how did we get a negative count of types in the dictionary?");
101                     SessionData.EdmItemErrors.Add(new EdmItemError(Strings.Validator_OSpace_Convention_MultipleTypesWithSameName(type.Name), pair.Key));
102                 }
103             }
104
105             cspaceType = null;
106             return false;
107         }
108
109         /// <summary>
110         /// Creates a structural or enum OSpace type based on CLR type and CSpace type.
111         /// </summary>
112         /// <param name="type">CLR type.</param>
113         /// <param name="cspaceType">CSpace Type</param>
114         /// <param name="newOSpaceType">OSpace type created based on CLR <paramref name="type"/> and <paramref name="cspaceType"/></param>
115         /// <returns><c>true</c> if the type was created successfully. Otherwise <c>false</c>.</returns>
116         private bool TryCreateType(Type type, EdmType cspaceType, out EdmType newOSpaceType)
117         {
118             Debug.Assert(type != null, "type != null");
119             Debug.Assert(cspaceType != null, "cspaceType != null");
120             Debug.Assert(cspaceType is StructuralType || Helper.IsEnumType(cspaceType), "Structural or enum type expected");
121
122             newOSpaceType = null;
123
124             // if one of the types is an enum while the other is not there is no match
125             if (Helper.IsEnumType(cspaceType) ^ type.IsEnum)
126             {
127                 SessionData.LoadMessageLogger.LogLoadMessage(
128                     Strings.Validator_OSpace_Convention_SSpaceOSpaceTypeMismatch(cspaceType.FullName, cspaceType.FullName),
129                     cspaceType);
130                 return false;
131             }
132
133             if(Helper.IsEnumType(cspaceType))
134             {
135                 return TryCreateEnumType(type, (EnumType)cspaceType, out newOSpaceType);
136             }
137             else
138             {
139                 Debug.Assert(cspaceType is StructuralType);
140                 return TryCreateStructuralType(type, (StructuralType)cspaceType, out newOSpaceType);
141             }
142         }
143
144         /// <summary>
145         /// Creates a structural OSpace type based on CLR type and CSpace type.
146         /// </summary>
147         /// <param name="type">CLR type.</param>
148         /// <param name="cspaceType">CSpace Type</param>
149         /// <param name="newOSpaceType">OSpace type created based on CLR <paramref name="type"/> and <paramref name="cspaceType"/></param>
150         /// <returns><c>true</c> if the type was created successfully. Otherwise <c>false</c>.</returns>
151         private bool TryCreateStructuralType(Type type, StructuralType cspaceType, out EdmType newOSpaceType)
152         {
153             Debug.Assert(type != null, "type != null");
154             Debug.Assert(cspaceType != null, "cspaceType != null");
155
156             List<Action> referenceResolutionListForCurrentType = new List<Action>();
157             newOSpaceType = null;
158             Debug.Assert(TypesMatchByConvention(type, cspaceType), "The types passed as parameters don't match by convention.");
159
160             StructuralType ospaceType;
161             if (Helper.IsEntityType(cspaceType))
162             {
163                 ospaceType = new ClrEntityType(type, cspaceType.NamespaceName, cspaceType.Name);
164             }
165             else
166             {
167                 Debug.Assert(Helper.IsComplexType(cspaceType), "Invalid type attribute encountered");
168                 ospaceType = new ClrComplexType(type, cspaceType.NamespaceName, cspaceType.Name);
169             }
170
171             if (cspaceType.BaseType != null)
172             {
173                 if (TypesMatchByConvention(type.BaseType, cspaceType.BaseType))
174                 {
175                     TrackClosure(type.BaseType);
176                     referenceResolutionListForCurrentType.Add(
177                         () => ospaceType.BaseType = ResolveBaseType((StructuralType)cspaceType.BaseType, type));
178                 }
179                 else
180                 {
181                     string message = Strings.Validator_OSpace_Convention_BaseTypeIncompatible(type.BaseType.FullName, type.FullName, cspaceType.BaseType.FullName);
182                     SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
183                     return false;
184                 }
185             }
186
187             // Load the properties for this type
188             if (!TryCreateMembers(type, (StructuralType)cspaceType, ospaceType, referenceResolutionListForCurrentType))
189             {
190                 return false;
191             }
192
193             // Add this to the known type map so we won't try to load it again
194             SessionData.TypesInLoading.Add(type.FullName, ospaceType);
195
196             // we only add the referenceResolution to the list unless we structrually matched this type
197             foreach (var referenceResolution in referenceResolutionListForCurrentType)
198             {
199                 this._referenceResolutions.Add(referenceResolution);
200             }
201
202             newOSpaceType = ospaceType;
203             return true;
204         }
205
206         /// <summary>
207         /// Creates new enum OSpace type built based on CLR <paramref name="enumType"/> and <paramref name="cspaceEnumType"/>
208         /// </summary>
209         /// <param name="enumType">CLR type to create OSpace type from.</param>
210         /// <param name="cspaceEnumType">CSpace type used to get namespace and name for the newly created OSpace type.</param>
211         /// <param name="newOSpaceType">
212         /// New enum OSpace type built based on CLR <paramref name="enumType"/> and <paramref name="cspaceEnumType"/> or null
213         /// if the type could not be built.
214         /// </param>
215         /// <returns><c>true</c> if the type was built successfully.<c>false</c> otherwise.</returns>
216         private bool TryCreateEnumType(Type enumType, EnumType cspaceEnumType, out EdmType newOSpaceType)
217         {
218             Debug.Assert(enumType != null, "enumType != null");
219             Debug.Assert(enumType.IsEnum, "enum type expected");
220             Debug.Assert(cspaceEnumType != null, "cspaceEnumType != null");
221             Debug.Assert(Helper.IsEnumType(cspaceEnumType), "Enum type expected");
222             Debug.Assert(TypesMatchByConvention(enumType, cspaceEnumType), "The types passed as parameters don't match by convention.");
223
224             newOSpaceType = null;
225
226             // Check if the OSpace and CSpace enum type match
227             if (!UnderlyingEnumTypesMatch(enumType, cspaceEnumType) || !EnumMembersMatch(enumType, cspaceEnumType))
228             {
229                 return false;
230             }
231
232             newOSpaceType = new ClrEnumType(enumType, cspaceEnumType.NamespaceName, cspaceEnumType.Name);
233             SessionData.TypesInLoading.Add(enumType.FullName, newOSpaceType);
234
235             return true;
236         }
237
238         /// <summary>
239         /// Verifies whether underlying types of CLR and EDM types match
240         /// </summary>
241         /// <param name="enumType">OSpace CLR enum type.</param>
242         /// <param name="cspaceEnumType">CSpace EDM enum type.</param>
243         /// <returns><c>true</c> if types match. <c>false</c> otherwise.</returns>
244         private bool UnderlyingEnumTypesMatch(Type enumType, EnumType cspaceEnumType)
245         {
246             Debug.Assert(enumType != null, "enumType != null");
247             Debug.Assert(enumType.IsEnum, "expected enum OSpace type");
248             Debug.Assert(cspaceEnumType != null, "cspaceEnumType != null");
249             Debug.Assert(Helper.IsEnumType(cspaceEnumType), "Enum type expected");
250
251             // Note that TryGetPrimitiveType() will return false not only for types that are not primitive 
252             // but also for CLR primitive types that are valid underlying enum types in CLR but are not 
253             // a valid Edm primitive types (e.g. ulong) 
254             PrimitiveType underlyingEnumType;
255             if (!ClrProviderManifest.Instance.TryGetPrimitiveType(enumType.GetEnumUnderlyingType(), out underlyingEnumType))
256             {
257                 SessionData.LoadMessageLogger.LogLoadMessage(
258                     Strings.Validator_UnsupportedEnumUnderlyingType(enumType.GetEnumUnderlyingType().FullName), 
259                     cspaceEnumType);
260
261                 return false;
262             }
263             else if (underlyingEnumType.PrimitiveTypeKind != cspaceEnumType.UnderlyingType.PrimitiveTypeKind)
264             {
265                 SessionData.LoadMessageLogger.LogLoadMessage(
266                     Strings.Validator_OSpace_Convention_NonMatchingUnderlyingTypes, cspaceEnumType);
267
268                 return false;
269             }
270
271             return true;
272         }
273
274         /// <summary>
275         /// Verifies whether enum members of CLR and EDM types match.
276         /// </summary>
277         /// <param name="enumType">OSpace CLR enum type.</param>
278         /// <param name="cspaceEnumType">CSpace EDM enum type.</param>
279         /// <returns><c>true</c> if members match. <c>false</c> otherwise.</returns>
280         private bool EnumMembersMatch(Type enumType, EnumType cspaceEnumType)
281         {
282             Debug.Assert(enumType != null, "enumType != null");
283             Debug.Assert(enumType.IsEnum, "expected enum OSpace type");
284             Debug.Assert(cspaceEnumType != null, "cspaceEnumType != null");
285             Debug.Assert(Helper.IsEnumType(cspaceEnumType), "Enum type expected");
286             Debug.Assert(cspaceEnumType.UnderlyingType.ClrEquivalentType == enumType.GetEnumUnderlyingType(), "underlying types should have already been checked");
287
288             var enumUnderlyingType = enumType.GetEnumUnderlyingType();
289
290             var cspaceSortedEnumMemberEnumerator = cspaceEnumType.Members.OrderBy(m => m.Name).GetEnumerator();
291             var ospaceSortedEnumMemberNamesEnumerator = enumType.GetEnumNames().OrderBy(n => n).GetEnumerator();
292
293             // no checks required if edm enum type does not have any members 
294             if (!cspaceSortedEnumMemberEnumerator.MoveNext())
295             {
296                 return true;
297             }
298
299             while (ospaceSortedEnumMemberNamesEnumerator.MoveNext())
300             {
301                 if (cspaceSortedEnumMemberEnumerator.Current.Name == ospaceSortedEnumMemberNamesEnumerator.Current &&
302                     cspaceSortedEnumMemberEnumerator.Current.Value.Equals(
303                         Convert.ChangeType(
304                             Enum.Parse(enumType, ospaceSortedEnumMemberNamesEnumerator.Current), enumUnderlyingType, CultureInfo.InvariantCulture)))
305                 {
306                     if (!cspaceSortedEnumMemberEnumerator.MoveNext())
307                     {
308                         return true;
309                     }
310                 }
311             }
312
313             SessionData.LoadMessageLogger.LogLoadMessage(
314                 System.Data.Entity.Strings.Mapping_Enum_OCMapping_MemberMismatch(
315                         enumType.FullName,
316                         cspaceSortedEnumMemberEnumerator.Current.Name,
317                         cspaceSortedEnumMemberEnumerator.Current.Value,
318                         cspaceEnumType.FullName), cspaceEnumType);
319                 
320             return false;
321         }
322
323         internal override void OnLevel1SessionProcessing()
324         {
325             CreateRelationships();
326
327             foreach (Action resolve in _referenceResolutions)
328             {
329                 resolve();
330             }
331
332             base.OnLevel1SessionProcessing();
333         }
334
335         private EdmType ResolveBaseType(StructuralType baseCSpaceType, Type type)
336         {
337             EdmType ospaceType;
338             bool foundValue = SessionData.CspaceToOspace.TryGetValue(baseCSpaceType, out ospaceType);
339             if (!foundValue)
340             {
341                 string message =
342                     SessionData.LoadMessageLogger.CreateErrorMessageWithTypeSpecificLoadLogs(
343                         Strings.Validator_OSpace_Convention_BaseTypeNotLoaded(type, baseCSpaceType), 
344                         baseCSpaceType);
345                 SessionData.EdmItemErrors.Add(new EdmItemError(message, ospaceType));
346             }
347
348             Debug.Assert(!foundValue || ospaceType is StructuralType, "Structural type expected (if found).");
349
350             return ospaceType;
351         }
352
353         private bool TryCreateMembers(Type type, StructuralType cspaceType, StructuralType ospaceType, List<Action> referenceResolutionListForCurrentType)
354         {
355             BindingFlags flags = cspaceType.BaseType == null ? RootEntityPropertyReflectionBindingFlags : PropertyReflectionBindingFlags;
356
357             PropertyInfo[] clrProperties = type.GetProperties(flags);
358
359             // required properties scalar properties first
360             if (!TryFindAndCreatePrimitiveProperties(type, cspaceType, ospaceType, clrProperties))
361             {
362                 return false;
363             }
364
365             if(!TryFindAndCreateEnumProperties(type, cspaceType, ospaceType, clrProperties, referenceResolutionListForCurrentType))
366             {
367                 return false;
368             }
369
370             if (!TryFindComplexProperties(type, cspaceType, ospaceType, clrProperties, referenceResolutionListForCurrentType))
371             {
372                 return false;
373             }
374
375             if (!TryFindNavigationProperties(type, cspaceType, ospaceType, clrProperties, referenceResolutionListForCurrentType))
376             {
377                 return false;
378             }
379
380             return true;
381         }
382
383         private bool TryFindComplexProperties(Type type, StructuralType cspaceType, StructuralType ospaceType, PropertyInfo[] clrProperties, List<Action> referenceResolutionListForCurrentType)
384         {
385             List<KeyValuePair<EdmProperty, PropertyInfo>> typeClosureToTrack =
386                 new List<KeyValuePair<EdmProperty, PropertyInfo>>();
387             foreach (EdmProperty cspaceProperty in cspaceType.GetDeclaredOnlyMembers<EdmProperty>().Where(m => Helper.IsComplexType(m.TypeUsage.EdmType)))
388             {
389                 PropertyInfo clrProperty = clrProperties.FirstOrDefault(p => MemberMatchesByConvention(p, cspaceProperty));
390                 if (clrProperty != null)
391                 {
392                     typeClosureToTrack.Add(
393                         new KeyValuePair<EdmProperty, PropertyInfo>(
394                             cspaceProperty, clrProperty));
395                 }
396                 else
397                 {
398                     string message = Strings.Validator_OSpace_Convention_MissingRequiredProperty(cspaceProperty.Name, type.FullName);
399                     SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
400                     return false;
401                 }
402             }
403
404             foreach (var typeToTrack in typeClosureToTrack)
405             {
406                 TrackClosure(typeToTrack.Value.PropertyType);
407                 // prevent the lifting of these closure variables
408                 var ot = ospaceType;
409                 var cp = typeToTrack.Key;
410                 var clrp = typeToTrack.Value;
411                 referenceResolutionListForCurrentType.Add(() => CreateAndAddComplexType(type, ot, cp, clrp));
412             }
413
414             return true;
415         }
416
417         private bool TryFindNavigationProperties(Type type, StructuralType cspaceType, StructuralType ospaceType, PropertyInfo[] clrProperties, List<Action> referenceResolutionListForCurrentType)
418         {
419             List<KeyValuePair<NavigationProperty, PropertyInfo>> typeClosureToTrack =
420                 new List<KeyValuePair<NavigationProperty, PropertyInfo>>();
421             foreach (NavigationProperty cspaceProperty in cspaceType.GetDeclaredOnlyMembers<NavigationProperty>())
422             {
423                 PropertyInfo clrProperty = clrProperties.FirstOrDefault(p => NonPrimitiveMemberMatchesByConvention(p, cspaceProperty));
424                 if (clrProperty != null)
425                 {
426                     bool needsSetter = cspaceProperty.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many;
427                     if (clrProperty.CanRead && (!needsSetter || clrProperty.CanWrite))
428                     {
429                         typeClosureToTrack.Add(
430                             new KeyValuePair<NavigationProperty, PropertyInfo>(
431                                 cspaceProperty, clrProperty));
432                     }
433                 }
434                 else
435                 {
436                     string message = Strings.Validator_OSpace_Convention_MissingRequiredProperty(
437                         cspaceProperty.Name, type.FullName);
438                     SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
439                     return false;
440                 }
441             }
442             
443             foreach (var typeToTrack in typeClosureToTrack)
444             {
445                 TrackClosure(typeToTrack.Value.PropertyType);
446
447                 // keep from lifting these closure variables
448                 var ct = cspaceType;
449                 var ot = ospaceType;
450                 var cp = typeToTrack.Key;
451                 var clrp = typeToTrack.Value;
452
453                 referenceResolutionListForCurrentType.Add(() => CreateAndAddNavigationProperty(ct, ot, cp, clrp));
454             }
455
456             return true;
457         }
458
459         private void TrackClosure(Type type)
460         {
461
462             if (SourceAssembly != type.Assembly && 
463                 !CacheEntry.ClosureAssemblies.Contains(type.Assembly) &&
464                 !(type.IsGenericType && 
465                   (
466                     EntityUtil.IsAnICollection(type) || // EntityCollection<>, List<>, ICollection<>
467                     type.GetGenericTypeDefinition() == typeof(System.Data.Objects.DataClasses.EntityReference<>) ||
468                     type.GetGenericTypeDefinition() == typeof(System.Nullable<>)
469                   )
470                  )
471                 )
472             {
473                 CacheEntry.ClosureAssemblies.Add(type.Assembly);
474             }
475
476             if (type.IsGenericType)
477             {
478                 foreach (Type genericArgument in type.GetGenericArguments())
479                 {
480                     TrackClosure(genericArgument);
481                 }
482             }
483         }
484
485         private void CreateAndAddComplexType(Type type, StructuralType ospaceType, EdmProperty cspaceProperty, PropertyInfo clrProperty)
486         {
487             EdmType propertyType;
488             if (SessionData.CspaceToOspace.TryGetValue((StructuralType)cspaceProperty.TypeUsage.EdmType, out propertyType))
489             {
490                 Debug.Assert(propertyType is StructuralType, "Structural type expected.");
491
492                 EdmProperty property = new EdmProperty(cspaceProperty.Name, TypeUsage.Create(propertyType, new FacetValues { Nullable = false }), clrProperty, type.TypeHandle);
493                 ospaceType.AddMember(property);
494             }
495             else
496             {
497                 string message =
498                     SessionData.LoadMessageLogger.CreateErrorMessageWithTypeSpecificLoadLogs(
499                         Strings.Validator_OSpace_Convention_MissingOSpaceType(cspaceProperty.TypeUsage.EdmType.FullName),
500                         cspaceProperty.TypeUsage.EdmType);
501                 SessionData.EdmItemErrors.Add(new EdmItemError(message, ospaceType));
502             }
503
504         }
505
506         private void CreateAndAddNavigationProperty(StructuralType cspaceType, StructuralType ospaceType, NavigationProperty cspaceProperty, PropertyInfo clrProperty)
507         {
508             EdmType ospaceRelationship;
509             if (SessionData.CspaceToOspace.TryGetValue(cspaceProperty.RelationshipType, out ospaceRelationship))
510             {
511                 Debug.Assert(ospaceRelationship is StructuralType, "Structural type expected.");
512
513                 bool foundTarget = false;
514                 EdmType targetType = null;
515                 if (Helper.IsCollectionType(cspaceProperty.TypeUsage.EdmType))
516                 {
517                     EdmType findType;
518                     foundTarget = SessionData.CspaceToOspace.TryGetValue((StructuralType)((CollectionType)cspaceProperty.TypeUsage.EdmType).TypeUsage.EdmType, out findType);
519                     if (foundTarget)
520                     {
521                         Debug.Assert(findType is StructuralType, "Structural type expected.");
522
523                         targetType = findType.GetCollectionType();
524                     }
525                 }
526                 else
527                 {
528                     EdmType findType;
529                     foundTarget = SessionData.CspaceToOspace.TryGetValue((StructuralType)cspaceProperty.TypeUsage.EdmType, out findType);
530                     if (foundTarget)
531                     {
532                         Debug.Assert(findType is StructuralType, "Structural type expected.");
533
534                         targetType = findType;
535                     }
536                 }
537
538
539                 Debug.Assert(foundTarget, "Since the relationship will only be created if it can find the types for both ends, we will never fail to find one of the ends");
540
541                 NavigationProperty navigationProperty = new NavigationProperty(cspaceProperty.Name, TypeUsage.Create(targetType), clrProperty);
542                 navigationProperty.RelationshipType = (RelationshipType)ospaceRelationship;
543
544                 // we can use First because o-space relationships are created directly from 
545                 // c-space relationship
546                 navigationProperty.ToEndMember = (RelationshipEndMember)((RelationshipType)ospaceRelationship).Members.First(e => e.Name == cspaceProperty.ToEndMember.Name);
547                 navigationProperty.FromEndMember = (RelationshipEndMember)((RelationshipType)ospaceRelationship).Members.First(e => e.Name == cspaceProperty.FromEndMember.Name);
548                 ospaceType.AddMember(navigationProperty);
549             }
550             else
551             {
552                 EntityTypeBase missingType = cspaceProperty.RelationshipType.RelationshipEndMembers.Select(e => ((RefType)e.TypeUsage.EdmType).ElementType).First(e => e != cspaceType);
553                 string message =
554                     SessionData.LoadMessageLogger.CreateErrorMessageWithTypeSpecificLoadLogs(
555                         Strings.Validator_OSpace_Convention_RelationshipNotLoaded(cspaceProperty.RelationshipType.FullName, missingType.FullName),
556                         missingType);
557                 SessionData.EdmItemErrors.Add(new EdmItemError(message, ospaceType));
558             }
559         }
560
561         private bool TryFindAndCreatePrimitiveProperties(Type type, StructuralType cspaceType, StructuralType ospaceType, PropertyInfo[] clrProperties)
562         {
563             foreach (EdmProperty cspaceProperty in cspaceType.GetDeclaredOnlyMembers<EdmProperty>().Where(p => Helper.IsPrimitiveType(p.TypeUsage.EdmType)))
564             {
565                 PropertyInfo clrProperty = clrProperties.FirstOrDefault(p => MemberMatchesByConvention(p, cspaceProperty));
566                 if (clrProperty != null)
567                 {
568                     PrimitiveType propertyType;
569                     if (TryGetPrimitiveType(clrProperty.PropertyType, out propertyType))
570                     {
571                         if (clrProperty.CanRead && clrProperty.CanWrite)
572                         {
573                             AddScalarMember(type, clrProperty, ospaceType, cspaceProperty, propertyType);
574                         }
575                         else
576                         {
577                             string message = Strings.Validator_OSpace_Convention_ScalarPropertyMissginGetterOrSetter(clrProperty.Name, type.FullName, type.Assembly.FullName);
578                             SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
579                             return false;
580                         }
581                     }
582                     else
583                     {
584                         string message = Strings.Validator_OSpace_Convention_NonPrimitiveTypeProperty(clrProperty.Name, type.FullName, clrProperty.PropertyType.FullName);
585                         SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
586                         return false;
587                     }
588                 }
589                 else
590                 {
591                     string message = Strings.Validator_OSpace_Convention_MissingRequiredProperty(cspaceProperty.Name, type.FullName);
592                     SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
593                     return false;
594                 }
595             }
596             return true;
597         }
598
599         private bool TryFindAndCreateEnumProperties(Type type, StructuralType cspaceType, StructuralType ospaceType, PropertyInfo[] clrProperties, List<Action> referenceResolutionListForCurrentType)
600         {
601             var typeClosureToTrack = new List<KeyValuePair<EdmProperty, PropertyInfo>>();
602
603             foreach (EdmProperty cspaceProperty in cspaceType.GetDeclaredOnlyMembers<EdmProperty>().Where(p => Helper.IsEnumType(p.TypeUsage.EdmType)))
604             {
605                 PropertyInfo clrProperty = clrProperties.FirstOrDefault(p => MemberMatchesByConvention(p, cspaceProperty));
606                 if (clrProperty != null)
607                 {
608                     typeClosureToTrack.Add(new KeyValuePair<EdmProperty, PropertyInfo>(cspaceProperty, clrProperty));
609                 }
610                 else
611                 {
612                     string message = Strings.Validator_OSpace_Convention_MissingRequiredProperty(cspaceProperty.Name, type.FullName);
613                     SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
614                     return false;
615                 }
616             }
617
618             foreach (var typeToTrack in typeClosureToTrack)
619             {
620                 TrackClosure(typeToTrack.Value.PropertyType);
621                 // prevent the lifting of these closure variables
622                 var ot = ospaceType;
623                 var cp = typeToTrack.Key;
624                 var clrp = typeToTrack.Value;
625                 referenceResolutionListForCurrentType.Add(() => CreateAndAddEnumProperty(type, ot, cp, clrp));
626             }
627
628             return true;
629         }
630
631         /// <summary>
632         /// Creates an Enum property based on <paramref name="clrProperty"/>and adds it to the parent structural type.
633         /// </summary>
634         /// <param name="type">CLR type owning <paramref name="clrProperty"/>.</param>
635         /// <param name="ospaceType">OSpace type the created property will be added to.</param>
636         /// <param name="cspaceProperty">Corresponding property from CSpace.</param>
637         /// <param name="clrProperty">CLR property used to build an Enum property.</param>
638         private void CreateAndAddEnumProperty(Type type, StructuralType ospaceType, EdmProperty cspaceProperty, PropertyInfo clrProperty)
639         {
640             EdmType propertyType;
641             if (SessionData.CspaceToOspace.TryGetValue(cspaceProperty.TypeUsage.EdmType, out propertyType))
642             {
643                 if (clrProperty.CanRead && clrProperty.CanWrite)
644                 {
645                     AddScalarMember(type, clrProperty, ospaceType, cspaceProperty, propertyType);
646                 }
647                 else
648                 {
649                     string message =
650                         SessionData.LoadMessageLogger.CreateErrorMessageWithTypeSpecificLoadLogs(
651                             Strings.Validator_OSpace_Convention_ScalarPropertyMissginGetterOrSetter(clrProperty.Name, type.FullName, type.Assembly.FullName),
652                             cspaceProperty.TypeUsage.EdmType);
653
654                     SessionData.EdmItemErrors.Add(new EdmItemError(message, ospaceType));
655                 }
656             }
657             else
658             {
659                 string message =
660                     SessionData.LoadMessageLogger.CreateErrorMessageWithTypeSpecificLoadLogs(
661                         Strings.Validator_OSpace_Convention_MissingOSpaceType(cspaceProperty.TypeUsage.EdmType.FullName),
662                         cspaceProperty.TypeUsage.EdmType);
663
664                 SessionData.EdmItemErrors.Add(new EdmItemError(message, ospaceType));
665             }
666         }
667
668         private void CreateRelationships()
669         {
670             if (SessionData.ConventionBasedRelationshipsAreLoaded)
671             {
672                 return;                
673             }
674             
675             
676             SessionData.ConventionBasedRelationshipsAreLoaded = true;
677
678             // find all the relationships
679             foreach (AssociationType cspaceAssociation in SessionData.EdmItemCollection.GetItems<AssociationType>())
680             {
681                 Debug.Assert(cspaceAssociation.RelationshipEndMembers.Count == 2, "Relationships are assumed to have exactly two ends");
682
683                 if (SessionData.CspaceToOspace.ContainsKey(cspaceAssociation))
684                 {
685                     // don't try to load relationships that we already know about
686                     continue;
687                 }
688
689                 EdmType[] ospaceEndTypes = new EdmType[2];
690                 if (SessionData.CspaceToOspace.TryGetValue(GetRelationshipEndType(cspaceAssociation.RelationshipEndMembers[0]), out ospaceEndTypes[0]) &&
691                     SessionData.CspaceToOspace.TryGetValue(GetRelationshipEndType(cspaceAssociation.RelationshipEndMembers[1]), out ospaceEndTypes[1]))
692                 {
693                     Debug.Assert(ospaceEndTypes[0] is StructuralType);
694                     Debug.Assert(ospaceEndTypes[1] is StructuralType);
695
696                     // if we can find both ends of the relationship, then create it
697
698                     AssociationType ospaceAssociation = new AssociationType(cspaceAssociation.Name, cspaceAssociation.NamespaceName, cspaceAssociation.IsForeignKey, DataSpace.OSpace);
699                     for (int i = 0; i < cspaceAssociation.RelationshipEndMembers.Count; i++)
700                     {
701                         EntityType ospaceEndType = (EntityType)ospaceEndTypes[i];
702                         RelationshipEndMember cspaceEnd = cspaceAssociation.RelationshipEndMembers[i];
703
704                         ospaceAssociation.AddKeyMember(new AssociationEndMember(cspaceEnd.Name, ospaceEndType.GetReferenceType(), cspaceEnd.RelationshipMultiplicity));
705                     }
706                     CacheEntry.TypesInAssembly.Add(ospaceAssociation);
707                     SessionData.TypesInLoading.Add(ospaceAssociation.FullName, ospaceAssociation);
708                     SessionData.CspaceToOspace.Add(cspaceAssociation, ospaceAssociation); 
709
710                 }
711             }
712         }
713
714         private static StructuralType GetRelationshipEndType(RelationshipEndMember relationshipEndMember)
715         {
716             return ((RefType)relationshipEndMember.TypeUsage.EdmType).ElementType;
717         }
718
719         private static bool MemberMatchesByConvention(PropertyInfo clrProperty, EdmMember cspaceMember)
720         {
721             return clrProperty.Name == cspaceMember.Name;
722         }
723
724         private static bool NonPrimitiveMemberMatchesByConvention(PropertyInfo clrProperty, EdmMember cspaceMember)
725         {
726             return !clrProperty.PropertyType.IsValueType && !clrProperty.PropertyType.IsAssignableFrom(typeof(string)) && clrProperty.Name == cspaceMember.Name;
727         }
728
729         internal static bool SessionContainsConventionParameters(ObjectItemLoadingSessionData sessionData)
730         {
731             return sessionData.EdmItemCollection != null;
732         }
733
734         internal static bool TypesMatchByConvention(Type type, EdmType cspaceType)
735         {
736             return type.Name == cspaceType.Name;
737         }
738
739         private void AddScalarMember(Type type, PropertyInfo clrProperty, StructuralType ospaceType, EdmProperty cspaceProperty, EdmType propertyType)
740         {
741             Debug.Assert(type != null, "type != null");
742             Debug.Assert(clrProperty != null, "clrProperty != null");
743             Debug.Assert(clrProperty.CanRead && clrProperty.CanWrite, "The clr property has to have a setter and a getter.");
744             Debug.Assert(ospaceType != null, "ospaceType != null");
745             Debug.Assert(cspaceProperty != null, "cspaceProperty != null");
746             Debug.Assert(propertyType != null, "propertyType != null");
747             Debug.Assert(Helper.IsScalarType(propertyType), "Property has to be primitive or enum.");
748
749             var cspaceType = cspaceProperty.DeclaringType;
750
751             bool isKeyMember = Helper.IsEntityType(cspaceType) && ((EntityType)cspaceType).KeyMemberNames.Contains(clrProperty.Name);
752
753             // the property is nullable only if it is not a key and can actually be set to null (i.e. is not a value type or is a nullable value type)
754             bool nullableFacetValue = !isKeyMember && (!clrProperty.PropertyType.IsValueType || Nullable.GetUnderlyingType(clrProperty.PropertyType) != null);
755
756             EdmProperty ospaceProperty =
757                 new EdmProperty(
758                     cspaceProperty.Name,
759                     TypeUsage.Create(propertyType, new FacetValues { Nullable = nullableFacetValue }),
760                     clrProperty,
761                     type.TypeHandle);
762
763             if (isKeyMember)
764             {
765                 ((EntityType)ospaceType).AddKeyMember(ospaceProperty);
766             }
767             else
768             {
769                 ospaceType.AddMember(ospaceProperty);
770             }
771         }
772         
773         internal static ObjectItemAssemblyLoader Create(Assembly assembly, ObjectItemLoadingSessionData sessionData)
774         {
775             if (!ObjectItemAttributeAssemblyLoader.IsSchemaAttributePresent(assembly))
776             {
777                 return new ObjectItemConventionAssemblyLoader(assembly, sessionData);
778             }
779             else
780             {
781                 // we were loading in convention mode, and ran into an assembly that can't be loaded by convention
782                 sessionData.EdmItemErrors.Add(new EdmItemError(Strings.Validator_OSpace_Convention_AttributeAssemblyReferenced(assembly.FullName), null));
783                 return new ObjectItemNoOpAssemblyLoader(assembly, sessionData);
784             }
785         }
786     }
787 }