e79aa3c0ed
Former-commit-id: a2155e9bd80020e49e72e86c44da02a8ac0e57a4
908 lines
49 KiB
C#
908 lines
49 KiB
C#
//---------------------------------------------------------------------
|
|
// <copyright file="StorageMappingItemCollection.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//
|
|
// @owner [....]
|
|
// @backupOwner [....]
|
|
//---------------------------------------------------------------------
|
|
|
|
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
|
|
|
|
}//----
|