File: System\Data\Metadata\ObjectLayer\ObjectItemConventionAssemblyLoader.cs
Project: ndp\fx\src\DataEntity\System.Data.Entity.csproj (System.Data.Entity)
//---------------------------------------------------------------------
// <copyright file="vs.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
 
namespace System.Data.Metadata.Edm
{
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Diagnostics;
    using System.Globalization;
    using System.Linq;
    using System.Reflection;
 
    internal sealed class ObjectItemConventionAssemblyLoader : ObjectItemAssemblyLoader
    {
        // for root entities, entities with no base type, we will additionally look 
        // at properties on the clr base hierarchy.
        private const BindingFlags RootEntityPropertyReflectionBindingFlags = PropertyReflectionBindingFlags & ~BindingFlags.DeclaredOnly | BindingFlags.FlattenHierarchy;
 
        private new MutableAssemblyCacheEntry CacheEntry { get { return (MutableAssemblyCacheEntry)base.CacheEntry; } }
        private List<Action> _referenceResolutions = new List<Action>();
 
        internal ObjectItemConventionAssemblyLoader(Assembly assembly, ObjectItemLoadingSessionData sessionData)
            : base(assembly, new MutableAssemblyCacheEntry(), sessionData)
        {
            Debug.Assert(Create == sessionData.ObjectItemAssemblyLoaderFactory, "Why is there a different factory creating this class");
            SessionData.RegisterForLevel1PostSessionProcessing(this);
        }
 
        protected override void LoadTypesFromAssembly()
        {
            foreach (Type type in EntityUtil.GetTypesSpecial(SourceAssembly))
            {
                EdmType cspaceType;
                if (TryGetCSpaceTypeMatch(type, out cspaceType))
                {
                    if (type.IsValueType && !type.IsEnum)
                    {
                        SessionData.LoadMessageLogger.LogLoadMessage(Strings.Validator_OSpace_Convention_Struct(cspaceType.FullName, type.FullName), cspaceType);
                        continue;
                    }
 
                    EdmType ospaceType;
                    if (TryCreateType(type, cspaceType, out ospaceType))
                    {
                        Debug.Assert(ospaceType is StructuralType || Helper.IsEnumType(ospaceType), "Only StructuralType or EnumType expected.");
 
                        CacheEntry.TypesInAssembly.Add(ospaceType);
                        // check for duplicates so we don't cause an ArgumentException, 
                        // Mapping will do the actual error for the duplicate type later
                        if (!SessionData.CspaceToOspace.ContainsKey(cspaceType))
                        {
                            SessionData.CspaceToOspace.Add(cspaceType, ospaceType);
                        }
                        else
                        {
                            // at this point there is already a Clr Type that is structurally matched to this CSpace type, we throw exception
                            EdmType previousOSpaceType = SessionData.CspaceToOspace[cspaceType];
                            SessionData.EdmItemErrors.Add(
                                new EdmItemError(Strings.Validator_OSpace_Convention_AmbiguousClrType(cspaceType.Name, previousOSpaceType.ClrType.FullName, type.FullName), previousOSpaceType));
                        }
                    }
                }
            }
 
            if (SessionData.TypesInLoading.Count == 0)
            {
                Debug.Assert(CacheEntry.ClosureAssemblies.Count == 0, "How did we get closure assemblies?");
 
                // since we didn't find any types, don't lock into convention based
                SessionData.ObjectItemAssemblyLoaderFactory = null;
            }
        }
 
 
        protected override void AddToAssembliesLoaded()
        {
            SessionData.AssembliesLoaded.Add(SourceAssembly, CacheEntry);
        }
 
        private bool TryGetCSpaceTypeMatch(Type type, out EdmType cspaceType)
        {
            // brute force try and find a matching name
            KeyValuePair<EdmType, int> pair;
            if (SessionData.ConventionCSpaceTypeNames.TryGetValue(type.Name, out pair))
            {
                if (pair.Value == 1)
                {
                    // we found a type match
                    cspaceType = pair.Key;
                    return true;
                }
                else
                {
                    Debug.Assert(pair.Value > 1, "how did we get a negative count of types in the dictionary?");
                    SessionData.EdmItemErrors.Add(new EdmItemError(Strings.Validator_OSpace_Convention_MultipleTypesWithSameName(type.Name), pair.Key));
                }
            }
 
            cspaceType = null;
            return false;
        }
 
        /// <summary>
        /// Creates a structural or enum OSpace type based on CLR type and CSpace type.
        /// </summary>
        /// <param name="type">CLR type.</param>
        /// <param name="cspaceType">CSpace Type</param>
        /// <param name="newOSpaceType">OSpace type created based on CLR <paramref name="type"/> and <paramref name="cspaceType"/></param>
        /// <returns><c>true</c> if the type was created successfully. Otherwise <c>false</c>.</returns>
        private bool TryCreateType(Type type, EdmType cspaceType, out EdmType newOSpaceType)
        {
            Debug.Assert(type != null, "type != null");
            Debug.Assert(cspaceType != null, "cspaceType != null");
            Debug.Assert(cspaceType is StructuralType || Helper.IsEnumType(cspaceType), "Structural or enum type expected");
 
            newOSpaceType = null;
 
            // if one of the types is an enum while the other is not there is no match
            if (Helper.IsEnumType(cspaceType) ^ type.IsEnum)
            {
                SessionData.LoadMessageLogger.LogLoadMessage(
                    Strings.Validator_OSpace_Convention_SSpaceOSpaceTypeMismatch(cspaceType.FullName, cspaceType.FullName),
                    cspaceType);
                return false;
            }
 
            if(Helper.IsEnumType(cspaceType))
            {
                return TryCreateEnumType(type, (EnumType)cspaceType, out newOSpaceType);
            }
            else
            {
                Debug.Assert(cspaceType is StructuralType);
                return TryCreateStructuralType(type, (StructuralType)cspaceType, out newOSpaceType);
            }
        }
 
        /// <summary>
        /// Creates a structural OSpace type based on CLR type and CSpace type.
        /// </summary>
        /// <param name="type">CLR type.</param>
        /// <param name="cspaceType">CSpace Type</param>
        /// <param name="newOSpaceType">OSpace type created based on CLR <paramref name="type"/> and <paramref name="cspaceType"/></param>
        /// <returns><c>true</c> if the type was created successfully. Otherwise <c>false</c>.</returns>
        private bool TryCreateStructuralType(Type type, StructuralType cspaceType, out EdmType newOSpaceType)
        {
            Debug.Assert(type != null, "type != null");
            Debug.Assert(cspaceType != null, "cspaceType != null");
 
            List<Action> referenceResolutionListForCurrentType = new List<Action>();
            newOSpaceType = null;
            Debug.Assert(TypesMatchByConvention(type, cspaceType), "The types passed as parameters don't match by convention.");
 
            StructuralType ospaceType;
            if (Helper.IsEntityType(cspaceType))
            {
                ospaceType = new ClrEntityType(type, cspaceType.NamespaceName, cspaceType.Name);
            }
            else
            {
                Debug.Assert(Helper.IsComplexType(cspaceType), "Invalid type attribute encountered");
                ospaceType = new ClrComplexType(type, cspaceType.NamespaceName, cspaceType.Name);
            }
 
            if (cspaceType.BaseType != null)
            {
                if (TypesMatchByConvention(type.BaseType, cspaceType.BaseType))
                {
                    TrackClosure(type.BaseType);
                    referenceResolutionListForCurrentType.Add(
                        () => ospaceType.BaseType = ResolveBaseType((StructuralType)cspaceType.BaseType, type));
                }
                else
                {
                    string message = Strings.Validator_OSpace_Convention_BaseTypeIncompatible(type.BaseType.FullName, type.FullName, cspaceType.BaseType.FullName);
                    SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
                    return false;
                }
            }
 
            // Load the properties for this type
            if (!TryCreateMembers(type, (StructuralType)cspaceType, ospaceType, referenceResolutionListForCurrentType))
            {
                return false;
            }
 
            // Add this to the known type map so we won't try to load it again
            SessionData.TypesInLoading.Add(type.FullName, ospaceType);
 
            // we only add the referenceResolution to the list unless we structrually matched this type
            foreach (var referenceResolution in referenceResolutionListForCurrentType)
            {
                this._referenceResolutions.Add(referenceResolution);
            }
 
            newOSpaceType = ospaceType;
            return true;
        }
 
        /// <summary>
        /// Creates new enum OSpace type built based on CLR <paramref name="enumType"/> and <paramref name="cspaceEnumType"/>
        /// </summary>
        /// <param name="enumType">CLR type to create OSpace type from.</param>
        /// <param name="cspaceEnumType">CSpace type used to get namespace and name for the newly created OSpace type.</param>
        /// <param name="newOSpaceType">
        /// New enum OSpace type built based on CLR <paramref name="enumType"/> and <paramref name="cspaceEnumType"/> or null
        /// if the type could not be built.
        /// </param>
        /// <returns><c>true</c> if the type was built successfully.<c>false</c> otherwise.</returns>
        private bool TryCreateEnumType(Type enumType, EnumType cspaceEnumType, out EdmType newOSpaceType)
        {
            Debug.Assert(enumType != null, "enumType != null");
            Debug.Assert(enumType.IsEnum, "enum type expected");
            Debug.Assert(cspaceEnumType != null, "cspaceEnumType != null");
            Debug.Assert(Helper.IsEnumType(cspaceEnumType), "Enum type expected");
            Debug.Assert(TypesMatchByConvention(enumType, cspaceEnumType), "The types passed as parameters don't match by convention.");
 
            newOSpaceType = null;
 
            // Check if the OSpace and CSpace enum type match
            if (!UnderlyingEnumTypesMatch(enumType, cspaceEnumType) || !EnumMembersMatch(enumType, cspaceEnumType))
            {
                return false;
            }
 
            newOSpaceType = new ClrEnumType(enumType, cspaceEnumType.NamespaceName, cspaceEnumType.Name);
            SessionData.TypesInLoading.Add(enumType.FullName, newOSpaceType);
 
            return true;
        }
 
        /// <summary>
        /// Verifies whether underlying types of CLR and EDM types match
        /// </summary>
        /// <param name="enumType">OSpace CLR enum type.</param>
        /// <param name="cspaceEnumType">CSpace EDM enum type.</param>
        /// <returns><c>true</c> if types match. <c>false</c> otherwise.</returns>
        private bool UnderlyingEnumTypesMatch(Type enumType, EnumType cspaceEnumType)
        {
            Debug.Assert(enumType != null, "enumType != null");
            Debug.Assert(enumType.IsEnum, "expected enum OSpace type");
            Debug.Assert(cspaceEnumType != null, "cspaceEnumType != null");
            Debug.Assert(Helper.IsEnumType(cspaceEnumType), "Enum type expected");
 
            // Note that TryGetPrimitiveType() will return false not only for types that are not primitive 
            // but also for CLR primitive types that are valid underlying enum types in CLR but are not 
            // a valid Edm primitive types (e.g. ulong) 
            PrimitiveType underlyingEnumType;
            if (!ClrProviderManifest.Instance.TryGetPrimitiveType(enumType.GetEnumUnderlyingType(), out underlyingEnumType))
            {
                SessionData.LoadMessageLogger.LogLoadMessage(
                    Strings.Validator_UnsupportedEnumUnderlyingType(enumType.GetEnumUnderlyingType().FullName), 
                    cspaceEnumType);
 
                return false;
            }
            else if (underlyingEnumType.PrimitiveTypeKind != cspaceEnumType.UnderlyingType.PrimitiveTypeKind)
            {
                SessionData.LoadMessageLogger.LogLoadMessage(
                    Strings.Validator_OSpace_Convention_NonMatchingUnderlyingTypes, cspaceEnumType);
 
                return false;
            }
 
            return true;
        }
 
        /// <summary>
        /// Verifies whether enum members of CLR and EDM types match.
        /// </summary>
        /// <param name="enumType">OSpace CLR enum type.</param>
        /// <param name="cspaceEnumType">CSpace EDM enum type.</param>
        /// <returns><c>true</c> if members match. <c>false</c> otherwise.</returns>
        private bool EnumMembersMatch(Type enumType, EnumType cspaceEnumType)
        {
            Debug.Assert(enumType != null, "enumType != null");
            Debug.Assert(enumType.IsEnum, "expected enum OSpace type");
            Debug.Assert(cspaceEnumType != null, "cspaceEnumType != null");
            Debug.Assert(Helper.IsEnumType(cspaceEnumType), "Enum type expected");
            Debug.Assert(cspaceEnumType.UnderlyingType.ClrEquivalentType == enumType.GetEnumUnderlyingType(), "underlying types should have already been checked");
 
            var enumUnderlyingType = enumType.GetEnumUnderlyingType();
 
            var cspaceSortedEnumMemberEnumerator = cspaceEnumType.Members.OrderBy(m => m.Name).GetEnumerator();
            var ospaceSortedEnumMemberNamesEnumerator = enumType.GetEnumNames().OrderBy(n => n).GetEnumerator();
 
            // no checks required if edm enum type does not have any members 
            if (!cspaceSortedEnumMemberEnumerator.MoveNext())
            {
                return true;
            }
 
            while (ospaceSortedEnumMemberNamesEnumerator.MoveNext())
            {
                if (cspaceSortedEnumMemberEnumerator.Current.Name == ospaceSortedEnumMemberNamesEnumerator.Current &&
                    cspaceSortedEnumMemberEnumerator.Current.Value.Equals(
                        Convert.ChangeType(
                            Enum.Parse(enumType, ospaceSortedEnumMemberNamesEnumerator.Current), enumUnderlyingType, CultureInfo.InvariantCulture)))
                {
                    if (!cspaceSortedEnumMemberEnumerator.MoveNext())
                    {
                        return true;
                    }
                }
            }
 
            SessionData.LoadMessageLogger.LogLoadMessage(
                System.Data.Entity.Strings.Mapping_Enum_OCMapping_MemberMismatch(
                        enumType.FullName,
                        cspaceSortedEnumMemberEnumerator.Current.Name,
                        cspaceSortedEnumMemberEnumerator.Current.Value,
                        cspaceEnumType.FullName), cspaceEnumType);
                
            return false;
        }
 
        internal override void OnLevel1SessionProcessing()
        {
            CreateRelationships();
 
            foreach (Action resolve in _referenceResolutions)
            {
                resolve();
            }
 
            base.OnLevel1SessionProcessing();
        }
 
        private EdmType ResolveBaseType(StructuralType baseCSpaceType, Type type)
        {
            EdmType ospaceType;
            bool foundValue = SessionData.CspaceToOspace.TryGetValue(baseCSpaceType, out ospaceType);
            if (!foundValue)
            {
                string message =
                    SessionData.LoadMessageLogger.CreateErrorMessageWithTypeSpecificLoadLogs(
                        Strings.Validator_OSpace_Convention_BaseTypeNotLoaded(type, baseCSpaceType), 
                        baseCSpaceType);
                SessionData.EdmItemErrors.Add(new EdmItemError(message, ospaceType));
            }
 
            Debug.Assert(!foundValue || ospaceType is StructuralType, "Structural type expected (if found).");
 
            return ospaceType;
        }
 
        private bool TryCreateMembers(Type type, StructuralType cspaceType, StructuralType ospaceType, List<Action> referenceResolutionListForCurrentType)
        {
            BindingFlags flags = cspaceType.BaseType == null ? RootEntityPropertyReflectionBindingFlags : PropertyReflectionBindingFlags;
 
            PropertyInfo[] clrProperties = type.GetProperties(flags);
 
            // required properties scalar properties first
            if (!TryFindAndCreatePrimitiveProperties(type, cspaceType, ospaceType, clrProperties))
            {
                return false;
            }
 
            if(!TryFindAndCreateEnumProperties(type, cspaceType, ospaceType, clrProperties, referenceResolutionListForCurrentType))
            {
                return false;
            }
 
            if (!TryFindComplexProperties(type, cspaceType, ospaceType, clrProperties, referenceResolutionListForCurrentType))
            {
                return false;
            }
 
            if (!TryFindNavigationProperties(type, cspaceType, ospaceType, clrProperties, referenceResolutionListForCurrentType))
            {
                return false;
            }
 
            return true;
        }
 
        private bool TryFindComplexProperties(Type type, StructuralType cspaceType, StructuralType ospaceType, PropertyInfo[] clrProperties, List<Action> referenceResolutionListForCurrentType)
        {
            List<KeyValuePair<EdmProperty, PropertyInfo>> typeClosureToTrack =
                new List<KeyValuePair<EdmProperty, PropertyInfo>>();
            foreach (EdmProperty cspaceProperty in cspaceType.GetDeclaredOnlyMembers<EdmProperty>().Where(m => Helper.IsComplexType(m.TypeUsage.EdmType)))
            {
                PropertyInfo clrProperty = clrProperties.FirstOrDefault(p => MemberMatchesByConvention(p, cspaceProperty));
                if (clrProperty != null)
                {
                    typeClosureToTrack.Add(
                        new KeyValuePair<EdmProperty, PropertyInfo>(
                            cspaceProperty, clrProperty));
                }
                else
                {
                    string message = Strings.Validator_OSpace_Convention_MissingRequiredProperty(cspaceProperty.Name, type.FullName);
                    SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
                    return false;
                }
            }
 
            foreach (var typeToTrack in typeClosureToTrack)
            {
                TrackClosure(typeToTrack.Value.PropertyType);
                // prevent the lifting of these closure variables
                var ot = ospaceType;
                var cp = typeToTrack.Key;
                var clrp = typeToTrack.Value;
                referenceResolutionListForCurrentType.Add(() => CreateAndAddComplexType(type, ot, cp, clrp));
            }
 
            return true;
        }
 
        private bool TryFindNavigationProperties(Type type, StructuralType cspaceType, StructuralType ospaceType, PropertyInfo[] clrProperties, List<Action> referenceResolutionListForCurrentType)
        {
            List<KeyValuePair<NavigationProperty, PropertyInfo>> typeClosureToTrack =
                new List<KeyValuePair<NavigationProperty, PropertyInfo>>();
            foreach (NavigationProperty cspaceProperty in cspaceType.GetDeclaredOnlyMembers<NavigationProperty>())
            {
                PropertyInfo clrProperty = clrProperties.FirstOrDefault(p => NonPrimitiveMemberMatchesByConvention(p, cspaceProperty));
                if (clrProperty != null)
                {
                    bool needsSetter = cspaceProperty.ToEndMember.RelationshipMultiplicity != RelationshipMultiplicity.Many;
                    if (clrProperty.CanRead && (!needsSetter || clrProperty.CanWrite))
                    {
                        typeClosureToTrack.Add(
                            new KeyValuePair<NavigationProperty, PropertyInfo>(
                                cspaceProperty, clrProperty));
                    }
                }
                else
                {
                    string message = Strings.Validator_OSpace_Convention_MissingRequiredProperty(
                        cspaceProperty.Name, type.FullName);
                    SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
                    return false;
                }
            }
            
            foreach (var typeToTrack in typeClosureToTrack)
            {
                TrackClosure(typeToTrack.Value.PropertyType);
 
                // keep from lifting these closure variables
                var ct = cspaceType;
                var ot = ospaceType;
                var cp = typeToTrack.Key;
                var clrp = typeToTrack.Value;
 
                referenceResolutionListForCurrentType.Add(() => CreateAndAddNavigationProperty(ct, ot, cp, clrp));
            }
 
            return true;
        }
 
        private void TrackClosure(Type type)
        {
 
            if (SourceAssembly != type.Assembly && 
                !CacheEntry.ClosureAssemblies.Contains(type.Assembly) &&
                !(type.IsGenericType && 
                  (
                    EntityUtil.IsAnICollection(type) || // EntityCollection<>, List<>, ICollection<>
                    type.GetGenericTypeDefinition() == typeof(System.Data.Objects.DataClasses.EntityReference<>) ||
                    type.GetGenericTypeDefinition() == typeof(System.Nullable<>)
                  )
                 )
                )
            {
                CacheEntry.ClosureAssemblies.Add(type.Assembly);
            }
 
            if (type.IsGenericType)
            {
                foreach (Type genericArgument in type.GetGenericArguments())
                {
                    TrackClosure(genericArgument);
                }
            }
        }
 
        private void CreateAndAddComplexType(Type type, StructuralType ospaceType, EdmProperty cspaceProperty, PropertyInfo clrProperty)
        {
            EdmType propertyType;
            if (SessionData.CspaceToOspace.TryGetValue((StructuralType)cspaceProperty.TypeUsage.EdmType, out propertyType))
            {
                Debug.Assert(propertyType is StructuralType, "Structural type expected.");
 
                EdmProperty property = new EdmProperty(cspaceProperty.Name, TypeUsage.Create(propertyType, new FacetValues { Nullable = false }), clrProperty, type.TypeHandle);
                ospaceType.AddMember(property);
            }
            else
            {
                string message =
                    SessionData.LoadMessageLogger.CreateErrorMessageWithTypeSpecificLoadLogs(
                        Strings.Validator_OSpace_Convention_MissingOSpaceType(cspaceProperty.TypeUsage.EdmType.FullName),
                        cspaceProperty.TypeUsage.EdmType);
                SessionData.EdmItemErrors.Add(new EdmItemError(message, ospaceType));
            }
 
        }
 
        private void CreateAndAddNavigationProperty(StructuralType cspaceType, StructuralType ospaceType, NavigationProperty cspaceProperty, PropertyInfo clrProperty)
        {
            EdmType ospaceRelationship;
            if (SessionData.CspaceToOspace.TryGetValue(cspaceProperty.RelationshipType, out ospaceRelationship))
            {
                Debug.Assert(ospaceRelationship is StructuralType, "Structural type expected.");
 
                bool foundTarget = false;
                EdmType targetType = null;
                if (Helper.IsCollectionType(cspaceProperty.TypeUsage.EdmType))
                {
                    EdmType findType;
                    foundTarget = SessionData.CspaceToOspace.TryGetValue((StructuralType)((CollectionType)cspaceProperty.TypeUsage.EdmType).TypeUsage.EdmType, out findType);
                    if (foundTarget)
                    {
                        Debug.Assert(findType is StructuralType, "Structural type expected.");
 
                        targetType = findType.GetCollectionType();
                    }
                }
                else
                {
                    EdmType findType;
                    foundTarget = SessionData.CspaceToOspace.TryGetValue((StructuralType)cspaceProperty.TypeUsage.EdmType, out findType);
                    if (foundTarget)
                    {
                        Debug.Assert(findType is StructuralType, "Structural type expected.");
 
                        targetType = findType;
                    }
                }
 
 
                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");
 
                NavigationProperty navigationProperty = new NavigationProperty(cspaceProperty.Name, TypeUsage.Create(targetType), clrProperty);
                navigationProperty.RelationshipType = (RelationshipType)ospaceRelationship;
 
                // we can use First because o-space relationships are created directly from 
                // c-space relationship
                navigationProperty.ToEndMember = (RelationshipEndMember)((RelationshipType)ospaceRelationship).Members.First(e => e.Name == cspaceProperty.ToEndMember.Name);
                navigationProperty.FromEndMember = (RelationshipEndMember)((RelationshipType)ospaceRelationship).Members.First(e => e.Name == cspaceProperty.FromEndMember.Name);
                ospaceType.AddMember(navigationProperty);
            }
            else
            {
                EntityTypeBase missingType = cspaceProperty.RelationshipType.RelationshipEndMembers.Select(e => ((RefType)e.TypeUsage.EdmType).ElementType).First(e => e != cspaceType);
                string message =
                    SessionData.LoadMessageLogger.CreateErrorMessageWithTypeSpecificLoadLogs(
                        Strings.Validator_OSpace_Convention_RelationshipNotLoaded(cspaceProperty.RelationshipType.FullName, missingType.FullName),
                        missingType);
                SessionData.EdmItemErrors.Add(new EdmItemError(message, ospaceType));
            }
        }
 
        private bool TryFindAndCreatePrimitiveProperties(Type type, StructuralType cspaceType, StructuralType ospaceType, PropertyInfo[] clrProperties)
        {
            foreach (EdmProperty cspaceProperty in cspaceType.GetDeclaredOnlyMembers<EdmProperty>().Where(p => Helper.IsPrimitiveType(p.TypeUsage.EdmType)))
            {
                PropertyInfo clrProperty = clrProperties.FirstOrDefault(p => MemberMatchesByConvention(p, cspaceProperty));
                if (clrProperty != null)
                {
                    PrimitiveType propertyType;
                    if (TryGetPrimitiveType(clrProperty.PropertyType, out propertyType))
                    {
                        if (clrProperty.CanRead && clrProperty.CanWrite)
                        {
                            AddScalarMember(type, clrProperty, ospaceType, cspaceProperty, propertyType);
                        }
                        else
                        {
                            string message = Strings.Validator_OSpace_Convention_ScalarPropertyMissginGetterOrSetter(clrProperty.Name, type.FullName, type.Assembly.FullName);
                            SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
                            return false;
                        }
                    }
                    else
                    {
                        string message = Strings.Validator_OSpace_Convention_NonPrimitiveTypeProperty(clrProperty.Name, type.FullName, clrProperty.PropertyType.FullName);
                        SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
                        return false;
                    }
                }
                else
                {
                    string message = Strings.Validator_OSpace_Convention_MissingRequiredProperty(cspaceProperty.Name, type.FullName);
                    SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
                    return false;
                }
            }
            return true;
        }
 
        private bool TryFindAndCreateEnumProperties(Type type, StructuralType cspaceType, StructuralType ospaceType, PropertyInfo[] clrProperties, List<Action> referenceResolutionListForCurrentType)
        {
            var typeClosureToTrack = new List<KeyValuePair<EdmProperty, PropertyInfo>>();
 
            foreach (EdmProperty cspaceProperty in cspaceType.GetDeclaredOnlyMembers<EdmProperty>().Where(p => Helper.IsEnumType(p.TypeUsage.EdmType)))
            {
                PropertyInfo clrProperty = clrProperties.FirstOrDefault(p => MemberMatchesByConvention(p, cspaceProperty));
                if (clrProperty != null)
                {
                    typeClosureToTrack.Add(new KeyValuePair<EdmProperty, PropertyInfo>(cspaceProperty, clrProperty));
                }
                else
                {
                    string message = Strings.Validator_OSpace_Convention_MissingRequiredProperty(cspaceProperty.Name, type.FullName);
                    SessionData.LoadMessageLogger.LogLoadMessage(message, cspaceType);
                    return false;
                }
            }
 
            foreach (var typeToTrack in typeClosureToTrack)
            {
                TrackClosure(typeToTrack.Value.PropertyType);
                // prevent the lifting of these closure variables
                var ot = ospaceType;
                var cp = typeToTrack.Key;
                var clrp = typeToTrack.Value;
                referenceResolutionListForCurrentType.Add(() => CreateAndAddEnumProperty(type, ot, cp, clrp));
            }
 
            return true;
        }
 
        /// <summary>
        /// Creates an Enum property based on <paramref name="clrProperty"/>and adds it to the parent structural type.
        /// </summary>
        /// <param name="type">CLR type owning <paramref name="clrProperty"/>.</param>
        /// <param name="ospaceType">OSpace type the created property will be added to.</param>
        /// <param name="cspaceProperty">Corresponding property from CSpace.</param>
        /// <param name="clrProperty">CLR property used to build an Enum property.</param>
        private void CreateAndAddEnumProperty(Type type, StructuralType ospaceType, EdmProperty cspaceProperty, PropertyInfo clrProperty)
        {
            EdmType propertyType;
            if (SessionData.CspaceToOspace.TryGetValue(cspaceProperty.TypeUsage.EdmType, out propertyType))
            {
                if (clrProperty.CanRead && clrProperty.CanWrite)
                {
                    AddScalarMember(type, clrProperty, ospaceType, cspaceProperty, propertyType);
                }
                else
                {
                    string message =
                        SessionData.LoadMessageLogger.CreateErrorMessageWithTypeSpecificLoadLogs(
                            Strings.Validator_OSpace_Convention_ScalarPropertyMissginGetterOrSetter(clrProperty.Name, type.FullName, type.Assembly.FullName),
                            cspaceProperty.TypeUsage.EdmType);
 
                    SessionData.EdmItemErrors.Add(new EdmItemError(message, ospaceType));
                }
            }
            else
            {
                string message =
                    SessionData.LoadMessageLogger.CreateErrorMessageWithTypeSpecificLoadLogs(
                        Strings.Validator_OSpace_Convention_MissingOSpaceType(cspaceProperty.TypeUsage.EdmType.FullName),
                        cspaceProperty.TypeUsage.EdmType);
 
                SessionData.EdmItemErrors.Add(new EdmItemError(message, ospaceType));
            }
        }
 
        private void CreateRelationships()
        {
            if (SessionData.ConventionBasedRelationshipsAreLoaded)
            {
                return;                
            }
            
            
            SessionData.ConventionBasedRelationshipsAreLoaded = true;
 
            // find all the relationships
            foreach (AssociationType cspaceAssociation in SessionData.EdmItemCollection.GetItems<AssociationType>())
            {
                Debug.Assert(cspaceAssociation.RelationshipEndMembers.Count == 2, "Relationships are assumed to have exactly two ends");
 
                if (SessionData.CspaceToOspace.ContainsKey(cspaceAssociation))
                {
                    // don't try to load relationships that we already know about
                    continue;
                }
 
                EdmType[] ospaceEndTypes = new EdmType[2];
                if (SessionData.CspaceToOspace.TryGetValue(GetRelationshipEndType(cspaceAssociation.RelationshipEndMembers[0]), out ospaceEndTypes[0]) &&
                    SessionData.CspaceToOspace.TryGetValue(GetRelationshipEndType(cspaceAssociation.RelationshipEndMembers[1]), out ospaceEndTypes[1]))
                {
                    Debug.Assert(ospaceEndTypes[0] is StructuralType);
                    Debug.Assert(ospaceEndTypes[1] is StructuralType);
 
                    // if we can find both ends of the relationship, then create it
 
                    AssociationType ospaceAssociation = new AssociationType(cspaceAssociation.Name, cspaceAssociation.NamespaceName, cspaceAssociation.IsForeignKey, DataSpace.OSpace);
                    for (int i = 0; i < cspaceAssociation.RelationshipEndMembers.Count; i++)
                    {
                        EntityType ospaceEndType = (EntityType)ospaceEndTypes[i];
                        RelationshipEndMember cspaceEnd = cspaceAssociation.RelationshipEndMembers[i];
 
                        ospaceAssociation.AddKeyMember(new AssociationEndMember(cspaceEnd.Name, ospaceEndType.GetReferenceType(), cspaceEnd.RelationshipMultiplicity));
                    }
                    CacheEntry.TypesInAssembly.Add(ospaceAssociation);
                    SessionData.TypesInLoading.Add(ospaceAssociation.FullName, ospaceAssociation);
                    SessionData.CspaceToOspace.Add(cspaceAssociation, ospaceAssociation); 
 
                }
            }
        }
 
        private static StructuralType GetRelationshipEndType(RelationshipEndMember relationshipEndMember)
        {
            return ((RefType)relationshipEndMember.TypeUsage.EdmType).ElementType;
        }
 
        private static bool MemberMatchesByConvention(PropertyInfo clrProperty, EdmMember cspaceMember)
        {
            return clrProperty.Name == cspaceMember.Name;
        }
 
        private static bool NonPrimitiveMemberMatchesByConvention(PropertyInfo clrProperty, EdmMember cspaceMember)
        {
            return !clrProperty.PropertyType.IsValueType && !clrProperty.PropertyType.IsAssignableFrom(typeof(string)) && clrProperty.Name == cspaceMember.Name;
        }
 
        internal static bool SessionContainsConventionParameters(ObjectItemLoadingSessionData sessionData)
        {
            return sessionData.EdmItemCollection != null;
        }
 
        internal static bool TypesMatchByConvention(Type type, EdmType cspaceType)
        {
            return type.Name == cspaceType.Name;
        }
 
        private void AddScalarMember(Type type, PropertyInfo clrProperty, StructuralType ospaceType, EdmProperty cspaceProperty, EdmType propertyType)
        {
            Debug.Assert(type != null, "type != null");
            Debug.Assert(clrProperty != null, "clrProperty != null");
            Debug.Assert(clrProperty.CanRead && clrProperty.CanWrite, "The clr property has to have a setter and a getter.");
            Debug.Assert(ospaceType != null, "ospaceType != null");
            Debug.Assert(cspaceProperty != null, "cspaceProperty != null");
            Debug.Assert(propertyType != null, "propertyType != null");
            Debug.Assert(Helper.IsScalarType(propertyType), "Property has to be primitive or enum.");
 
            var cspaceType = cspaceProperty.DeclaringType;
 
            bool isKeyMember = Helper.IsEntityType(cspaceType) && ((EntityType)cspaceType).KeyMemberNames.Contains(clrProperty.Name);
 
            // 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)
            bool nullableFacetValue = !isKeyMember && (!clrProperty.PropertyType.IsValueType || Nullable.GetUnderlyingType(clrProperty.PropertyType) != null);
 
            EdmProperty ospaceProperty =
                new EdmProperty(
                    cspaceProperty.Name,
                    TypeUsage.Create(propertyType, new FacetValues { Nullable = nullableFacetValue }),
                    clrProperty,
                    type.TypeHandle);
 
            if (isKeyMember)
            {
                ((EntityType)ospaceType).AddKeyMember(ospaceProperty);
            }
            else
            {
                ospaceType.AddMember(ospaceProperty);
            }
        }
        
        internal static ObjectItemAssemblyLoader Create(Assembly assembly, ObjectItemLoadingSessionData sessionData)
        {
            if (!ObjectItemAttributeAssemblyLoader.IsSchemaAttributePresent(assembly))
            {
                return new ObjectItemConventionAssemblyLoader(assembly, sessionData);
            }
            else
            {
                // we were loading in convention mode, and ran into an assembly that can't be loaded by convention
                sessionData.EdmItemErrors.Add(new EdmItemError(Strings.Validator_OSpace_Convention_AttributeAssemblyReferenced(assembly.FullName), null));
                return new ObjectItemNoOpAssemblyLoader(assembly, sessionData);
            }
        }
    }
}