2016-08-03 10:59:49 +00:00
//---------------------------------------------------------------------
// <copyright file="StorageMappingItemCollection.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
2017-08-21 15:34:15 +00:00
// @owner Microsoft
// @backupOwner Microsoft
2016-08-03 10:59:49 +00:00
//---------------------------------------------------------------------
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
} //----