File: System\Data\Mapping\StorageMappingItemCollection.cs
Project: ndp\fx\src\DataEntity\System.Data.Entity.csproj (System.Data.Entity)
//---------------------------------------------------------------------
// <copyright file="StorageMappingItemCollection.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
 
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Common.Utils;
using System.Data.Entity;
using System.Data.Mapping.Update.Internal;
using System.Data.Mapping.ViewGeneration;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Linq;
using System.Runtime.Versioning;
using System.Xml;
using som = System.Data.EntityModel.SchemaObjectModel;
 
namespace System.Data.Mapping
{
    using OfTypeQVCacheKey = Pair<EntitySetBase, Pair<EntityTypeBase, bool>>;
 
    /// <summary>
    /// Class for representing a collection of items in Storage Mapping( CS Mapping) space.
    /// </summary>
    [CLSCompliant(false)]
    public partial class StorageMappingItemCollection : MappingItemCollection
    {
        #region Fields
        //EdmItemCollection that is associated with the MSL Loader.
        private EdmItemCollection m_edmCollection;
 
        //StoreItemCollection that is associated with the MSL Loader.
        private StoreItemCollection m_storeItemCollection;
        private ViewDictionary m_viewDictionary;
        private double m_mappingVersion = XmlConstants.UndefinedVersion;
 
        private MetadataWorkspace m_workspace;
 
        // In this version, we won't allow same types in CSpace to map to different types in store. If the same type
        // need to be reused, the store type must be the same. To keep track of this, we need to keep track of the member 
        // mapping across maps to make sure they are mapped to the same store side.
        // The first TypeUsage in the KeyValuePair stores the store equivalent type for the cspace member type and the second
        // one store the actual store type to which the member is mapped to.
        // For e.g. If the CSpace member of type Edm.Int32 maps to a sspace member of type SqlServer.bigint, then the KeyValuePair
        // for the cspace member will contain SqlServer.int (store equivalent for Edm.Int32) and SqlServer.bigint (Actual store type
        // to which the member was mapped to)
        private Dictionary<EdmMember, KeyValuePair<TypeUsage, TypeUsage>> m_memberMappings = new Dictionary<EdmMember, KeyValuePair<TypeUsage, TypeUsage>>();
        private ViewLoader _viewLoader;
 
        internal enum InterestingMembersKind
        { 
            RequiredOriginalValueMembers,   // legacy - used by the obsolete GetRequiredOriginalValueMembers
            FullUpdate,                     // Interesting members in case of full update scenario
            PartialUpdate                   // Interesting members in case of partial update scenario
        };
 
        private ConcurrentDictionary<Tuple<EntitySetBase, EntityTypeBase, InterestingMembersKind>, ReadOnlyCollection<EdmMember>> _cachedInterestingMembers =
            new ConcurrentDictionary<Tuple<EntitySetBase, EntityTypeBase, InterestingMembersKind>, ReadOnlyCollection<EdmMember>>();
 
        #endregion
 
        #region Constructors
        /// <summary>
        /// constructor that takes in a list of folder or files or a mix of both and
        /// creates metadata for mapping in all the files.
        /// </summary>
        /// <param name="edmCollection"></param>
        /// <param name="storeCollection"></param>
        /// <param name="filePaths"></param>
        [ResourceExposure(ResourceScope.Machine)] //Exposes the file path names which are a Machine resource
        [ResourceConsumption(ResourceScope.Machine)] //For MetadataArtifactLoader.CreateCompositeFromFilePaths method call but we do not create the file paths in this method
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "edm")]
        public StorageMappingItemCollection(EdmItemCollection edmCollection, StoreItemCollection storeCollection,
            params string[] filePaths)
            : base(DataSpace.CSSpace)
        {
            EntityUtil.CheckArgumentNull(edmCollection, "edmCollection");
            EntityUtil.CheckArgumentNull(storeCollection, "storeCollection");
            EntityUtil.CheckArgumentNull(filePaths, "filePaths");
 
            this.m_edmCollection = edmCollection;
            this.m_storeItemCollection = storeCollection;
 
            // Wrap the file paths in instances of the MetadataArtifactLoader class, which provides
            // an abstraction and a uniform interface over a diverse set of metadata artifacts.
            //
            MetadataArtifactLoader composite = null;
            List<XmlReader> readers = null;
            try
            {
                composite = MetadataArtifactLoader.CreateCompositeFromFilePaths(filePaths, XmlConstants.CSSpaceSchemaExtension);
                readers = composite.CreateReaders(DataSpace.CSSpace);
 
                this.Init(edmCollection, storeCollection, readers,
                          composite.GetPaths(DataSpace.CSSpace), true /*throwOnError*/);
            }
            finally
            {
                if (readers != null)
                {
                    Helper.DisposeXmlReaders(readers);
                }
            }
        }
 
        /// <summary>
        /// constructor that takes in a list of XmlReaders and creates metadata for mapping 
        /// in all the files.  
        /// </summary>
        /// <param name="edmCollection">The edm metadata collection that this mapping is to use</param>
        /// <param name="storeCollection">The store metadata collection that this mapping is to use</param>
        /// <param name="xmlReaders">The XmlReaders to load mapping from</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "edm")]
        public StorageMappingItemCollection(EdmItemCollection edmCollection,
                                            StoreItemCollection storeCollection,
                                            IEnumerable<XmlReader> xmlReaders)
            : base(DataSpace.CSSpace)
        {
            EntityUtil.CheckArgumentNull(xmlReaders, "xmlReaders");
 
            MetadataArtifactLoader composite = MetadataArtifactLoader.CreateCompositeFromXmlReaders(xmlReaders);
 
            this.Init(edmCollection,
                      storeCollection,
                      composite.GetReaders(),   // filter out duplicates
                      composite.GetPaths(),
                      true /* throwOnError*/);
 
        }
 
        /// <summary>
        /// constructor that takes in a list of XmlReaders and creates metadata for mapping 
        /// in all the files.  
        /// </summary>
        /// <param name="edmCollection">The edm metadata collection that this mapping is to use</param>
        /// <param name="storeCollection">The store metadata collection that this mapping is to use</param>
        /// <param name="filePaths">Mapping URIs</param>
        /// <param name="xmlReaders">The XmlReaders to load mapping from</param>
        /// <param name="errors">a list of errors for each file loaded</param>
        // referenced by System.Data.Entity.Design.dll
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        internal StorageMappingItemCollection(EdmItemCollection edmCollection,
                                              StoreItemCollection storeCollection,
                                              IEnumerable<XmlReader> xmlReaders,
                                              List<string> filePaths,
                                              out IList<EdmSchemaError> errors)
            : base(DataSpace.CSSpace)
        {
            // we will check the parameters for this internal ctor becuase
            // it is pretty much publicly exposed through the MetadataItemCollectionFactory
            // in System.Data.Entity.Design
            EntityUtil.CheckArgumentNull(xmlReaders, "xmlReaders");
            EntityUtil.CheckArgumentContainsNull(ref xmlReaders, "xmlReaders");
            // filePaths is allowed to be null
 
            errors = this.Init(edmCollection, storeCollection, xmlReaders, filePaths, false /*throwOnError*/);
        }
 
        /// <summary>
        /// constructor that takes in a list of XmlReaders and creates metadata for mapping 
        /// in all the files.  
        /// </summary>
        /// <param name="edmCollection">The edm metadata collection that this mapping is to use</param>
        /// <param name="storeCollection">The store metadata collection that this mapping is to use</param>
        /// <param name="filePaths">Mapping URIs</param>
        /// <param name="xmlReaders">The XmlReaders to load mapping from</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        internal StorageMappingItemCollection(EdmItemCollection edmCollection,
                                              StoreItemCollection storeCollection,
                                              IEnumerable<XmlReader> xmlReaders,
                                              List<string> filePaths)
            : base(DataSpace.CSSpace)
        {
            this.Init(edmCollection, storeCollection, xmlReaders, filePaths, true /*throwOnError*/);
        }
 
        /// <summary>
        /// Initializer that takes in a list of XmlReaders and creates metadata for mapping 
        /// in all the files.  
        /// </summary>
        /// <param name="edmCollection">The edm metadata collection that this mapping is to use</param>
        /// <param name="storeCollection">The store metadata collection that this mapping is to use</param>
        /// <param name="filePaths">Mapping URIs</param>
        /// <param name="xmlReaders">The XmlReaders to load mapping from</param>
        /// <param name="errors">a list of errors for each file loaded</param>
        private IList<EdmSchemaError> Init(EdmItemCollection edmCollection,
                          StoreItemCollection storeCollection,
                          IEnumerable<XmlReader> xmlReaders,
                          List<string> filePaths,
                          bool throwOnError)
        {
            EntityUtil.CheckArgumentNull(xmlReaders, "xmlReaders");
            EntityUtil.CheckArgumentNull(edmCollection, "edmCollection");
            EntityUtil.CheckArgumentNull(storeCollection, "storeCollection");
 
            this.m_edmCollection = edmCollection;
            this.m_storeItemCollection = storeCollection;
            
            Dictionary<EntitySetBase, GeneratedView> userDefinedQueryViewsDict;
            Dictionary<OfTypeQVCacheKey, GeneratedView> userDefinedQueryViewsOfTypeDict;
            
            this.m_viewDictionary = new ViewDictionary(this, out userDefinedQueryViewsDict, out userDefinedQueryViewsOfTypeDict);
 
            List<EdmSchemaError> errors = new List<EdmSchemaError>();
            
            if(this.m_edmCollection.EdmVersion != XmlConstants.UndefinedVersion &&
                this.m_storeItemCollection.StoreSchemaVersion != XmlConstants.UndefinedVersion &&
                this.m_edmCollection.EdmVersion != this.m_storeItemCollection.StoreSchemaVersion)
            {
                errors.Add(
                    new EdmSchemaError(
                        Strings.Mapping_DifferentEdmStoreVersion, 
                        (int)StorageMappingErrorCode.MappingDifferentEdmStoreVersion, EdmSchemaErrorSeverity.Error));
            }
            else
            {
                double expectedVersion = this.m_edmCollection.EdmVersion != XmlConstants.UndefinedVersion
                    ? this.m_edmCollection.EdmVersion
                    : this.m_storeItemCollection.StoreSchemaVersion;
                errors.AddRange(LoadItems(xmlReaders, filePaths, userDefinedQueryViewsDict, userDefinedQueryViewsOfTypeDict, expectedVersion));
            }
 
            Debug.Assert(errors != null);
 
            if (errors.Count > 0 && throwOnError)
            {
                if (!System.Data.Common.Utils.MetadataHelper.CheckIfAllErrorsAreWarnings(errors))
                {
                    // NOTE: not using Strings.InvalidSchemaEncountered because it will truncate the errors list.
                    throw new MappingException(
                    String.Format(System.Globalization.CultureInfo.CurrentCulture,
                                    EntityRes.GetString(EntityRes.InvalidSchemaEncountered),
                                    Helper.CombineErrorMessage(errors)));
                }
            }
 
            return errors;
        }
 
        #endregion Constructors
 
        internal MetadataWorkspace Workspace
        {
            get
            {
                if (m_workspace == null)
                {
                    m_workspace = new MetadataWorkspace();
                    m_workspace.RegisterItemCollection(m_edmCollection);
                    m_workspace.RegisterItemCollection(m_storeItemCollection);
                    m_workspace.RegisterItemCollection(this);
                }
                return m_workspace;
            }
        }
 
        /// <summary>
        /// Return the EdmItemCollection associated with the Mapping Collection
        /// </summary>
        internal EdmItemCollection EdmItemCollection
        {
            get
            {
                return this.m_edmCollection;
            }
        }
 
        /// <summary>
        /// Version of this StorageMappingItemCollection represents.
        /// </summary>
        public double MappingVersion
        {
            get
            {
                return this.m_mappingVersion;
            }
        }
 
        /// <summary>
        /// Return the StoreItemCollection associated with the Mapping Collection
        /// </summary>
        internal StoreItemCollection StoreItemCollection
        {
            get
            {
                return this.m_storeItemCollection;
            }
        }
 
        /// <summary>
        /// Search for a Mapping metadata with the specified type key.
        /// </summary>
        /// <param name="identity">identity of the type</param>
        /// <param name="typeSpace">The dataspace that the type for which map needs to be returned belongs to</param>
        /// <param name="ignoreCase">true for case-insensitive lookup</param>
        /// <exception cref="ArgumentException"> Thrown if mapping space is not valid</exception>
        internal override Map GetMap(string identity, DataSpace typeSpace, bool ignoreCase)
        {
            EntityUtil.CheckArgumentNull(identity, "identity");
            if (typeSpace != DataSpace.CSpace)
            {
                throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.Mapping_Storage_InvalidSpace(typeSpace));
            }
            return GetItem<Map>(identity, ignoreCase);
        }
 
        /// <summary>
        /// Search for a Mapping metadata with the specified type key.
        /// </summary>
        /// <param name="identity">identity of the type</param>
        /// <param name="typeSpace">The dataspace that the type for which map needs to be returned belongs to</param>
        /// <param name="ignoreCase">true for case-insensitive lookup</param>
        /// <param name="map"></param>
        /// <returns>Returns false if no match found.</returns>
        internal override bool TryGetMap(string identity, DataSpace typeSpace, bool ignoreCase, out Map map)
        {
            if (typeSpace != DataSpace.CSpace)
            {
                throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.Mapping_Storage_InvalidSpace(typeSpace));
            }
            return TryGetItem<Map>(identity, ignoreCase, out map);
        }
 
        /// <summary>
        /// Search for a Mapping metadata with the specified type key.
        /// </summary>
        /// <param name="identity">identity of the type</param>
        /// <param name="typeSpace">The dataspace that the type for which map needs to be returned belongs to</param>
        /// <exception cref="ArgumentException"> Thrown if mapping space is not valid</exception>
        internal override Map GetMap(string identity, DataSpace typeSpace)
        {
            return this.GetMap(identity, typeSpace, false /*ignoreCase*/);
        }
 
        /// <summary>
        /// Search for a Mapping metadata with the specified type key.
        /// </summary>
        /// <param name="identity">identity of the type</param>
        /// <param name="typeSpace">The dataspace that the type for which map needs to be returned belongs to</param>
        /// <param name="map"></param>
        /// <returns>Returns false if no match found.</returns>
        internal override bool TryGetMap(string identity, DataSpace typeSpace, out Map map)
        {
            return this.TryGetMap(identity, typeSpace, false /*ignoreCase*/, out map);
        }
 
        /// <summary>
        /// Search for a Mapping metadata with the specified type key.
        /// </summary>
        /// <param name="item"></param>
        internal override Map GetMap(GlobalItem item)
        {
            EntityUtil.CheckArgumentNull(item, "item");
            DataSpace typeSpace = item.DataSpace;
            if (typeSpace != DataSpace.CSpace)
            {
                throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.Mapping_Storage_InvalidSpace(typeSpace));
            }
            return this.GetMap(item.Identity, typeSpace);
        }
 
        /// <summary>
        /// Search for a Mapping metadata with the specified type key.
        /// </summary>
        /// <param name="item"></param>
        /// <param name="map"></param>
        /// <returns>Returns false if no match found.</returns>
        internal override bool TryGetMap(GlobalItem item, out Map map)
        {
            if (item == null)
            {
                map = null;
                return false;
            }
            DataSpace typeSpace = item.DataSpace;
            if (typeSpace != DataSpace.CSpace)
            {
                map = null;
                return false;
            }
            return this.TryGetMap(item.Identity, typeSpace, out map);
        }
 
        /// <summary>
        /// This method
        ///     - generates views from the mapping elements in the collection;
        ///     - does not process user defined views - these are processed during mapping collection loading;
        ///     - does not cache generated views in the mapping collection.
        /// The main purpose is design-time view validation and generation.
        /// </summary>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] // referenced by System.Data.Entity.Design.dll
        internal Dictionary<EntitySetBase, string> GenerateEntitySetViews(out IList<EdmSchemaError> errors)
        {
            Dictionary<EntitySetBase, string> esqlViews = new Dictionary<EntitySetBase, string>();
            errors = new List<EdmSchemaError>();
            foreach (var mapping in GetItems<Map>())
            {
                var entityContainerMapping = mapping as StorageEntityContainerMapping;
                if (entityContainerMapping != null)
                {
                    // If there are no entity set maps, don't call the view generation process.
                    if (!entityContainerMapping.HasViews)
                    {
                        return esqlViews;
                    }
 
                    // If entityContainerMapping contains only query views, then add a warning to the errors and continue to next mapping.
                    if (!entityContainerMapping.HasMappingFragments())
                    {
                        Debug.Assert(2088 == (int)StorageMappingErrorCode.MappingAllQueryViewAtCompileTime, "Please change the ERRORCODE_MAPPINGALLQUERYVIEWATCOMPILETIME value as well");
                        errors.Add(new EdmSchemaError(
                            Strings.Mapping_AllQueryViewAtCompileTime(entityContainerMapping.Identity),
                            (int)StorageMappingErrorCode.MappingAllQueryViewAtCompileTime,
                            EdmSchemaErrorSeverity.Warning));
                    }
                    else
                    {
                        ViewGenResults viewGenResults = ViewgenGatekeeper.GenerateViewsFromMapping(entityContainerMapping, new ConfigViewGenerator() { GenerateEsql = true });
                        if (viewGenResults.HasErrors)
                        {
                            ((List<EdmSchemaError>)errors).AddRange(viewGenResults.Errors);
                        }
                        KeyToListMap<EntitySetBase, GeneratedView> extentMappingViews = viewGenResults.Views;
                        foreach (KeyValuePair<EntitySetBase, List<GeneratedView>> extentViewPair in extentMappingViews.KeyValuePairs)
                        {
                            List<GeneratedView> generatedViews = extentViewPair.Value;
                            // Multiple Views are returned for an extent but the first view
                            // is the only one that we will use for now. In the future,
                            // we might start using the other views which are per type within an extent.
                            esqlViews.Add(extentViewPair.Key, generatedViews[0].eSQL);
                        }
                    }
                }
            }
            return esqlViews;
        }
 
        #region Get interesting members
 
        /// <summary>
        /// Return members for MetdataWorkspace.GetRequiredOriginalValueMembers() and MetdataWorkspace.GetRelevantMembersForUpdate() methods.
        /// </summary>
        /// <param name="entitySet">An EntitySet belonging to the C-Space. Must not be null.</param>
        /// <param name="entityType">An EntityType that participates in the given EntitySet. Must not be null.</param>
        /// <param name="interestingMembersKind">Scenario the members should be returned for.</param>
        /// <returns>ReadOnlyCollection of interesting members for the requested scenario (<paramref name="interestingMembersKind"/>).</returns>        
        internal ReadOnlyCollection<EdmMember> GetInterestingMembers(EntitySetBase entitySet, EntityTypeBase entityType, InterestingMembersKind interestingMembersKind)
        {
            Debug.Assert(entitySet != null, "entitySet != null");
            Debug.Assert(entityType != null, "entityType != null");
 
            var key = new Tuple<EntitySetBase, EntityTypeBase, InterestingMembersKind>(entitySet, entityType, interestingMembersKind);
            return _cachedInterestingMembers.GetOrAdd(key,  FindInterestingMembers(entitySet, entityType, interestingMembersKind));
        }
 
        /// <summary>
        /// Finds interesting members for MetdataWorkspace.GetRequiredOriginalValueMembers() and MetdataWorkspace.GetRelevantMembersForUpdate() methods
        /// for the given <paramref name="entitySet"/> and <paramref name="entityType"/>.
        /// </summary>
        /// <param name="entitySet">An EntitySet belonging to the C-Space. Must not be null.</param>
        /// <param name="entityType">An EntityType that participates in the given EntitySet. Must not be null.</param>
        /// <param name="interestingMembersKind">Scenario the members should be returned for.</param>
        /// <returns>ReadOnlyCollection of interesting members for the requested scenario (<paramref name="interestingMembersKind"/>).</returns>        
        private ReadOnlyCollection<EdmMember> FindInterestingMembers(EntitySetBase entitySet, EntityTypeBase entityType, InterestingMembersKind interestingMembersKind)
        {
            Debug.Assert(entitySet != null, "entitySet != null");
            Debug.Assert(entityType != null, "entityType != null");
 
            var interestingMembers = new List<EdmMember>();
 
            foreach (var storageTypeMapping in MappingMetadataHelper.GetMappingsForEntitySetAndSuperTypes(this, entitySet.EntityContainer, entitySet, entityType))
            {
                StorageAssociationTypeMapping associationTypeMapping = storageTypeMapping as StorageAssociationTypeMapping;
                if (associationTypeMapping != null)
                {
                    FindInterestingAssociationMappingMembers(associationTypeMapping, interestingMembers);
                }
                else
                {
                    Debug.Assert(storageTypeMapping is StorageEntityTypeMapping, "StorageEntityTypeMapping expected.");
 
                    FindInterestingEntityMappingMembers((StorageEntityTypeMapping)storageTypeMapping, interestingMembersKind, interestingMembers);
                }
            }
 
            // For backwards compatibility we don't return foreign keys from the obsolete MetadataWorkspace.GetRequiredOriginalValueMembers() method
            if (interestingMembersKind != InterestingMembersKind.RequiredOriginalValueMembers)
            {
                FindForeignKeyProperties(entitySet, entityType, interestingMembers);
            }
 
            foreach (var functionMappings in MappingMetadataHelper
                                                .GetModificationFunctionMappingsForEntitySetAndType(this, entitySet.EntityContainer, entitySet, entityType)
                                                .Where(functionMappings => functionMappings.UpdateFunctionMapping != null))
            {
                FindInterestingFunctionMappingMembers(functionMappings, interestingMembersKind, ref interestingMembers);
            }
 
            Debug.Assert(interestingMembers != null, "interestingMembers must never be null.");
 
            return new ReadOnlyCollection<EdmMember>(interestingMembers.Distinct().ToList());
        }
 
        /// <summary>
        /// Finds members participating in the assocciation and adds them to the <paramref name="interestingMembers"/>.
        /// </summary>
        /// <param name="associationTypeMapping">Association type mapping. Must not be null.</param>
        /// <param name="interestingMembers">The list the interesting members (if any) will be added to. Must not be null.</param>
        private static void FindInterestingAssociationMappingMembers(StorageAssociationTypeMapping associationTypeMapping, List<EdmMember> interestingMembers)
        {
            Debug.Assert(associationTypeMapping != null, "entityTypeMapping != null");
            Debug.Assert(interestingMembers != null, "interestingMembers != null");
 
            //(2) Ends participating in association are "interesting"
            interestingMembers.AddRange(
                associationTypeMapping
                .MappingFragments
                .SelectMany(m => m.AllProperties)
                .OfType<StorageEndPropertyMapping>()
                .Select(epm => epm.EndMember));
        }
 
        /// <summary>
        /// Finds interesting entity properties - primary keys (if requested), properties (including complex properties and nested properties)
        /// with concurrency mode set to fixed and C-Side condition members and adds them to the <paramref name="interestingMembers"/>.
        /// </summary>
        /// <param name="entityTypeMapping">Entity type mapping. Must not be null.</param>
        /// <param name="interestingMembersKind">Scenario the members should be returned for.</param>
        /// <param name="interestingMembers">The list the interesting members (if any) will be added to. Must not be null.</param>
        private static void FindInterestingEntityMappingMembers(StorageEntityTypeMapping entityTypeMapping, InterestingMembersKind interestingMembersKind, List<EdmMember> interestingMembers)
        {
            Debug.Assert(entityTypeMapping != null, "entityTypeMapping != null");
            Debug.Assert(interestingMembers != null, "interestingMembers != null");
 
            foreach (var propertyMapping in entityTypeMapping.MappingFragments.SelectMany(mf => mf.AllProperties))
            {
                StorageScalarPropertyMapping scalarPropMapping = propertyMapping as StorageScalarPropertyMapping;
                StorageComplexPropertyMapping complexPropMapping = propertyMapping as StorageComplexPropertyMapping;
                StorageConditionPropertyMapping conditionMapping = propertyMapping as StorageConditionPropertyMapping;
 
                Debug.Assert(!(propertyMapping is StorageEndPropertyMapping), "association mapping properties should be handled elsewhere.");
 
                Debug.Assert(scalarPropMapping != null ||
                             complexPropMapping != null ||
                             conditionMapping != null, "Unimplemented property mapping");
 
                //scalar property
                if (scalarPropMapping != null && scalarPropMapping.EdmProperty != null)
                {
                    // (0) if a member is part of the key it is interesting
                    if (MetadataHelper.IsPartOfEntityTypeKey(scalarPropMapping.EdmProperty))
                    {
                        // For backwards compatibility we do return primary keys from the obsolete MetadataWorkspace.GetRequiredOriginalValueMembers() method
                        if (interestingMembersKind == InterestingMembersKind.RequiredOriginalValueMembers)
                        {
                            interestingMembers.Add(scalarPropMapping.EdmProperty);
                        }
                    }
                    //(3) if a scalar property has Fixed concurrency mode then it is "interesting"
                    else if (MetadataHelper.GetConcurrencyMode(scalarPropMapping.EdmProperty) == ConcurrencyMode.Fixed)
                    {
                        interestingMembers.Add(scalarPropMapping.EdmProperty);
                    }
                }
                else if (complexPropMapping != null)
                {
                    // (7) All complex members - partial update scenarios only
                    // (3.1) The complex property or its one of its children has fixed concurrency mode
                    if (interestingMembersKind == InterestingMembersKind.PartialUpdate ||
                        MetadataHelper.GetConcurrencyMode(complexPropMapping.EdmProperty) == ConcurrencyMode.Fixed || HasFixedConcurrencyModeInAnyChildProperty(complexPropMapping))
                    {
                        interestingMembers.Add(complexPropMapping.EdmProperty);
                    }
                }
                else if (conditionMapping != null)
                {
                    //(1) C-Side condition members are 'interesting'
                    if (conditionMapping.EdmProperty != null)
                    {
                        interestingMembers.Add(conditionMapping.EdmProperty);
                    }
                }
            }
        }
 
        /// <summary>
        /// Recurses down the complex property to find whether any of the nseted properties has concurrency mode set to "Fixed"
        /// </summary>
        /// <param name="complexMapping">Complex property mapping. Must not be null.</param>
        /// <returns><c>true</c> if any of the descendant properties has concurrency mode set to "Fixed". Otherwise <c>false</c>.</returns>
        private static bool HasFixedConcurrencyModeInAnyChildProperty(StorageComplexPropertyMapping complexMapping)
        {
            Debug.Assert(complexMapping != null, "complexMapping != null");
 
            foreach (StoragePropertyMapping propertyMapping in complexMapping.TypeMappings.SelectMany(m => m.AllProperties))
            {
                StorageScalarPropertyMapping childScalarPropertyMapping = propertyMapping as StorageScalarPropertyMapping;
                StorageComplexPropertyMapping childComplexPropertyMapping = propertyMapping as StorageComplexPropertyMapping;
 
                Debug.Assert(childScalarPropertyMapping != null ||
                             childComplexPropertyMapping != null, "Unimplemented property mapping for complex property");
 
                //scalar property and has Fixed CC mode
                if (childScalarPropertyMapping != null && MetadataHelper.GetConcurrencyMode(childScalarPropertyMapping.EdmProperty) == ConcurrencyMode.Fixed)
                {
                    return true;
                }
                // Complex Prop and sub-properties or itself has fixed CC mode
                else if (childComplexPropertyMapping != null &&
                    (MetadataHelper.GetConcurrencyMode(childComplexPropertyMapping.EdmProperty) == ConcurrencyMode.Fixed
                        || HasFixedConcurrencyModeInAnyChildProperty(childComplexPropertyMapping)))
                {
                    return true;
                }
            }
 
            return false;
        }
 
        /// <summary>
        /// Finds foreign key properties and adds them to the <paramref name="interestingMembers"/>.
        /// </summary>
        /// <param name="entitySetBase">Entity set <paramref name="entityType"/> relates to. Must not be null.</param>
        /// <param name="entityType">Entity type for which to find foreign key properties. Must not be null.</param>
        /// <param name="interestingMembers">The list the interesting members (if any) will be added to. Must not be null.</param>
        private void FindForeignKeyProperties(EntitySetBase entitySetBase, EntityTypeBase entityType, List<EdmMember> interestingMembers)
        {
            var entitySet = entitySetBase as EntitySet;
            if (entitySet != null && entitySet.HasForeignKeyRelationships)
            {
                // (6) Foreign keys
                // select all foreign key properties defined on the entityType and all its ancestors
                interestingMembers.AddRange(
                        MetadataHelper.GetTypeAndParentTypesOf(entityType, this.m_edmCollection, true)
                        .SelectMany(e => ((EntityType)e).Properties)
                        .Where(p => entitySet.ForeignKeyDependents.SelectMany(fk => fk.Item2.ToProperties).Contains(p)));
            }
        }
        
        /// <summary>
        /// Finds interesting members for modification functions mapped to stored procedures and adds them to the <paramref name="interestingMembers"/>.
        /// </summary>
        /// <param name="functionMappings">Modification function mapping. Must not be null.</param>
        /// <param name="interestingMembersKind">Update scenario the members will be used in (in general - partial update vs. full update).</param>
        /// <param name="interestingMembers"></param>
        private static void FindInterestingFunctionMappingMembers(StorageEntityTypeModificationFunctionMapping functionMappings, InterestingMembersKind interestingMembersKind, ref List<EdmMember> interestingMembers)
        {
            Debug.Assert(functionMappings != null && functionMappings.UpdateFunctionMapping != null, "Expected function mapping fragment with non-null update function mapping");
            Debug.Assert(interestingMembers != null, "interestingMembers != null");
 
            // for partial update scenarios (e.g. EntityDataSourceControl) all members are interesting otherwise the data may be corrupt. 
            // See bugs #272992 and #124460 in DevDiv database for more details. For full update scenarios and the obsolete 
            // MetadataWorkspace.GetRequiredOriginalValueMembers() metod we return only members with Version set to "Original".
            if (interestingMembersKind == InterestingMembersKind.PartialUpdate)
            {
                // (5) Members included in Update ModificationFunction
                interestingMembers.AddRange(functionMappings.UpdateFunctionMapping.ParameterBindings.Select(p => p.MemberPath.Members.Last()));
            }
            else
            {
                //(4) Members in update ModificationFunction with Version="Original" are "interesting"
                // This also works when you have complex-types (4.1)
 
                Debug.Assert(
                    interestingMembersKind == InterestingMembersKind.FullUpdate || interestingMembersKind == InterestingMembersKind.RequiredOriginalValueMembers,
                    "Unexpected kind of interesting members - if you changed the InterestingMembersKind enum type update this code accordingly");
 
                foreach (var parameterBinding in functionMappings.UpdateFunctionMapping.ParameterBindings.Where(p => !p.IsCurrent))
                {
                    //Last is the root element (with respect to the Entity)
                    //For example,  Entity1={
                    //                  S1, 
                    //                  C1{S2, 
                    //                     C2{ S3, S4 } 
                    //                     }, 
                    //                  S5}
                    // if S4 matches (i.e. C1.C2.S4), then it returns C1
                    //because internally the list is [S4][C2][C1]
                    interestingMembers.Add(parameterBinding.MemberPath.Members.Last());
                }
            }
        }
 
        #endregion
 
        /// <summary>
        /// Calls the view dictionary to load the view, see detailed comments in the view dictionary class.
        /// </summary>
        internal GeneratedView GetGeneratedView(EntitySetBase extent, MetadataWorkspace workspace)
        {
            return this.m_viewDictionary.GetGeneratedView(extent, workspace, this);
        }
 
        // Add to the cache. If it is already present, then throw an exception
        private void AddInternal(Map storageMap)
        {
            storageMap.DataSpace = DataSpace.CSSpace;
            try
            {
                base.AddInternal(storageMap);
            }
            catch (ArgumentException e)
            {
                throw new MappingException(System.Data.Entity.Strings.Mapping_Duplicate_Type(storageMap.EdmItem.Identity), e);
            }
        }
 
        // Contains whether the given StorageEntityContainerName
        internal bool ContainsStorageEntityContainer(string storageEntityContainerName)
        {
            ReadOnlyCollection<StorageEntityContainerMapping> entityContainerMaps =
                this.GetItems<StorageEntityContainerMapping>();
            return entityContainerMaps.Any(map => map.StorageEntityContainer.Name.Equals(storageEntityContainerName, StringComparison.Ordinal));
        }
 
 
        /// <summary>
        /// This helper method loads items based on contents of in-memory XmlReader instances.
        /// Assumption: This method is called only from the constructor because m_extentMappingViews is not thread safe.
        /// </summary>
        /// <param name="xmlReaders">A list of XmlReader instances</param>
        /// <param name="mappingSchemaUris">A list of URIs</param>
        /// <returns>A list of schema errors</returns>
        private List<EdmSchemaError> LoadItems(IEnumerable<XmlReader> xmlReaders,
                                               List<string> mappingSchemaUris,
                                               Dictionary<EntitySetBase, GeneratedView> userDefinedQueryViewsDict,
                                               Dictionary<OfTypeQVCacheKey, GeneratedView> userDefinedQueryViewsOfTypeDict,
                                               double expectedVersion)
        {
            Debug.Assert(m_memberMappings.Count == 0, "Assumption: This method is called only once, and from the constructor because m_extentMappingViews is not thread safe.");
 
            List<EdmSchemaError> errors = new List<EdmSchemaError>();
 
            int index = -1;
            foreach (XmlReader xmlReader in xmlReaders)
            {
                index++;
                string location = null;
                if (mappingSchemaUris == null)
                {
                    som.SchemaManager.TryGetBaseUri(xmlReader, out location);
                }
                else
                {
                    location = mappingSchemaUris[index];
                }
 
                StorageMappingItemLoader mapLoader = new StorageMappingItemLoader(
                                                            xmlReader,
                                                            this,
                                                            location,  // ASSUMPTION: location is only used for generating error-messages
                                                            m_memberMappings);
                errors.AddRange(mapLoader.ParsingErrors);
 
                CheckIsSameVersion(expectedVersion, mapLoader.MappingVersion, errors);
 
                // Process container mapping.
                StorageEntityContainerMapping containerMapping = mapLoader.ContainerMapping;
                if (mapLoader.HasQueryViews && containerMapping != null)
                {
                    // Compile the query views so that we can report the errors in the user specified views.
                    CompileUserDefinedQueryViews(containerMapping, userDefinedQueryViewsDict, userDefinedQueryViewsOfTypeDict, errors);
                }
                // Add container mapping if there are no errors and entity container mapping is not already present.
                if (MetadataHelper.CheckIfAllErrorsAreWarnings(errors) && !this.Contains(containerMapping))
                {
                    AddInternal(containerMapping);
                }
            }
 
            CheckForDuplicateItems(EdmItemCollection, StoreItemCollection, errors);
 
            return errors;
        }
 
        /// <summary>
        /// This method compiles all the user defined query views in the <paramref name="entityContainerMapping"/>.
        /// </summary>
        private static void CompileUserDefinedQueryViews(StorageEntityContainerMapping entityContainerMapping,
                                                         Dictionary<EntitySetBase, GeneratedView> userDefinedQueryViewsDict,
                                                         Dictionary<OfTypeQVCacheKey, GeneratedView> userDefinedQueryViewsOfTypeDict,
                                                         IList<EdmSchemaError> errors)
        {
            ConfigViewGenerator config = new ConfigViewGenerator();
            foreach (StorageSetMapping setMapping in entityContainerMapping.AllSetMaps)
            {
                if (setMapping.QueryView != null)
                {
                    GeneratedView generatedView;
                    if (!userDefinedQueryViewsDict.TryGetValue(setMapping.Set, out generatedView))
                    {
                        // Parse the view so that we will get back any errors in the view.
                        if (GeneratedView.TryParseUserSpecifiedView(setMapping,
                                                                    setMapping.Set.ElementType,
                                                                    setMapping.QueryView,
                                                                    true, // includeSubtypes
                                                                    entityContainerMapping.StorageMappingItemCollection,
                                                                    config,
                                                                    /*out*/ errors,
                                                                    out generatedView))
                        {
                            // Add first QueryView
                            userDefinedQueryViewsDict.Add(setMapping.Set, generatedView);
                        }
 
                        // Add all type-specific QueryViews
                        foreach (OfTypeQVCacheKey key in setMapping.GetTypeSpecificQVKeys())
                        {
                            Debug.Assert(key.First.Equals(setMapping.Set));
 
                            if (GeneratedView.TryParseUserSpecifiedView(setMapping,
                                                                        key.Second.First, // type
                                                                        setMapping.GetTypeSpecificQueryView(key),
                                                                        key.Second.Second, // includeSubtypes
                                                                        entityContainerMapping.StorageMappingItemCollection,
                                                                        config,
                                                                        /*out*/ errors,
                                                                        out generatedView))
                            {
                                userDefinedQueryViewsOfTypeDict.Add(key, generatedView);
                            }
                        }
                    }
                }
            }
        }
 
        private void CheckIsSameVersion(double expectedVersion, double currentLoaderVersion, IList<EdmSchemaError> errors)
        {
            if (m_mappingVersion == XmlConstants.UndefinedVersion)
            {
                m_mappingVersion = currentLoaderVersion;
            }
            if (expectedVersion != XmlConstants.UndefinedVersion && currentLoaderVersion != XmlConstants.UndefinedVersion && currentLoaderVersion != expectedVersion)
            {
                // Check that the mapping version is the same as the storage and model version
                errors.Add(
                    new EdmSchemaError(
                        Strings.Mapping_DifferentMappingEdmStoreVersion,
                        (int)StorageMappingErrorCode.MappingDifferentMappingEdmStoreVersion, EdmSchemaErrorSeverity.Error));
            }
            if (currentLoaderVersion != m_mappingVersion && currentLoaderVersion != XmlConstants.UndefinedVersion)
            {
                // Check that the mapping versions are all consistent with each other
                errors.Add(
                   new EdmSchemaError(
                       Strings.CannotLoadDifferentVersionOfSchemaInTheSameItemCollection,
                       (int)StorageMappingErrorCode.CannotLoadDifferentVersionOfSchemaInTheSameItemCollection,
                       EdmSchemaErrorSeverity.Error));
            }
        }
 
        /// <summary>
        /// Return the update view loader
        /// </summary>
        /// <returns></returns>
        internal ViewLoader GetUpdateViewLoader()
        {
            if (_viewLoader == null)
            {
                _viewLoader = new ViewLoader(this);
            }
 
            return _viewLoader;
        }
 
        /// <summary>
        /// this method will be called in metadatworkspace, the signature is the same as the one in ViewDictionary
        /// </summary>
        /// <param name="workspace"></param>
        /// <param name="entity"></param>
        /// <param name="type"></param>
        /// <param name="includeSubtypes"></param>
        /// <param name="generatedView"></param>
        /// <returns></returns>
        internal bool TryGetGeneratedViewOfType(MetadataWorkspace workspace, EntitySetBase entity, EntityTypeBase type, bool includeSubtypes, out GeneratedView generatedView)
        {
            return this.m_viewDictionary.TryGetGeneratedViewOfType(workspace, entity, type, includeSubtypes, out generatedView);
        }
 
        // Check for duplicate items (items with same name) in edm item collection and store item collection. Mapping is the only logical place to do this. 
        // The only other place is workspace, but that is at the time of registering item collections (only when the second one gets registered) and we 
        // will have to throw exceptions at that time. If we do this check in mapping, we might throw error in a more consistent way (by adding it to error
        // collection). Also if someone is just creating item collection, and not registering it with workspace (tools), doing it in mapping makes more sense
        private static void CheckForDuplicateItems(EdmItemCollection edmItemCollection, StoreItemCollection storeItemCollection, List<EdmSchemaError> errorCollection)
        {
            Debug.Assert(edmItemCollection != null && storeItemCollection != null && errorCollection != null, "The parameters must not be null in CheckForDuplicateItems");
 
            foreach (GlobalItem item in edmItemCollection)
            {
                if (storeItemCollection.Contains(item.Identity))
                {
                    errorCollection.Add(new EdmSchemaError(Strings.Mapping_ItemWithSameNameExistsBothInCSpaceAndSSpace(item.Identity),
                                        (int)StorageMappingErrorCode.ItemWithSameNameExistsBothInCSpaceAndSSpace, EdmSchemaErrorSeverity.Error));
                }
            }
        }
    }//---- ItemCollection
 
}//----