//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// @owner [....], [....]
//---------------------------------------------------------------------
using System;
using System.Diagnostics;
using System.Collections;
using System.IO;
using System.Text;
using System.Collections.Generic;
using System.Xml;
using System.Xml.Schema;
using System.Xml.XPath;
using System.Data.Metadata.Edm;
using System.Data.Common.Utils;
using System.Data.Mapping.ViewGeneration.Utils;
using System.Collections.ObjectModel;
using System.Data.EntityModel;
using System.Data.Entity;
using System.Globalization;
using System.Linq;
namespace System.Data.Mapping
{
using Triple = Pair>;
///
/// The class loads an MSL file into memory and exposes CSMappingMetadata interfaces.
/// The primary consumers of the interfaces are view genration and tools.
///
///
/// For Example if conceptually you could represent the CS MSL file as following
/// --Mapping
/// --EntityContainerMapping ( CNorthwind-->SNorthwind )
/// --EntitySetMapping
/// --EntityTypeMapping
/// --TableMappingFragment
/// --EntityKey
/// --ScalarPropertyMap ( CMemberMetadata-->SMemberMetadata )
/// --ScalarPropertyMap ( CMemberMetadata-->SMemberMetadata )
/// --DiscriminatorProperyMap ( constant value-->SMemberMetadata )
/// --EntityTypeMapping
/// --TableMappingFragment
/// --EntityKey
/// --ScalarPropertyMap ( CMemberMetadata-->SMemberMetadata )
/// --ComplexPropertyMap
/// --ComplexTypeMap
/// --ScalarPropertyMap ( CMemberMetadata-->SMemberMetadata )
/// --ScalarProperyMap ( CMemberMetadata-->SMemberMetadata )
/// --DiscriminatorProperyMap ( constant value-->SMemberMetadata )
/// --AssociationSetMapping
/// --AssociationTypeMapping
/// --TableMappingFragment
/// --EndPropertyMap
/// --ScalarPropertyMap ( CMemberMetadata-->SMemberMetadata )
/// --ScalarProperyMap ( CMemberMetadata-->SMemberMetadata )
/// --EndPropertyMap
/// --ScalarPropertyMap ( CMemberMetadata-->SMemberMetadata )
/// --EntityContainerMapping ( CMyDatabase-->SMyDatabase )
/// --CompositionSetMapping
/// --CompositionTypeMapping
/// --TableMappingFragment
/// --ParentEntityKey
/// --ScalarPropertyMap ( CMemberMetadata-->SMemberMetadata )
/// --ScalarPropertyMap ( CMemberMetadata-->SMemberMetadata )
/// --EntityKey
/// --ScalarPropertyMap ( CMemberMetadata-->SMemberMetadata )
/// --ScalarPropertyMap ( CMemberMetadata-->Constant value )
/// --ComplexPropertyMap
/// --ComplexTypeMap
/// --ScalarPropertyMap ( CMemberMetadata-->SMemberMetadata )
/// --DiscriminatorProperyMap ( constant value-->SMemberMetadata )
/// --ScalarPropertyMap ( CMemberMetadata-->Constant value )
/// The CCMappingSchemaLoader loads an Xml file that has a conceptual structure
/// equivalent to the above example into in-memory data structure in a
/// top-dwon approach.
///
///
/// The loader uses XPathNavigator to parse the XML. The advantage of using XPathNavigator
/// over DOM is that it exposes the line number of the current xml content.
/// This is really helpful when throwing exceptions. Another advantage is
///
internal class StorageMappingItemLoader
{
#region Constructors
///
/// Public constructor.
/// For Beta2 we wont support delay loading Mapping information and we would also support
/// only one mapping file for workspace.
///
///
///
///
/// Dictionary to keep the list of all scalar member mappings
internal StorageMappingItemLoader(StorageMappingItemCollection storageMappingItemCollection, string fileName, Dictionary> scalarMemberMappings)
{
Debug.Assert(storageMappingItemCollection != null);
Debug.Assert(scalarMemberMappings != null);
this.m_storageMappingItemCollection = storageMappingItemCollection;
this.m_alias = new Dictionary(StringComparer.Ordinal);
//The fileName field in this class will always have absolute path since
//StorageMappingItemCollection would have already done it while
//preparing the filePaths
if (fileName != null)
{
this.m_sourceLocation = fileName;
}
else
{
this.m_sourceLocation = null;
}
m_parsingErrors = new List();
this.m_scalarMemberMappings = scalarMemberMappings;
}
#endregion
#region Fields
private Dictionary m_alias; //To support the aliasing mechanism provided by MSL.
private StorageMappingItemCollection m_storageMappingItemCollection; //StorageMappingItemCollection
private string m_sourceLocation; //location identifier for the MSL file.
private List m_parsingErrors;
private Dictionary> m_scalarMemberMappings; // dictionary of all the scalar member mappings - this is to validate that no property is mapped to different store types across mappings.
private bool m_hasQueryViews; //set to true if any of the SetMaps have a query view so that
// cached xsd schema
private static XmlSchemaSet s_mappingXmlSchema;
#endregion
#region Properties
internal IList ParsingErrors
{
get
{
return m_parsingErrors;
}
}
internal bool HasQueryViews
{
get { return m_hasQueryViews; }
}
private EdmItemCollection EdmItemCollection
{
get
{
return m_storageMappingItemCollection.EdmItemCollection;
}
}
private StoreItemCollection StoreItemCollection
{
get
{
return m_storageMappingItemCollection.StoreItemCollection;
}
}
#endregion
#region Methods
///
/// The LoadMappingSchema method loads the mapping file and initializes the
/// MappingSchema that represents this mapping file.
/// For Beta2 atleast, we will support only one EntityContainerMapping per mapping file.
///
///
internal StorageEntityContainerMapping LoadMappingItems(XmlReader innerReader)
{
//Using XPathDocument to load the xml file into memory.
XmlReader reader = GetSchemaValidatingReader(innerReader);
try
{
XPathDocument doc = new XPathDocument(reader);
//If there were any xsd validation errors, we would have
//caught these while creatring xpath document.
if (m_parsingErrors.Count != 0)
{
//If the errors were only warnings continue, otherwise return the errors without
//loading the mapping
if (!MetadataHelper.CheckIfAllErrorsAreWarnings(m_parsingErrors))
{
return null;
}
}
//Create an XPathNavigator to navigate the document in a forward only manner.
//The XPathNavigator can also be used to run quries through the document while still maintaining
//the current position. This will be helpful in running validation rules that are not part of Schema.
XPathNavigator nav = doc.CreateNavigator();
return LoadMappingItems(nav);
}
catch (XmlException xmlException)
{
//There must have been a xml parsing exception. Add the exception information
//to the error list.
EdmSchemaError error = new EdmSchemaError(System.Data.Entity.Strings.Mapping_InvalidMappingSchema_Parsing_1(xmlException.Message)
, (int)StorageMappingErrorCode.XmlSchemaParsingError, EdmSchemaErrorSeverity.Error, m_sourceLocation, xmlException.LineNumber, xmlException.LinePosition);
m_parsingErrors.Add(error);
}
// do not close the wrapping reader here, as doing so will close the inner reader;
// see SQLBUDT 522950 for details
return null;
}
internal StorageEntityContainerMapping LoadMappingItems(XPathNavigator nav)
{
//XSD validation is not validating missing Root element.
if ((!nav.MoveToChild(StorageMslConstructs.MappingElement, StorageMslConstructs.NamespaceURI)) || (nav.NodeType != XPathNodeType.Element))
{
StorageMappingItemLoader.AddToSchemaErrors(Strings.Mapping_Invalid_CSRootElementMissing_0, StorageMappingErrorCode.RootMappingElementMissing, m_sourceLocation,
(IXmlLineInfo)nav, m_parsingErrors);
//There is no point in going forward if the required root element is not found
return null;
}
StorageEntityContainerMapping entityContainerMap =
LoadMappingChildNodes(nav.Clone());
//If there were any parsing errors, invalidate the entity container map and return null.
if (m_parsingErrors.Count != 0)
{
//If all the schema errors are warnings, don't return null
if (!MetadataHelper.CheckIfAllErrorsAreWarnings(m_parsingErrors))
{
entityContainerMap = null;
}
}
return entityContainerMap;
}
///
/// The method loads the child nodes for the root Mapping node
/// into the internal datastructures.
///
///
///
private StorageEntityContainerMapping LoadMappingChildNodes(XPathNavigator nav)
{
IXmlLineInfo navLineInfo = (IXmlLineInfo)nav;
bool hasContainerMapping;
//If there are any Alias elements in the document, they should be the first ones.
//This method can only move to the Alias element since comments, PIS etc wont have any Namespace
//though they could have same name as Alias element
if (nav.MoveToChild(StorageMslConstructs.AliasElement, StorageMslConstructs.NamespaceURI))
{
//Collect all the alias elements
do
{
m_alias.Add(StorageMappingItemLoader.GetAttributeValue(nav.Clone(), StorageMslConstructs.AliasKeyAttribute), StorageMappingItemLoader.GetAttributeValue(nav.Clone(), StorageMslConstructs.AliasValueAttribute));
} while (nav.MoveToNext(StorageMslConstructs.AliasElement, StorageMslConstructs.NamespaceURI));
//Now move on to the Next element that will be "EntityContainer" element
hasContainerMapping = nav.MoveToNext(XPathNodeType.Element);
}
else
{
//Since there was no Alias element, move on to the Container element
hasContainerMapping = nav.MoveToChild(XPathNodeType.Element);
}
//No container mappings found, so just return
if (!hasContainerMapping)
return null;
//The element name can only be EntityContainerMapping element name since XSD validation should have guarneteed this.
Debug.Assert(nav.LocalName == StorageMslConstructs.EntityContainerMappingElement);
string entityContainerName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.CDMEntityContainerAttribute);
string storageEntityContainerName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.StorageEntityContainerAttribute);
StorageEntityContainerMapping entityContainerMapping;
EntityContainer entityContainerType;
EntityContainer storageEntityContainerType;
// Now that we support partial mapping, we should first check if the entity container mapping is
// already present. If its already present, we should add the new child nodes to the existing entity container mapping
if (m_storageMappingItemCollection.TryGetItem(
entityContainerName, out entityContainerMapping))
{
entityContainerType = entityContainerMapping.EdmEntityContainer;
storageEntityContainerType = entityContainerMapping.StorageEntityContainer;
// The only thing we need to make sure is that the storage entity container mapping is the same.
if (storageEntityContainerName != storageEntityContainerType.Name)
{
AddToSchemaErrors(System.Data.Entity.Strings.StorageEntityContainerNameMismatchWhileSpecifyingPartialMapping(
storageEntityContainerName, storageEntityContainerType.Name, entityContainerType.Name),
StorageMappingErrorCode.StorageEntityContainerNameMismatchWhileSpecifyingPartialMapping,
m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
}
else
{
//At this point we know that the EdmEntityContainer has not been
//mapped already. if we do find that StorageEntityContainer
//has already been mapped, return null.
if (m_storageMappingItemCollection.ContainsStorageEntityContainer(storageEntityContainerName))
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_AlreadyMapped_StorageEntityContainer_1, storageEntityContainerName,
StorageMappingErrorCode.AlreadyMappedStorageEntityContainer, m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
//Get the CDM EntityContainer by this name from the metadata workspace.
this.EdmItemCollection.TryGetEntityContainer(entityContainerName, out entityContainerType);
if (entityContainerType == null)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_EntityContainer_1,
entityContainerName, StorageMappingErrorCode.InvalidEntityContainer, m_sourceLocation,
navLineInfo, m_parsingErrors);
}
this.StoreItemCollection.TryGetEntityContainer(storageEntityContainerName, out storageEntityContainerType);
if (storageEntityContainerType == null)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_StorageEntityContainer_1, storageEntityContainerName,
StorageMappingErrorCode.InvalidEntityContainer, m_sourceLocation, navLineInfo, m_parsingErrors);
}
//If the EntityContainerTypes are not found, there is no point in
//continuing with the parsing.
if ((entityContainerType == null) || (storageEntityContainerType == null))
{
return null;
}
//Create an EntityContainerMapping object to hold the mapping information for this
//EntityContainer. Create a MappingKey and pass it in.
entityContainerMapping = new StorageEntityContainerMapping(entityContainerType, storageEntityContainerType, m_storageMappingItemCollection);
entityContainerMapping.StartLineNumber = navLineInfo.LineNumber;
entityContainerMapping.StartLinePosition = navLineInfo.LinePosition;
}
//Load the child nodes for the created EntityContainerMapping
LoadEntityContainerMapping(nav.Clone(), entityContainerMapping, storageEntityContainerType);
return entityContainerMapping;
}
///
/// The method loads the child nodes for the EntityContainer Mapping node
/// into the internal datastructures.
///
///
///
///
private void LoadEntityContainerMapping(XPathNavigator nav, StorageEntityContainerMapping entityContainerMapping, EntityContainer storageEntityContainerType)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
bool anyEntitySetMapped = false;
//If there is no child node for the EntityContainerMapping Element, return.
if (nav.MoveToChild(XPathNodeType.Element))
{
//The valid child nodes for EntityContainerMapping node are various SetMappings( EntitySet, AssociationSet etc ).
//Loop through the child nodes and lod them as children of the EntityContainerMapping object.
do
{
switch (nav.LocalName)
{
case StorageMslConstructs.EntitySetMappingElement:
{
LoadEntitySetMapping(nav.Clone(), entityContainerMapping, storageEntityContainerType);
anyEntitySetMapped = true;
break;
}
case StorageMslConstructs.AssociationSetMappingElement:
{
LoadAssociationSetMapping(nav.Clone(), entityContainerMapping, storageEntityContainerType);
break;
}
case StorageMslConstructs.FunctionImportMappingElement:
{
LoadFunctionImportMapping(nav.Clone(), entityContainerMapping, storageEntityContainerType);
break;
}
default:
AddToSchemaErrors(Strings.Mapping_InvalidContent_Set_Mapping_0,
StorageMappingErrorCode.SetMappingExpected, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
break;
}
} while (nav.MoveToNext(XPathNodeType.Element));
}
//If the EntityContainer contains entity sets but they are not mapped then we should add an error
if (entityContainerMapping.EdmEntityContainer.BaseEntitySets.Count != 0 && !anyEntitySetMapped)
{
AddToSchemaErrorsWithMemberInfo(Strings.ViewGen_Missing_Sets_Mapping_0,
entityContainerMapping.EdmEntityContainer.Name, StorageMappingErrorCode.EmptyContainerMapping,
this.m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return;
}
ValidateFunctionMappingClosure(nav.Clone(), entityContainerMapping);
ValidateFunctionAssociationFunctionMappingUnique(nav.Clone(), entityContainerMapping);
ValidateAssociationFunctionMappingConsistent(nav.Clone(), entityContainerMapping);
ValidateQueryViewsClosure(nav.Clone(), entityContainerMapping);
//The fileName field in this class will always have absolute path since
//StorageMappingItemCollection would have already done it while
//preparing the filePaths
entityContainerMapping.SourceLocation = m_sourceLocation;
}
///
/// Validates that collocated association sets are consistently mapped for each entity set (all operations or none). In the case
/// of relationships between sub-types of an entity set, ensures the relationship mapping is legal.
///
///
///
private void ValidateAssociationFunctionMappingConsistent(XPathNavigator nav, StorageEntityContainerMapping entityContainerMapping)
{
foreach (StorageEntitySetMapping entitySetMapping in entityContainerMapping.EntitySetMaps)
{
if (entitySetMapping.FunctionMappings.Count > 0)
{
// determine the set of ----oication sets that should be mapped for every operation
Set expectedEnds = new Set(
entitySetMapping.ImplicitlyMappedAssociationSetEnds).MakeReadOnly();
// check that each operation covers each association set
foreach (StorageEntityTypeFunctionMapping entityTypeMapping in entitySetMapping.FunctionMappings)
{
ValidateAssociationFunctionMappingConsistent(nav, entitySetMapping, entityTypeMapping,
entityTypeMapping.DeleteFunctionMapping,
expectedEnds, StorageMslConstructs.DeleteFunctionElement);
ValidateAssociationFunctionMappingConsistent(nav, entitySetMapping, entityTypeMapping,
entityTypeMapping.InsertFunctionMapping,
expectedEnds, StorageMslConstructs.InsertFunctionElement);
ValidateAssociationFunctionMappingConsistent(nav, entitySetMapping, entityTypeMapping,
entityTypeMapping.UpdateFunctionMapping,
expectedEnds, StorageMslConstructs.UpdateFunctionElement);
}
}
}
}
private void ValidateAssociationFunctionMappingConsistent(XPathNavigator nav,
StorageEntitySetMapping entitySetMapping, StorageEntityTypeFunctionMapping entityTypeMapping,
StorageFunctionMapping functionMapping, Set expectedEnds, string elementName)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
// check that all expected association sets are mapped for in this function mapping
Set actualEnds = new Set(
functionMapping.GetReferencedAssociationSetEnds((EntitySet)entitySetMapping.Set));
actualEnds.MakeReadOnly();
// check that all required ends are present
foreach (AssociationSetEnd expectedEnd in expectedEnds)
{
// check that the association set is required based on the entity type
if (MetadataHelper.IsAssociationValidForEntityType(expectedEnd, entityTypeMapping.EntityType))
{
if (!actualEnds.Contains(expectedEnd))
{
AddToSchemaErrorWithMessage(System.Data.Entity.Strings.Mapping_Invalid_Function_Mapping_AssociationSetNotMappedForOperation_4(
entitySetMapping.Set.Name,
expectedEnd.ParentAssociationSet.Name,
elementName,
entityTypeMapping.EntityType.FullName),
StorageMappingErrorCode.InvalidFunctionMappingAssociationSetNotMappedForOperation,
m_sourceLocation,
xmlLineInfoNav,
m_parsingErrors);
}
}
}
// check that no ends with invalid types are included
foreach (AssociationSetEnd actualEnd in actualEnds)
{
if (!MetadataHelper.IsAssociationValidForEntityType(actualEnd, entityTypeMapping.EntityType))
{
AddToSchemaErrorWithMessage(System.Data.Entity.Strings.Mapping_Invalid_Function_Mapping_AssociationEndMappingInvalidForEntityType_3(
entityTypeMapping.EntityType.FullName,
actualEnd.ParentAssociationSet.Name,
MetadataHelper.GetEntityTypeForEnd(MetadataHelper.GetOppositeEnd(actualEnd).CorrespondingAssociationEndMember).FullName),
StorageMappingErrorCode.InvalidFunctionMappingAssociationEndMappingInvalidForEntityType,
m_sourceLocation,
xmlLineInfoNav,
m_parsingErrors);
}
}
}
///
/// Validates that association sets are only mapped once.
///
///
/// Container to validate
private void ValidateFunctionAssociationFunctionMappingUnique(XPathNavigator nav, StorageEntityContainerMapping entityContainerMapping)
{
Dictionary mappingCounts = new Dictionary();
// Walk through all entity set mappings
foreach (StorageEntitySetMapping entitySetMapping in entityContainerMapping.EntitySetMaps)
{
if (entitySetMapping.FunctionMappings.Count > 0)
{
// Get set of association sets implicitly mapped associations to avoid double counting
Set associationSets = new Set();
foreach (AssociationSetEnd end in entitySetMapping.ImplicitlyMappedAssociationSetEnds)
{
associationSets.Add(end.ParentAssociationSet);
}
foreach (EntitySetBase associationSet in associationSets)
{
IncrementCount(mappingCounts, associationSet);
}
}
}
// Walk through all association set mappings
foreach (StorageAssociationSetMapping associationSetMapping in entityContainerMapping.RelationshipSetMaps)
{
if (null != associationSetMapping.FunctionMapping)
{
IncrementCount(mappingCounts, associationSetMapping.Set);
}
}
// Check for redundantly mapped association sets
List violationNames = new List();
foreach (KeyValuePair mappingCount in mappingCounts)
{
if (mappingCount.Value > 1)
{
violationNames.Add(mappingCount.Key.Name);
}
}
if (0 < violationNames.Count)
{
// Warn the user that association sets are mapped multiple times
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_Invalid_Function_Mapping_AssociationSetAmbiguous_1,
StringUtil.ToCommaSeparatedString(violationNames), StorageMappingErrorCode.AmbiguousFunctionMappingForAssociationSet,
m_sourceLocation, (IXmlLineInfo)nav, m_parsingErrors);
}
}
private static void IncrementCount(Dictionary counts, T key)
{
int count;
if (counts.TryGetValue(key, out count))
{
count++;
}
else
{
count = 1;
}
counts[key] = count;
}
///
/// Validates that all or no related extents have function mappings. If an EntitySet has a function mapping,
/// then all 'reference' association sets must also have function mappings. Similarly, if an AssociationSet has a function
/// mapping, 'reference' ends must have function mappings.
///
///
/// Container to validate.
private void ValidateFunctionMappingClosure(XPathNavigator nav, StorageEntityContainerMapping entityContainerMapping)
{
// Check that function mappings apply to complete subgraph by tracking which extents have function
// mappings and which extents must include function mappings
Set setsWithFunctionMapping = new Set();
// Walk through all entity set mappings
foreach (StorageEntitySetMapping entitySetMapping in entityContainerMapping.EntitySetMaps)
{
if (entitySetMapping.FunctionMappings.Count > 0)
{
// a function mapping exists for this entity set
setsWithFunctionMapping.Add(entitySetMapping.Set);
// add all implicitly mapped association sets
foreach (AssociationSetEnd end in entitySetMapping.ImplicitlyMappedAssociationSetEnds)
{
setsWithFunctionMapping.Add(end.ParentAssociationSet);
}
}
}
// Walk through all association set mapping
foreach (StorageAssociationSetMapping associationSetMapping in entityContainerMapping.RelationshipSetMaps)
{
if (null != associationSetMapping.FunctionMapping)
{
setsWithFunctionMapping.Add(associationSetMapping.Set);
}
}
// Initialize sets requiring function mapping with the sets that are actually function mapped
Set setsRequiringFunctionMapping = FindMissingFunctionMappings(entityContainerMapping, setsWithFunctionMapping);
// Check that no required entity or association sets are missing
if (0 < setsRequiringFunctionMapping.Count)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_Invalid_Function_Mapping_MissingSetClosure_1,
StringUtil.ToCommaSeparatedString(setsRequiringFunctionMapping),
StorageMappingErrorCode.MissingSetClosureInFunctionMapping, m_sourceLocation, (IXmlLineInfo)nav
, m_parsingErrors);
}
}
private static Set FindMissingFunctionMappings(StorageEntityContainerMapping entityContainerMapping, Set setsWithFunctionMapping)
{
Set setsRequiringFunctionMapping = new Set();
foreach (EntitySetBase entitySetBase in setsWithFunctionMapping)
{
if (BuiltInTypeKind.AssociationSet == entitySetBase.BuiltInTypeKind)
{
AssociationSet associationSet = (AssociationSet)entitySetBase;
// add the entity sets bound to the end roles to the required list
foreach (AssociationSetEnd end in associationSet.AssociationSetEnds)
{
var multiplicity = end.CorrespondingAssociationEndMember.RelationshipMultiplicity;
if (multiplicity == RelationshipMultiplicity.One ||
multiplicity == RelationshipMultiplicity.ZeroOrOne)
{
// for 'reference' ends of associations, the entity set is required
EntitySet requiredSet = MetadataHelper.GetOppositeEnd(end).EntitySet;
if (!setsWithFunctionMapping.Contains(requiredSet))
{
setsRequiringFunctionMapping.Add(MetadataHelper.GetOppositeEnd(end).EntitySet);
}
}
}
}
}
// Register all association sets referencing known entity sets
foreach (EntitySetBase entitySetBase in entityContainerMapping.EdmEntityContainer.BaseEntitySets)
{
if (BuiltInTypeKind.AssociationSet == entitySetBase.BuiltInTypeKind)
{
AssociationSet associationSet = (AssociationSet)entitySetBase;
// check that this association set isn't already mapped
if (!setsWithFunctionMapping.Contains(associationSet))
{
foreach (AssociationSetEnd end in associationSet.AssociationSetEnds)
{
var multiplicity = end.CorrespondingAssociationEndMember.RelationshipMultiplicity;
if (multiplicity == RelationshipMultiplicity.One ||
multiplicity == RelationshipMultiplicity.ZeroOrOne)
{
var oppositeEnd = MetadataHelper.GetOppositeEnd(end);
// check if the opposite end has a function mapping (which means
// this AssociationSet is a required reference end)
if (setsWithFunctionMapping.Contains(oppositeEnd.EntitySet))
{
setsRequiringFunctionMapping.Add(associationSet);
}
}
}
}
}
}
return setsRequiringFunctionMapping;
}
private static void ValidateClosureAmongSets(StorageEntityContainerMapping entityContainerMapping, Set sets, Set additionalSetsInClosure)
{
bool nodeFound;
do
{
nodeFound = false;
List newNodes = new List();
// Register entity sets dependencies for association sets
foreach (EntitySetBase entitySetBase in additionalSetsInClosure)
{
if (BuiltInTypeKind.AssociationSet == entitySetBase.BuiltInTypeKind)
{
AssociationSet associationSet = (AssociationSet)entitySetBase;
// add the entity sets bound to the end roles to the required list
foreach (AssociationSetEnd end in associationSet.AssociationSetEnds)
{
if (!additionalSetsInClosure.Contains(end.EntitySet))
{
newNodes.Add(end.EntitySet);
}
}
}
}
// Register all association sets referencing known entity sets
foreach (EntitySetBase entitySetBase in entityContainerMapping.EdmEntityContainer.BaseEntitySets)
{
if (BuiltInTypeKind.AssociationSet == entitySetBase.BuiltInTypeKind)
{
AssociationSet associationSet = (AssociationSet)entitySetBase;
// check that this association set isn't already in the required set
if (!additionalSetsInClosure.Contains(associationSet))
{
foreach (AssociationSetEnd end in associationSet.AssociationSetEnds)
{
if (additionalSetsInClosure.Contains(end.EntitySet))
{
// this association set must be added to the required list if
// any of its ends are in that list
newNodes.Add(associationSet);
break; // no point adding the association set twice
}
}
}
}
}
if (0 < newNodes.Count)
{
nodeFound = true;
additionalSetsInClosure.AddRange(newNodes);
}
}
while (nodeFound);
additionalSetsInClosure.Subtract(sets);
}
///
/// Validates that all or no related extents have query views defined. If an extent has a query view defined, then
/// all related extents must also have query views.
///
///
/// Container to validate.
private void ValidateQueryViewsClosure(XPathNavigator nav, StorageEntityContainerMapping entityContainerMapping)
{
//If there is no query view defined, no need to validate
if (!m_hasQueryViews)
{
return;
}
// Check that query views apply to complete subgraph by tracking which extents have query
// mappings and which extents must include query views
Set setsWithQueryViews = new Set();
Set setsRequiringQueryViews = new Set();
// Walk through all set mappings
foreach (StorageSetMapping setMapping in entityContainerMapping.AllSetMaps)
{
if (setMapping.QueryView != null)
{
// a function mapping exists for this entity set
setsWithQueryViews.Add(setMapping.Set);
}
}
// Initialize sets requiring function mapping with the sets that are actually function mapped
setsRequiringQueryViews.AddRange(setsWithQueryViews);
ValidateClosureAmongSets(entityContainerMapping, setsWithQueryViews, setsRequiringQueryViews);
// Check that no required entity or association sets are missing
if (0 < setsRequiringQueryViews.Count)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_Invalid_Query_Views_MissingSetClosure_1,
StringUtil.ToCommaSeparatedString(setsRequiringQueryViews),
StorageMappingErrorCode.MissingSetClosureInQueryViews, m_sourceLocation, (IXmlLineInfo)nav
, m_parsingErrors);
}
}
///
/// The method loads the child nodes for the EntitySet Mapping node
/// into the internal datastructures.
///
///
///
///
private void LoadEntitySetMapping(XPathNavigator nav, StorageEntityContainerMapping entityContainerMapping, EntityContainer storageEntityContainerType)
{
//Get the EntitySet name
string entitySetName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.EntitySetMappingNameAttribute);
//Get the EntityType name, need to parse it if the mapping information is being specified for multiple types
string entityTypeName = StorageMappingItemLoader.GetAttributeValue(nav.Clone(), StorageMslConstructs.EntitySetMappingTypeNameAttribute);
//Get the table name. This might be emptystring since the user can have a TableMappingFragment instead of this.
string tableName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.EntitySetMappingStoreEntitySetAttribute);
EntitySet entitySet;
// First check to see if the Entity Set Mapping is already specified. It can be specified, in the same schema file later on
// on a totally different file. Since we support partial mapping, we should just add mapping fragments or entity type
// mappings to the existing entity set mapping
StorageEntitySetMapping setMapping = (StorageEntitySetMapping)entityContainerMapping.GetEntitySetMapping(entitySetName);
// Update the info about the schema element
IXmlLineInfo navLineInfo = (IXmlLineInfo)nav;
if (setMapping == null)
{
//Try to find the EntitySet with the given name in the EntityContainer.
if (!entityContainerMapping.EdmEntityContainer.TryGetEntitySetByName(entitySetName, /*ignoreCase*/ false, out entitySet))
{
//If no EntitySet with the given name exists, than add a schema error and return
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Entity_Set_1, entitySetName,
StorageMappingErrorCode.InvalidEntitySet, m_sourceLocation, navLineInfo, m_parsingErrors);
//There is no point in continuing the loding of this EntitySetMapping if the EntitySet is not found
return;
}
//Create the EntitySet Mapping which contains the mapping information for EntitySetMap.
setMapping = new StorageEntitySetMapping(entitySet, entityContainerMapping);
}
else
{
entitySet = (EntitySet)setMapping.Set;
}
//Set the Start Line Information on Fragment
setMapping.StartLineNumber = navLineInfo.LineNumber;
setMapping.StartLinePosition = navLineInfo.LinePosition;
entityContainerMapping.AddEntitySetMapping(setMapping);
//If the TypeName was not specified as an attribute, than an EntityTypeMapping element should be present
if (String.IsNullOrEmpty(entityTypeName))
{
if (nav.MoveToChild(XPathNodeType.Element))
{
do
{
switch (nav.LocalName)
{
case StorageMslConstructs.EntityTypeMappingElement:
{
//TableName could also be specified on EntityTypeMapping element
tableName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.EntityTypeMappingStoreEntitySetAttribute);
//Load the EntityTypeMapping into memory.
LoadEntityTypeMapping(nav.Clone(), setMapping, tableName, storageEntityContainerType);
break;
}
case StorageMslConstructs.QueryViewElement:
{
if (!(String.IsNullOrEmpty(tableName)))
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_TableName_QueryView_1, entitySetName,
StorageMappingErrorCode.TableNameAttributeWithQueryView, m_sourceLocation, navLineInfo, m_parsingErrors);
return;
}
//Load the Query View into the set mapping,
//if you get an error, return immediately since
//you go on, you could be giving lot of dubious errors
if(!LoadQueryView(nav.Clone(), setMapping))
{
return;
}
break;
}
default:
AddToSchemaErrors(Strings.Mapping_InvalidContent_TypeMapping_QueryView,
StorageMappingErrorCode.InvalidContent, m_sourceLocation, navLineInfo, m_parsingErrors);
break;
}
} while (nav.MoveToNext(XPathNodeType.Element));
}
}
else
{
//Load the EntityTypeMapping into memory.
LoadEntityTypeMapping(nav.Clone(), setMapping, tableName, storageEntityContainerType);
}
ValidateAllEntityTypesHaveFunctionMapping(nav.Clone(), setMapping);
//Add a schema error if the set mapping has no content
if (setMapping.HasNoContent)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Emtpty_SetMap_1, entitySet.Name,
StorageMappingErrorCode.EmptySetMapping, m_sourceLocation, navLineInfo, m_parsingErrors);
}
}
// Ensure if any type has a function mapping, all types have function mappings
private void ValidateAllEntityTypesHaveFunctionMapping(XPathNavigator nav, StorageEntitySetMapping setMapping)
{
Set functionMappedTypes = new Set();
foreach (StorageEntityTypeFunctionMapping functionMapping in setMapping.FunctionMappings)
{
functionMappedTypes.Add(functionMapping.EntityType);
}
if (0 < functionMappedTypes.Count)
{
Set unmappedTypes = new Set(MetadataHelper.GetTypeAndSubtypesOf(setMapping.Set.ElementType, EdmItemCollection, false /*includeAbstractTypes*/));
unmappedTypes.Subtract(functionMappedTypes);
// Remove abstract types
Set abstractTypes = new Set();
foreach (EntityType unmappedType in unmappedTypes)
{
if (unmappedType.Abstract)
{
abstractTypes.Add(unmappedType);
}
}
unmappedTypes.Subtract(abstractTypes);
// See if there are any remaining entity types requiring function mapping
if (0 < unmappedTypes.Count)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_Invalid_Function_Mapping_MissingEntityType_1,
StringUtil.ToCommaSeparatedString(unmappedTypes),
StorageMappingErrorCode.MissingFunctionMappingForEntityType, m_sourceLocation, (IXmlLineInfo)nav
, m_parsingErrors);
}
}
}
private bool TryParseEntityTypeAttribute(XPathNavigator nav, EntityType rootEntityType,
Func typeNotAssignableMessage,
out Set isOfTypeEntityTypes, out Set entityTypes)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
string entityTypeAttribute = GetAttributeValue(nav.Clone(), StorageMslConstructs.EntitySetMappingTypeNameAttribute);
isOfTypeEntityTypes = new Set();
entityTypes = new Set();
// get components of type declaration
var entityTypeNames = entityTypeAttribute.Split(StorageMslConstructs.TypeNameSperator).Select(s => s.Trim());
// figure out each component
foreach (var name in entityTypeNames)
{
bool isTypeOf = name.StartsWith(StorageMslConstructs.IsTypeOf, StringComparison.Ordinal);
string entityTypeName;
if (isTypeOf)
{
// get entityTypeName of OfType(entityTypeName)
if (!name.EndsWith(StorageMslConstructs.IsTypeOfTerminal, StringComparison.Ordinal))
{
AddToSchemaErrorWithMessage(System.Data.Entity.Strings.Mapping_InvalidContent_IsTypeOfNotTerminated,
StorageMappingErrorCode.InvalidEntityType, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
// No point in continuing with an error in the entitytype name
return false;
}
entityTypeName = name.Substring(StorageMslConstructs.IsTypeOf.Length);
entityTypeName = entityTypeName.Substring(0, entityTypeName.Length - StorageMslConstructs.IsTypeOfTerminal.Length).Trim();
}
else
{
entityTypeName = name;
}
// resolve aliases
entityTypeName = GetAliasResolvedValue(entityTypeName);
EntityType entityType;
if (!this.EdmItemCollection.TryGetItem(entityTypeName, out entityType))
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Entity_Type_1, entityTypeName,
StorageMappingErrorCode.InvalidEntityType, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
// No point in continuing with an error in the entitytype name
return false;
}
if (!(Helper.IsAssignableFrom(rootEntityType, entityType)))
{
IXmlLineInfo lineInfo = xmlLineInfoNav;
AddToSchemaErrorWithMessage(
typeNotAssignableMessage(entityType),
StorageMappingErrorCode.InvalidEntityType, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
//no point in continuing with an error in the entitytype name
return false;
}
// Using TypeOf construct on an abstract type that does not have
// any concrete descendants is not allowed
if (entityType.Abstract)
{
if (isTypeOf)
{
IEnumerable typeAndSubTypes = MetadataHelper.GetTypeAndSubtypesOf(entityType, EdmItemCollection, false /*includeAbstractTypes*/);
if (!typeAndSubTypes.GetEnumerator().MoveNext())
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_AbstractEntity_IsOfType_1, entityType.FullName,
StorageMappingErrorCode.MappingForAbstractEntityType, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return false;
}
}
else
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_AbstractEntity_Type_1, entityType.FullName,
StorageMappingErrorCode.MappingForAbstractEntityType, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return false;
}
}
// Add type to set
if (isTypeOf)
{
isOfTypeEntityTypes.Add(entityType);
}
else
{
entityTypes.Add(entityType);
}
}
// No failures
return true;
}
///
/// The method loads the child nodes for the EntityType Mapping node
/// into the internal datastructures.
///
///
///
///
///
private void LoadEntityTypeMapping(XPathNavigator nav, StorageEntitySetMapping entitySetMapping, string tableName, EntityContainer storageEntityContainerType)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
//Create an EntityTypeMapping to hold the information for EntityType mapping.
StorageEntityTypeMapping entityTypeMapping = new StorageEntityTypeMapping(entitySetMapping);
//Get entity types
Set entityTypes;
Set isOfTypeEntityTypes;
EntityType rootEntityType = (EntityType)entitySetMapping.Set.ElementType;
if (!TryParseEntityTypeAttribute(nav.Clone(), rootEntityType,
e => System.Data.Entity.Strings.Mapping_InvalidContent_Entity_Type_For_Entity_Set_3(e.FullName, rootEntityType.FullName, entitySetMapping.Set.Name),
out isOfTypeEntityTypes,
out entityTypes))
{
// Return if we cannot parse entity types
return;
}
// Register all mapped types
foreach (EntityType entityType in entityTypes)
{
entityTypeMapping.AddType(entityType);
}
foreach (EntityType isOfTypeEntityType in isOfTypeEntityTypes)
{
entityTypeMapping.AddIsOfType(isOfTypeEntityType);
}
//If the table name was not specified on the EntitySetMapping element nor the EntityTypeMapping element
//than a table mapping fragment element should be present
//Loop through the TableMappingFragment elements and add them to EntityTypeMappings
if (String.IsNullOrEmpty(tableName))
{
if (!nav.MoveToChild(XPathNodeType.Element))
return;
do
{
if (nav.LocalName == StorageMslConstructs.ModificationFunctionMappingElement)
{
LoadEntityTypeFunctionMapping(nav.Clone(), entitySetMapping, entityTypeMapping);
}
else if (nav.LocalName != StorageMslConstructs.MappingFragmentElement)
{
AddToSchemaErrors(Strings.Mapping_InvalidContent_Table_Expected_0,
StorageMappingErrorCode.TableMappingFragmentExpected, m_sourceLocation, xmlLineInfoNav
, m_parsingErrors);
}
else
{
tableName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.MappingFragmentStoreEntitySetAttribute);
StorageMappingFragment fragment = LoadMappingFragment(nav.Clone(), entityTypeMapping, tableName,
storageEntityContainerType);
//The fragment can be null in the cases of validation errors.
if (fragment != null)
{
entityTypeMapping.AddFragment(fragment);
}
}
} while (nav.MoveToNext(XPathNodeType.Element));
}
else
{
if (nav.LocalName == StorageMslConstructs.ModificationFunctionMappingElement)
{
// function mappings cannot exist in the context of a table mapping
AddToSchemaErrors(Strings.Mapping_Invalid_Function_Mapping_In_Table_Context_0,
StorageMappingErrorCode.InvalidTableNameAttributeWithFunctionMapping,
m_sourceLocation, xmlLineInfoNav
, m_parsingErrors);
}
StorageMappingFragment fragment = LoadMappingFragment(nav.Clone(), entityTypeMapping, tableName,
storageEntityContainerType);
//The fragment can be null in the cases of validation errors.
if (fragment != null)
{
entityTypeMapping.AddFragment(fragment);
}
}
entitySetMapping.AddTypeMapping(entityTypeMapping);
}
///
/// Loads function mappings for entity type.
///
///
///
///
private void LoadEntityTypeFunctionMapping(XPathNavigator nav, StorageEntitySetMapping entitySetMapping,
StorageEntityTypeMapping entityTypeMapping)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
// Function mappings can apply only to a single type.
if (entityTypeMapping.IsOfTypes.Count != 0 || entityTypeMapping.Types.Count != 1)
{
AddToSchemaErrors(Strings.Mapping_Invalid_Function_Mapping_Multiple_Types_0,
StorageMappingErrorCode.InvalidFunctionMappingForMultipleTypes,
m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return;
}
EntityType entityType = (EntityType)entityTypeMapping.Types[0];
//Function Mapping is not allowed to be defined for Abstract Types
if (entityType.Abstract)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_AbstractEntity_FunctionMapping_1, entityType.FullName,
StorageMappingErrorCode.MappingForAbstractEntityType, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return;
}
// check that no mapping exists for this entity type already
foreach (StorageEntityTypeFunctionMapping existingFunctionMapping in entitySetMapping.FunctionMappings)
{
if (existingFunctionMapping.EntityType.Equals(entityType))
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_Invalid_Function_Mapping_RedundantEntityTypeMapping_1,
entityType.Name, StorageMappingErrorCode.RedundantEntityTypeMappingInFunctionMapping, m_sourceLocation, xmlLineInfoNav
, m_parsingErrors);
return;
}
}
// create function loader
FunctionMappingLoader functionLoader = new FunctionMappingLoader(this, entitySetMapping.Set);
// Load all function definitions (for insert, delete and update)
StorageFunctionMapping deleteFunctionMapping = null;
StorageFunctionMapping insertFunctionMapping = null;
StorageFunctionMapping updateFunctionMapping = null;
if (nav.MoveToChild(XPathNodeType.Element))
{
do
{
switch (nav.LocalName)
{
case StorageMslConstructs.DeleteFunctionElement:
deleteFunctionMapping = functionLoader.LoadEntityTypeFunctionMapping(nav.Clone(), false, true, entityType);
break;
case StorageMslConstructs.InsertFunctionElement:
insertFunctionMapping = functionLoader.LoadEntityTypeFunctionMapping(nav.Clone(), true, false, entityType);
break;
case StorageMslConstructs.UpdateFunctionElement:
updateFunctionMapping = functionLoader.LoadEntityTypeFunctionMapping(nav.Clone(), true, true, entityType);
break;
}
} while (nav.MoveToNext(XPathNodeType.Element));
}
//We might be returned null because of schema errors
if (null == deleteFunctionMapping || null == insertFunctionMapping
|| null == updateFunctionMapping)
{
return;
}
// Ensure that assocation set end mappings bind to the same end (e.g., in Person Manages Person
// self-association, ensure that the manager end or the report end is mapped but not both)
Dictionary associationEnds = new Dictionary();
foreach (StorageFunctionParameterBinding parameterBinding in Helper.Concat(
deleteFunctionMapping.ParameterBindings,
insertFunctionMapping.ParameterBindings,
updateFunctionMapping.ParameterBindings))
{
if (null != parameterBinding.MemberPath.AssociationSetEnd)
{
AssociationSet associationSet = parameterBinding.MemberPath.AssociationSetEnd.ParentAssociationSet;
// the "end" corresponds to the second member in the path, e.g.
// ID<-Manager where Manager is the end
EdmMember currentEnd = parameterBinding.MemberPath.AssociationSetEnd.CorrespondingAssociationEndMember;
EdmMember existingEnd;
if (associationEnds.TryGetValue(associationSet, out existingEnd) &&
existingEnd != currentEnd)
{
AddToSchemaErrorWithMessage(System.Data.Entity.Strings.Mapping_Invalid_Function_Mapping_MultipleEndsOfAssociationMapped_3(
currentEnd.Name, existingEnd.Name, associationSet.Name),
StorageMappingErrorCode.InvalidFunctionMappingMultipleEndsOfAssociationMapped, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return;
}
else
{
associationEnds[associationSet] = currentEnd;
}
}
}
// Register the function mapping on the entity set mapping
StorageEntityTypeFunctionMapping functionMapping = new StorageEntityTypeFunctionMapping(
entityType, deleteFunctionMapping, insertFunctionMapping, updateFunctionMapping);
entitySetMapping.AddFunctionMapping(functionMapping);
}
///
/// The method loads the query view for the Set Mapping node
/// into the internal datastructures.
///
private bool LoadQueryView(XPathNavigator nav, StorageSetMapping setMapping)
{
Debug.Assert(nav.LocalName == StorageMslConstructs.QueryViewElement);
string queryView = nav.Value;
bool includeSubtypes = false;
string typeNameString = StorageMappingItemLoader.GetAttributeValue(nav.Clone(), StorageMslConstructs.EntitySetMappingTypeNameAttribute);
if (typeNameString != null)
{
typeNameString = typeNameString.Trim();
}
if (setMapping.QueryView == null)
{
//QV must be the special-case first view
if (typeNameString != null)
{
AddToSchemaErrorsWithMemberInfo(val => Strings.Mapping_TypeName_For_First_QueryView,
setMapping.Set.Name, StorageMappingErrorCode.TypeNameForFirstQueryView,
m_sourceLocation, (IXmlLineInfo)nav, m_parsingErrors);
return false;
}
if (String.IsNullOrEmpty(queryView))
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_Empty_QueryView_1,
setMapping.Set.Name, StorageMappingErrorCode.EmptyQueryView,
m_sourceLocation, (IXmlLineInfo)nav, m_parsingErrors);
return false;
}
setMapping.QueryView = queryView;
this.m_hasQueryViews = true;
return true;
}
else
{
//QV must be typeof or typeofonly view
if (typeNameString == null || typeNameString.Trim().Length == 0)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_QueryView_TypeName_Not_Defined,
setMapping.Set.Name, StorageMappingErrorCode.NoTypeNameForTypeSpecificQueryView,
m_sourceLocation, (IXmlLineInfo)nav, m_parsingErrors);
return false;
}
//Get entity types
Set entityTypes;
Set isOfTypeEntityTypes;
EntityType rootEntityType = (EntityType)setMapping.Set.ElementType;
if (!TryParseEntityTypeAttribute(nav.Clone(), rootEntityType,
e => System.Data.Entity.Strings.Mapping_InvalidContent_Entity_Type_For_Entity_Set_3(e.FullName, rootEntityType.FullName, setMapping.Set.Name),
out isOfTypeEntityTypes,
out entityTypes))
{
// Return if we cannot parse entity types
return false;
}
Debug.Assert(isOfTypeEntityTypes.Count > 0 || entityTypes.Count > 0);
Debug.Assert(!(isOfTypeEntityTypes.Count > 0 && entityTypes.Count > 0));
EntityType entityType;
if (isOfTypeEntityTypes.Count == 1)
{ //OfType View
entityType = isOfTypeEntityTypes.First();
includeSubtypes = true;
}
else if (entityTypes.Count == 1)
{ //OfTypeOnly View
entityType = entityTypes.First();
includeSubtypes = false;
}
else
{
//More than one type
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_QueryViewMultipleTypeInTypeName, setMapping.Set.ToString(),
StorageMappingErrorCode.TypeNameContainsMultipleTypesForQueryView, m_sourceLocation, (IXmlLineInfo)nav, m_parsingErrors);
return false;
}
//Check if IsTypeOf(A) and A is the base type
if (includeSubtypes && setMapping.Set.ElementType.EdmEquals(entityType))
{ //Don't allow TypeOFOnly(a) if a is a base type.
AddToSchemaErrorWithMemberAndStructure(Strings.Mapping_QueryView_For_Base_Type, entityType.ToString(), setMapping.Set.ToString(),
StorageMappingErrorCode.IsTypeOfQueryViewForBaseType, m_sourceLocation, (IXmlLineInfo)nav, m_parsingErrors);
return false;
}
if (String.IsNullOrEmpty(queryView))
{
if (includeSubtypes)
{
AddToSchemaErrorWithMemberAndStructure(Strings.Mapping_Empty_QueryView_OfType_2,
entityType.Name, setMapping.Set.Name, StorageMappingErrorCode.EmptyQueryView,
m_sourceLocation, (IXmlLineInfo)nav, m_parsingErrors);
return false;
}
else
{
AddToSchemaErrorWithMemberAndStructure(Strings.Mapping_Empty_QueryView_OfTypeOnly_2,
setMapping.Set.Name, entityType.Name, StorageMappingErrorCode.EmptyQueryView,
m_sourceLocation, (IXmlLineInfo)nav, m_parsingErrors);
return false;
}
}
//Add it to the QV cache
Triple key = new Triple(setMapping.Set, new Pair(entityType, includeSubtypes));
if (setMapping.ContainsTypeSpecificQueryView(key))
{ //two QVs for the same type
EdmSchemaError error = null;
if (includeSubtypes)
{
error =
new EdmSchemaError(
Strings.Mapping_QueryView_Duplicate_OfType(setMapping.Set, entityType),
(int)StorageMappingErrorCode.QueryViewExistsForEntitySetAndType, EdmSchemaErrorSeverity.Error, m_sourceLocation,
((IXmlLineInfo)nav).LineNumber, ((IXmlLineInfo)nav).LinePosition);
}
else
{
error =
new EdmSchemaError(
Strings.Mapping_QueryView_Duplicate_OfTypeOnly(setMapping.Set, entityType),
(int)StorageMappingErrorCode.QueryViewExistsForEntitySetAndType, EdmSchemaErrorSeverity.Error, m_sourceLocation,
((IXmlLineInfo)nav).LineNumber, ((IXmlLineInfo)nav).LinePosition);
}
m_parsingErrors.Add(error);
return false;
}
setMapping.AddTypeSpecificQueryView(key, queryView);
return true;
}
}
///
/// The method loads the child nodes for the AssociationSet Mapping node
/// into the internal datastructures.
///
///
///
///
private void LoadAssociationSetMapping(XPathNavigator nav, StorageEntityContainerMapping entityContainerMapping, EntityContainer storageEntityContainerType)
{
IXmlLineInfo navLineInfo = (IXmlLineInfo)nav;
//Get the AssociationSet name
string associationSetName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.AssociationSetMappingNameAttribute);
//Get the AssociationType name, need to parse it if the mapping information is being specified for multiple types
string associationTypeName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.AssociationSetMappingTypeNameAttribute);
//Get the table name. This might be emptystring since the user can have a TableMappingFragment instead of this.
string tableName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.EntitySetMappingStoreEntitySetAttribute);
//Try to find the AssociationSet with the given name in the EntityContainer.
RelationshipSet relationshipSet;
entityContainerMapping.EdmEntityContainer.TryGetRelationshipSetByName(associationSetName, false /*ignoreCase*/, out relationshipSet);
AssociationSet associationSet = relationshipSet as AssociationSet;
//If no AssociationSet with the given name exists, than Add a schema error and return
if (associationSet == null)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Association_Set_1, associationSetName,
StorageMappingErrorCode.InvalidAssociationSet, m_sourceLocation, navLineInfo, m_parsingErrors);
//There is no point in continuing the loading of association set map if the AssociationSetName has a problem
return;
}
if (entityContainerMapping.ContainsAssociationSetMapping(associationSet))
{
//Can not add this set mapping since our storage dictionary won't allow
//duplicate maps
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_Duplicate_CdmAssociationSet_StorageMap_1, associationSetName,
StorageMappingErrorCode.DuplicateSetMapping, m_sourceLocation, navLineInfo, m_parsingErrors);
return;
}
//Create the AssociationSet Mapping which contains the mapping information for association set.
StorageAssociationSetMapping setMapping = new StorageAssociationSetMapping(associationSet, entityContainerMapping);
//Set the Start Line Information on Fragment
setMapping.StartLineNumber = navLineInfo.LineNumber;
setMapping.StartLinePosition = navLineInfo.LinePosition;
if (!nav.MoveToChild(XPathNodeType.Element))
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Emtpty_SetMap_1, associationSet.Name,
StorageMappingErrorCode.EmptySetMapping, m_sourceLocation, navLineInfo, m_parsingErrors);
return;
}
entityContainerMapping.AddAssociationSetMapping(setMapping);
//If there is a query view it has to be the first element
if (nav.LocalName == StorageMslConstructs.QueryViewElement)
{
if (!(String.IsNullOrEmpty(tableName)))
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_TableName_QueryView_1, associationSetName,
StorageMappingErrorCode.TableNameAttributeWithQueryView, m_sourceLocation, navLineInfo, m_parsingErrors);
return;
}
//Load the Query View into the set mapping,
//if you get an error, return immediately since
//you go on, you could be giving lot of dubious errors
if (!LoadQueryView(nav.Clone(), setMapping))
{
return;
}
//If there are no more elements just return
if (!nav.MoveToNext(XPathNodeType.Element))
{
return;
}
}
if ((nav.LocalName == StorageMslConstructs.EndPropertyMappingElement) ||
(nav.LocalName == StorageMslConstructs.ModificationFunctionMappingElement))
{
if ((String.IsNullOrEmpty(associationTypeName)))
{
AddToSchemaErrors(Strings.Mapping_InvalidContent_Association_Type_Empty_0,
StorageMappingErrorCode.InvalidAssociationType, m_sourceLocation, navLineInfo, m_parsingErrors);
return;
}
//Load the AssociationTypeMapping into memory.
LoadAssociationTypeMapping(nav.Clone(), setMapping, associationTypeName, tableName, storageEntityContainerType);
}
else if (nav.LocalName == StorageMslConstructs.ConditionElement)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_AssociationSet_Condition_1, associationSetName,
StorageMappingErrorCode.InvalidContent, m_sourceLocation, navLineInfo, m_parsingErrors);
return;
}
else
{
Debug.Assert(false, "XSD validation should ensure this");
}
}
///
/// The method loads a function import mapping element
///
///
///
///
private void LoadFunctionImportMapping(XPathNavigator nav, StorageEntityContainerMapping entityContainerMapping, EntityContainer storageEntityContainerType)
{
IXmlLineInfo lineInfo = (IXmlLineInfo)(nav.Clone());
// Get target (store) function
EdmFunction targetFunction;
if (!TryGetFunctionImportStoreFunction(nav, out targetFunction))
{
return;
}
// Get source (model) function
EdmFunction functionImport;
if (!TryGetFunctionImportModelFunction(nav, entityContainerMapping, out functionImport))
{
return;
}
// Validate parameters are compatible between the store and model functions
ValidateFunctionImportMappingParameters(nav, targetFunction, functionImport);
// Process type mapping information
List typeMappings = new List();
if (nav.MoveToChild(XPathNodeType.Element))
{
if (nav.LocalName == StorageMslConstructs.FunctionImportMappingResultMapping)
{
if (nav.MoveToChild(XPathNodeType.Element))
{
do
{
if (nav.LocalName == StorageMslConstructs.EntityTypeMappingElement)
{
FunctionImportEntityTypeMapping typeMapping;
if (TryLoadFunctionImportEntityTypeMapping(nav.Clone(), targetFunction, functionImport, out typeMapping))
{
typeMappings.Add(typeMapping);
}
}
}
while (nav.MoveToNext(XPathNodeType.Element));
}
}
}
// Add import mapping to list
FunctionImportMapping mapping = new FunctionImportMapping(targetFunction, functionImport, typeMappings, this.EdmItemCollection);
entityContainerMapping.AddFunctionImportMapping(functionImport, mapping);
// Verify that all types can be produced
KeyToListMap unreachableEntityTypes;
KeyToListMap unreachableIsTypeOfs;
mapping.GetUnreachableTypes(EdmItemCollection, out unreachableEntityTypes, out unreachableIsTypeOfs);
foreach (var unreachableEntityType in unreachableEntityTypes.KeyValuePairs)
{
string lines = StringUtil.ToCommaSeparatedString(unreachableEntityType.Value.Select(li => li.LineNumber));
AddToSchemaErrorWithMessage(System.Data.Entity.Strings.Mapping_FunctionImport_UnreachableType(unreachableEntityType.Key.FullName, lines),
StorageMappingErrorCode.MappingFunctionImportAmbiguousTypeConditions, m_sourceLocation, lineInfo, m_parsingErrors);
}
foreach (var unreachableIsTypeOf in unreachableIsTypeOfs.KeyValuePairs)
{
string lines = StringUtil.ToCommaSeparatedString(unreachableIsTypeOf.Value.Select(li => li.LineNumber));
string isTypeOfDescription = StorageMslConstructs.IsTypeOf + unreachableIsTypeOf.Key.FullName + StorageMslConstructs.IsTypeOfTerminal;
AddToSchemaErrorWithMessage(System.Data.Entity.Strings.Mapping_FunctionImport_UnreachableIsTypeOf(isTypeOfDescription, lines),
StorageMappingErrorCode.MappingFunctionImportAmbiguousTypeConditions, m_sourceLocation, lineInfo, m_parsingErrors);
}
// Verify that function imports returning abstract types include explicit mappings
EntityType returnEntityType;
if (MetadataHelper.TryGetFunctionImportReturnEntityType(functionImport, out returnEntityType) &&
returnEntityType.Abstract &&
mapping.NormalizedEntityTypeMappings.Count == 0)
{
AddToSchemaErrorWithMemberAndStructure(Strings.Mapping_InvalidContent_ImplicitMappingForAbstractReturnType_FunctionMapping_1, returnEntityType.FullName,
functionImport.FullName, StorageMappingErrorCode.MappingForAbstractEntityType, m_sourceLocation, lineInfo, m_parsingErrors);
}
}
private bool TryGetFunctionImportStoreFunction(XPathNavigator nav, out EdmFunction targetFunction)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
targetFunction = null;
// Get the function name
string functionName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.FunctionImportMappingFunctionNameAttribute);
// Try to find the function definition
ReadOnlyCollection functionOverloads = this.StoreItemCollection.GetFunctions(functionName);
if (functionOverloads.Count == 0)
{
AddToSchemaErrorWithMessage(System.Data.Entity.Strings.Mapping_FunctionImport_StoreFunctionDoesNotExist(functionName),
StorageMappingErrorCode.MappingFunctionImportStoreFunctionDoesNotExist,
m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return false;
}
else if (1 < functionOverloads.Count)
{
AddToSchemaErrorWithMessage(System.Data.Entity.Strings.Mapping_FunctionImport_StoreFunctionAmbiguous(functionName),
StorageMappingErrorCode.MappingFunctionImportStoreFunctionAmbiguous,
m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return false;
}
targetFunction = functionOverloads.Single();
// validate target function is supported (non-composable, etc.)
if (targetFunction.IsComposableAttribute)
{
AddToSchemaErrorWithMessage(System.Data.Entity.Strings.Mapping_FunctionImport_TargetFunctionMustBeComposable(targetFunction.FullName),
StorageMappingErrorCode.MappingFunctionImportTargetFunctionMustBeComposable,
m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return false;
}
return true;
}
private bool TryGetFunctionImportModelFunction(XPathNavigator nav, StorageEntityContainerMapping entityContainerMapping,
out EdmFunction functionImport)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
// Get the function import name
string functionImportName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.FunctionImportMappingFunctionImportNameAttribute);
// Try to find the function import
EntityContainer modelContainer = entityContainerMapping.EdmEntityContainer;
functionImport = null;
foreach (EdmFunction functionImportCandidate in modelContainer.FunctionImports)
{
if (functionImportCandidate.Name == functionImportName)
{
functionImport = functionImportCandidate;
break;
}
}
if (null == functionImport)
{
AddToSchemaErrorWithMessage(System.Data.Entity.Strings.Mapping_FunctionImport_FunctionImportDoesNotExist(functionImportName, entityContainerMapping.EdmEntityContainer.Name),
StorageMappingErrorCode.MappingFunctionImportFunctionImportDoesNotExist,
m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return false;
}
// check that no existing mapping exists for this function import
FunctionImportMapping targetFunctionCollision;
if (entityContainerMapping.TryGetFunctionImportMapping(functionImport, out targetFunctionCollision))
{
AddToSchemaErrorWithMessage(System.Data.Entity.Strings.Mapping_FunctionImport_FunctionImportMappedMultipleTimes(functionImportName),
StorageMappingErrorCode.MappingFunctionImportFunctionImportMappedMultipleTimes,
m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return false;
}
return true;
}
private void ValidateFunctionImportMappingParameters(XPathNavigator nav, EdmFunction targetFunction, EdmFunction functionImport)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
foreach (FunctionParameter targetParameter in targetFunction.Parameters)
{
// find corresponding import parameter
FunctionParameter importParameter;
if (!functionImport.Parameters.TryGetValue(targetParameter.Name, false, out importParameter))
{
AddToSchemaErrorWithMessage(System.Data.Entity.Strings.Mapping_FunctionImport_TargetParameterHasNoCorrespondingImportParameter(targetParameter.Name),
StorageMappingErrorCode.MappingFunctionImportTargetParameterHasNoCorrespondingImportParameter,
m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
}
else
{
// parameters must have the same direction (in|out)
if (targetParameter.Mode != importParameter.Mode)
{
AddToSchemaErrorWithMessage(System.Data.Entity.Strings.Mapping_FunctionImport_IncompatibleParameterMode(targetParameter.Name, targetParameter.Mode, importParameter.Mode),
StorageMappingErrorCode.MappingFunctionImportIncompatibleParameterMode,
m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
}
// there are no type facets declared for function parameter types;
// we simply verify the primitive type kind is equivalent
PrimitiveType importType = (PrimitiveType)importParameter.TypeUsage.EdmType;
PrimitiveType cspaceTargetType = (PrimitiveType)StoreItemCollection.StoreProviderManifest.GetEdmType(targetParameter.TypeUsage).EdmType;
if (cspaceTargetType == null)
{
AddToSchemaErrorWithMessage(System.Data.Entity.Strings.Mapping_ProviderReturnsNullType(targetParameter.Name),
StorageMappingErrorCode.MappingStoreProviderReturnsNullEdmType,
m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return;
}
if (cspaceTargetType.PrimitiveTypeKind != importType.PrimitiveTypeKind)
{
AddToSchemaErrorWithMessage(System.Data.Entity.Strings.Mapping_FunctionImport_IncompatibleParameterType(targetParameter.Name, cspaceTargetType.Name, importType.Name),
StorageMappingErrorCode.MappingFunctionImportIncompatibleParameterType,
m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
}
}
}
foreach (FunctionParameter importParameter in functionImport.Parameters)
{
// find corresponding target parameter
FunctionParameter targetParameter;
if (!targetFunction.Parameters.TryGetValue(importParameter.Name, false, out targetParameter))
{
AddToSchemaErrorWithMessage(System.Data.Entity.Strings.Mapping_FunctionImport_ImportParameterHasNoCorrespondingTargetParameter(importParameter.Name),
StorageMappingErrorCode.MappingFunctionImportImportParameterHasNoCorrespondingTargetParameter,
m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
}
}
}
private bool TryLoadFunctionImportEntityTypeMapping(XPathNavigator nav, EdmFunction targetFunction, EdmFunction functionImport,
out FunctionImportEntityTypeMapping typeMapping)
{
typeMapping = null;
IXmlLineInfo lineInfo = (IXmlLineInfo)(nav.Clone());
// cannot specify an entity type mapping for a function import that does not return members
// of an entity set
if (null == functionImport.EntitySet)
{
AddToSchemaErrors(System.Data.Entity.Strings.Mapping_FunctionImport_EntityTypeMappingForFunctionNotReturningEntitySet(
StorageMslConstructs.EntityTypeMappingElement, functionImport.FullName),
StorageMappingErrorCode.MappingFunctionImportEntityTypeMappingForFunctionNotReturningEntitySet,
m_sourceLocation, lineInfo, m_parsingErrors);
}
// process entity type
Set isOfTypeEntityTypes;
Set entityTypes;
{
string entityTypeName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.EntityTypeMappingTypeNameAttribute);
// verify the entity type is appropriate to the function import's entity type
EntityType rootEntityType;
if (!MetadataHelper.TryGetFunctionImportReturnEntityType(functionImport, out rootEntityType) ||
!TryParseEntityTypeAttribute(nav.Clone(), rootEntityType,
e => System.Data.Entity.Strings.Mapping_FunctionImport_InvalidContentEntityTypeForEntitySet(e.FullName, rootEntityType.FullName,
functionImport.EntitySet.Name, functionImport.FullName),
out isOfTypeEntityTypes,
out entityTypes))
{
return false;
}
}
// process all conditions
List conditions = new List();
if (nav.MoveToChild(XPathNodeType.Element))
{
do
{
if (nav.LocalName == StorageMslConstructs.ConditionElement)
{
LoadFunctionImportEntityTypeMappingCondition(nav, conditions);
}
}
while (nav.MoveToNext(XPathNodeType.Element));
}
// make sure a single condition is specified per column
HashSet columnsWithConditions = new HashSet();
foreach (var condition in conditions)
{
if (!columnsWithConditions.Add(condition.ColumnName))
{
AddToSchemaErrorWithMessage(
System.Data.Entity.Strings.Mapping_FunctionImport_MultipleConditionsOnSingleColumn(condition.ColumnName),
StorageMappingErrorCode.MappingFunctionMultipleTypeConditionsForOneColumn,
m_sourceLocation, lineInfo, m_parsingErrors);
return false;
}
}
typeMapping = new FunctionImportEntityTypeMapping(isOfTypeEntityTypes, entityTypes, conditions, lineInfo);
return true;
}
private void LoadFunctionImportEntityTypeMappingCondition(XPathNavigator nav, List conditions)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
string columnName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.ConditionColumnNameAttribute);
string value = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.ConditionValueAttribute);
string isNull = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.ConditionIsNullAttribute);
//Either Value or NotNull need to be specifid on the condition mapping but not both
if ((isNull != null) && (value != null))
{
AddToSchemaErrors(Strings.Mapping_InvalidContent_ConditionMapping_Both_Values_0,
StorageMappingErrorCode.ConditionError, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
}
else if ((isNull == null) && (value == null))
{
AddToSchemaErrors(Strings.Mapping_InvalidContent_ConditionMapping_Either_Values_0,
StorageMappingErrorCode.ConditionError, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
}
else
{
if (isNull != null)
{
bool isNullValue = Convert.ToBoolean(isNull, CultureInfo.InvariantCulture);
conditions.Add(new FunctionImportEntityTypeMappingConditionIsNull(columnName, isNullValue));
}
else
{
XPathNavigator columnValue = nav.Clone();
columnValue.MoveToAttribute(StorageMslConstructs.ConditionValueAttribute, string.Empty);
conditions.Add(new FunctionImportEntityTypeMappingConditionValue(columnName, columnValue));
}
}
}
///
/// The method loads the child nodes for the AssociationType Mapping node
/// into the internal datastructures.
///
///
///
///
///
///
private void LoadAssociationTypeMapping(XPathNavigator nav, StorageAssociationSetMapping associationSetMapping, string associationTypeName, string tableName, EntityContainer storageEntityContainerType)
{
IXmlLineInfo navLineInfo = (IXmlLineInfo)nav;
//Get the association type for association type name specified in MSL
//If no AssociationType with the given name exists, add a schema error and return
AssociationType associationType;
this.EdmItemCollection.TryGetItem(associationTypeName, out associationType);
if (associationType == null)
{
//There is no point in continuing loading if the AssociationType is null
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Association_Type_1, associationTypeName,
StorageMappingErrorCode.InvalidAssociationType, m_sourceLocation, navLineInfo, m_parsingErrors);
return;
}
//Verify that AssociationType specified should be the declared type of
//AssociationSet or a derived Type of it.
//Future Enhancement : Change the code to use EdmEquals
if ((!(associationSetMapping.Set.ElementType.Equals(associationType))))
{
AddToSchemaErrorWithMessage(System.Data.Entity.Strings.Mapping_Invalid_Association_Type_For_Association_Set_3(associationTypeName,
associationSetMapping.Set.ElementType.FullName, associationSetMapping.Set.Name),
StorageMappingErrorCode.DuplicateTypeMapping, m_sourceLocation, navLineInfo, m_parsingErrors);
return;
}
//Create an AssociationTypeMapping to hold the information for AssociationType mapping.
StorageAssociationTypeMapping associationTypeMapping = new StorageAssociationTypeMapping(associationType, associationSetMapping);
associationSetMapping.AddTypeMapping(associationTypeMapping);
//If the table name was not specified on the AssociationSetMapping element
//Then there should have been a query view. Otherwise throw.
if (String.IsNullOrEmpty(tableName) && (associationSetMapping.QueryView == null))
{
AddToSchemaErrors(Strings.Mapping_InvalidContent_Table_Expected_0, StorageMappingErrorCode.InvalidTable,
m_sourceLocation, navLineInfo, m_parsingErrors);
}
else
{
StorageMappingFragment fragment = LoadAssociationMappingFragment(nav.Clone(), associationSetMapping, associationTypeMapping, tableName, storageEntityContainerType);
if (fragment != null)
{
//Fragment can be null because of validation errors
associationTypeMapping.AddFragment(fragment);
}
}
}
///
/// Loads function mappings for the entity type.
///
///
///
///
private void LoadAssociationTypeFunctionMapping(XPathNavigator nav, StorageAssociationSetMapping associationSetMapping,
StorageAssociationTypeMapping associationTypeMapping)
{
// create function loader
FunctionMappingLoader functionLoader = new FunctionMappingLoader(this, associationSetMapping.Set);
// Load all function definitions (for insert, delete and update)
StorageFunctionMapping deleteFunctionMapping = null;
StorageFunctionMapping insertFunctionMapping = null;
if (nav.MoveToChild(XPathNodeType.Element))
{
do
{
switch (nav.LocalName)
{
case StorageMslConstructs.DeleteFunctionElement:
deleteFunctionMapping = functionLoader.LoadAssociationSetFunctionMapping(nav.Clone(), false);
break;
case StorageMslConstructs.InsertFunctionElement:
insertFunctionMapping = functionLoader.LoadAssociationSetFunctionMapping(nav.Clone(), true);
break;
}
} while (nav.MoveToNext(XPathNodeType.Element));
}
//We might be returned null because of schema errors
if (null == deleteFunctionMapping || null == insertFunctionMapping)
{
return;
}
// register function mapping information
associationSetMapping.FunctionMapping = new StorageAssociationSetFunctionMapping(
(AssociationSet)associationSetMapping.Set, deleteFunctionMapping, insertFunctionMapping);
}
///
/// The method loads the child nodes for the TableMappingFragment under the EntityType node
/// into the internal datastructures.
///
///
///
///
///
///
private StorageMappingFragment LoadMappingFragment(XPathNavigator nav, StorageEntityTypeMapping typeMapping, string tableName
, EntityContainer storageEntityContainerType)
{
IXmlLineInfo navLineInfo = (IXmlLineInfo)nav;
//First make sure that there was no QueryView specified for this Set
if (typeMapping.SetMapping.QueryView != null)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_QueryView_PropertyMaps_1, typeMapping.SetMapping.Set.Name,
StorageMappingErrorCode.PropertyMapsWithQueryView, m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
//Get the table type that represents this table
EntitySet tableMember;
storageEntityContainerType.TryGetEntitySetByName(tableName, false /*ignoreCase*/, out tableMember);
if (tableMember == null)
{
//There is no point in continuing loading if the Table on S side can not be found
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Table_1, tableName,
StorageMappingErrorCode.InvalidTable, m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
EntityType tableType = tableMember.ElementType;
//Create a table mapping fragment to hold the mapping information for a TableMappingFragment node
StorageMappingFragment fragment = new StorageMappingFragment(tableMember, typeMapping);
//Set the Start Line Information on Fragment
fragment.StartLineNumber = navLineInfo.LineNumber;
fragment.StartLinePosition = navLineInfo.LinePosition;
//Go through the property mappings for this TableMappingFragment and load them in memory.
if (nav.MoveToChild(XPathNodeType.Element))
{
do
{
//need to get the type that this member exists in
EdmType containerType = null;
string propertyName = StorageMappingItemLoader.GetAttributeValue(nav.Clone(), StorageMslConstructs.ComplexPropertyNameAttribute);
//PropertyName could be null for Condition Maps
if (propertyName != null)
{
containerType = typeMapping.GetContainerType(propertyName);
}
switch (nav.LocalName)
{
case StorageMslConstructs.ScalarPropertyElement:
StorageScalarPropertyMapping scalarMap = LoadScalarPropertyMapping(nav.Clone(),
containerType, tableType);
if (scalarMap != null)
{
//scalarMap can be null in invalid cases
fragment.AddProperty(scalarMap);
}
break;
case StorageMslConstructs.ComplexPropertyElement:
StorageComplexPropertyMapping complexMap =
LoadComplexPropertyMapping(nav.Clone(), containerType, tableType);
//Complex Map can be null in case of invalid MSL files.
if (complexMap != null)
{
fragment.AddProperty(complexMap);
}
break;
case StorageMslConstructs.ConditionElement:
StorageConditionPropertyMapping conditionMap =
LoadConditionPropertyMapping(nav.Clone(), containerType, tableType);
//conditionMap can be null in cases of invalid Map
if (conditionMap != null)
{
if (!fragment.AddConditionProperty(conditionMap))
{
EdmProperty conditionMember = (conditionMap.EdmProperty != null) ? conditionMap.EdmProperty : conditionMap.ColumnProperty;
AddToSchemaErrorsWithMemberInfo(
Strings.Mapping_InvalidContent_Duplicate_Condition_Member_1,
conditionMember.Name,
StorageMappingErrorCode.ConditionError,
m_sourceLocation,
navLineInfo,
m_parsingErrors);
return null;
}
}
break;
default:
AddToSchemaErrors(Strings.Mapping_InvalidContent_General_0,
StorageMappingErrorCode.InvalidContent, m_sourceLocation, navLineInfo, m_parsingErrors);
break;
}
} while (nav.MoveToNext(XPathNodeType.Element));
}
//Set the end Line Information on Fragment
fragment.EndLineNumber = navLineInfo.LineNumber;
fragment.EndLinePosition = navLineInfo.LinePosition;
nav.MoveToChild(XPathNodeType.Element);
return fragment;
}
///
/// The method loads the child nodes for the TableMappingFragment under the AssociationType node
/// into the internal datastructures.
///
///
///
///
///
///
///
private StorageMappingFragment LoadAssociationMappingFragment(XPathNavigator nav, StorageAssociationSetMapping setMapping, StorageAssociationTypeMapping typeMapping, string tableName, EntityContainer storageEntityContainerType)
{
IXmlLineInfo navLineInfo = (IXmlLineInfo)nav;
StorageMappingFragment fragment = null;
EntityType tableType = null;
//If there is a query view, Dont create a mapping fragment since there should n't be one
if (setMapping.QueryView == null)
{
//Get the table type that represents this table
EntitySet tableMember;
storageEntityContainerType.TryGetEntitySetByName(tableName, false /*ignoreCase*/, out tableMember);
if (tableMember == null)
{
//There is no point in continuing loading if the Table is null
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Table_1, tableName,
StorageMappingErrorCode.InvalidTable, m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
tableType = tableMember.ElementType;
//Create a Mapping fragment and load all the End node under it
fragment = new StorageMappingFragment(tableMember, typeMapping);
//Set the Start Line Information on Fragment, For AssociationSet there are
//no fragments, so the start Line Info is same as that of Set
fragment.StartLineNumber = setMapping.StartLineNumber;
fragment.StartLinePosition = setMapping.StartLinePosition;
}
do
{
//need to get the type that this member exists in
switch (nav.LocalName)
{
case StorageMslConstructs.EndPropertyMappingElement:
//Make sure that there was no QueryView specified for this Set
if (setMapping.QueryView != null)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_QueryView_PropertyMaps_1, setMapping.Set.Name,
StorageMappingErrorCode.PropertyMapsWithQueryView, m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
string endName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.EndPropertyMappingNameAttribute);
EdmMember endMember = null;
typeMapping.AssociationType.Members.TryGetValue(endName, false, out endMember);
AssociationEndMember end = endMember as AssociationEndMember;
if (end == null)
{
//Don't try to load the end property map if the end property itself is null
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_End_1, endName,
StorageMappingErrorCode.InvalidEdmMember, m_sourceLocation, navLineInfo, m_parsingErrors);
continue;
}
fragment.AddProperty((LoadEndPropertyMapping(nav.Clone(), end, tableType)));
break;
case StorageMslConstructs.ConditionElement:
//Make sure that there was no QueryView specified for this Set
if (setMapping.QueryView != null)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_QueryView_PropertyMaps_1, setMapping.Set.Name,
StorageMappingErrorCode.PropertyMapsWithQueryView, m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
//Need to add validation for conditions in Association mapping fragment.
StorageConditionPropertyMapping conditionMap =
LoadConditionPropertyMapping(nav.Clone(), null /*containerType*/, tableType);
//conditionMap can be null in cases of invalid Map
if (conditionMap != null)
{
if (!fragment.AddConditionProperty(conditionMap))
{
EdmProperty conditionMember = (conditionMap.EdmProperty != null) ? conditionMap.EdmProperty : conditionMap.ColumnProperty;
AddToSchemaErrorsWithMemberInfo(
Strings.Mapping_InvalidContent_Duplicate_Condition_Member_1,
conditionMember.Name,
StorageMappingErrorCode.ConditionError,
m_sourceLocation,
navLineInfo,
m_parsingErrors);
return null;
}
}
break;
case StorageMslConstructs.ModificationFunctionMappingElement:
LoadAssociationTypeFunctionMapping(nav.Clone(), setMapping, typeMapping);
break;
default:
AddToSchemaErrors(Strings.Mapping_InvalidContent_General_0,
StorageMappingErrorCode.InvalidContent, m_sourceLocation, navLineInfo, m_parsingErrors);
break;
}
} while (nav.MoveToNext(XPathNodeType.Element));
if (setMapping.QueryView == null)
{
Debug.Assert(fragment != null, "There should be Fragment when there is no query view");
//Set the end Line Information on Fragment
fragment.EndLineNumber = navLineInfo.LineNumber;
fragment.EndLinePosition = navLineInfo.LinePosition;
}
return fragment;
}
///
/// The method loads the ScalarProperty mapping
/// into the internal datastructures.
///
///
///
///
///
private StorageScalarPropertyMapping LoadScalarPropertyMapping(XPathNavigator nav, EdmType containerType, EntityType tableType)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
//Get the property name from MSL.
string propertyName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.ScalarPropertyNameAttribute);
EdmProperty member = null;
if (!String.IsNullOrEmpty(propertyName))
{
//If the container type is a collection type, there wouldn't be a member to represent this scalar property
if (containerType == null || !(Helper.IsCollectionType(containerType)))
{
//If container type is null that means we have not found the member in any of the IsOfTypes.
if (containerType != null)
{
if (Helper.IsRefType(containerType))
{
RefType refType = (RefType)containerType;
((EntityType)refType.ElementType).Properties.TryGetValue(propertyName, false /*ignoreCase*/, out member);
}
else
{
EdmMember tempMember;
(containerType as StructuralType).Members.TryGetValue(propertyName, false, out tempMember);
member = tempMember as EdmProperty;
}
}
if (member == null)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Cdm_Member_1, propertyName,
StorageMappingErrorCode.InvalidEdmMember, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
}
}
}
//Get the property from Storeside
string columnName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.ScalarPropertyColumnNameAttribute);
Debug.Assert(columnName != null, "XSD validation should have caught this");
EdmProperty columnMember;
tableType.Properties.TryGetValue(columnName, false, out columnMember);
if (columnMember == null)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Column_1, columnName,
StorageMappingErrorCode.InvalidStorageMember, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
}
//Don't create scalar property map if the property or column metadata is null
if ((member == null) || (columnMember == null))
{
return null;
}
if (!Helper.IsPrimitiveType(member.TypeUsage.EdmType))
{
EdmSchemaError error = new EdmSchemaError(
System.Data.Entity.Strings.Mapping_Invalid_CSide_ScalarProperty_1(
member.Name),
(int)StorageMappingErrorCode.InvalidTypeInScalarProperty,
EdmSchemaErrorSeverity.Error,
m_sourceLocation,
xmlLineInfoNav.LineNumber,
xmlLineInfoNav.LinePosition);
m_parsingErrors.Add(error);
return null;
}
ValidateAndUpdateScalarMemberMapping(member, columnMember, xmlLineInfoNav);
StorageScalarPropertyMapping scalarPropertyMapping = new StorageScalarPropertyMapping(member, columnMember);
return scalarPropertyMapping;
}
///
/// The method loads the ComplexProperty mapping
/// into the internal datastructures.
///
///
///
///
///
private StorageComplexPropertyMapping LoadComplexPropertyMapping(XPathNavigator nav, EdmType containerType, EntityType tableType)
{
IXmlLineInfo navLineInfo = (IXmlLineInfo)nav;
CollectionType collectionType = containerType as CollectionType;
//Get the property name from MSL
string propertyName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.ComplexPropertyNameAttribute);
//Get the member metadata from the contianer type passed in.
//But if the continer type is collection type, there would n't be any member to represent the member.
EdmProperty member = null;
EdmType memberType = null;
//If member specified the type name, it takes precedence
string memberTypeName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.ComplexTypeMappingTypeNameAttribute);
StructuralType containerStructuralType = containerType as StructuralType;
if (String.IsNullOrEmpty(memberTypeName))
{
if (collectionType == null)
{
EdmMember tempMember;
containerStructuralType.Members.TryGetValue(propertyName, false /*ignoreCase*/, out tempMember);
member = tempMember as EdmProperty;
if (member == null)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Cdm_Member_1, propertyName,
StorageMappingErrorCode.InvalidEdmMember, m_sourceLocation, navLineInfo, m_parsingErrors);
}
memberType = member.TypeUsage.EdmType;
}
else
{
memberType = collectionType.TypeUsage.EdmType;
}
}
else
{
//If container type is null that means we have not found the member in any of the IsOfTypes.
if (containerType != null)
{
EdmMember tempMember;
containerStructuralType.Members.TryGetValue(propertyName, false /*ignoreCase*/, out tempMember);
member = tempMember as EdmProperty;
}
if (member == null)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Cdm_Member_1, propertyName,
StorageMappingErrorCode.InvalidEdmMember, m_sourceLocation, navLineInfo, m_parsingErrors);
}
this.EdmItemCollection.TryGetItem(memberTypeName, out memberType);
memberType = memberType as ComplexType;
// If member type is null, that means the type wasn't found in the workspace
if (memberType == null)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Complex_Type_1, memberTypeName,
StorageMappingErrorCode.InvalidComplexType, m_sourceLocation, navLineInfo, m_parsingErrors);
}
}
StorageComplexPropertyMapping complexPropertyMapping = new StorageComplexPropertyMapping(member);
XPathNavigator cloneNav = nav.Clone();
bool hasComplexTypeMappingElements = false;
if (cloneNav.MoveToChild(XPathNodeType.Element))
{
if (cloneNav.LocalName == StorageMslConstructs.ComplexTypeMappingElement)
{
hasComplexTypeMappingElements = true;
}
}
//There is no point in continuing if the complex member or complex member type is null
if ((member == null) || (memberType == null))
{
return null;
}
if (hasComplexTypeMappingElements)
{
nav.MoveToChild(XPathNodeType.Element);
do
{
complexPropertyMapping.AddTypeMapping(LoadComplexTypeMapping(nav.Clone(), null, tableType));
} while (nav.MoveToNext(XPathNodeType.Element));
}
else
{
complexPropertyMapping.AddTypeMapping(LoadComplexTypeMapping(nav.Clone(), memberType, tableType));
}
return complexPropertyMapping;
}
///
///
///
///
///
private StorageComplexTypeMapping LoadComplexTypeMapping(XPathNavigator nav, EdmType type, EntityType tableType)
{
//Get the IsPartial attribute from MSL
bool isPartial = false;
string partialAttribute = StorageMappingItemLoader.GetAttributeValue(nav.Clone(), StorageMslConstructs.ComplexPropertyIsPartialAttribute);
if (!String.IsNullOrEmpty(partialAttribute))
{
//XSD validation should have guarenteed that the attribute value can only be true or false
Debug.Assert(partialAttribute == "true" || partialAttribute == "false");
isPartial = Convert.ToBoolean(partialAttribute, System.Globalization.CultureInfo.InvariantCulture);
}
//Create an ComplexTypeMapping to hold the information for Type mapping.
StorageComplexTypeMapping typeMapping = new StorageComplexTypeMapping(isPartial);
if (type != null)
{
typeMapping.AddType(type as ComplexType);
}
else
{
Debug.Assert(nav.LocalName == StorageMslConstructs.ComplexTypeMappingElement);
string typeName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.ComplexTypeMappingTypeNameAttribute);
int index = typeName.IndexOf(StorageMslConstructs.TypeNameSperator);
string currentTypeName = null;
do
{
if (index != -1)
{
currentTypeName = typeName.Substring(0, index);
typeName = typeName.Substring(index + 1, (typeName.Length - (index + 1)));
}
else
{
currentTypeName = typeName;
typeName = string.Empty;
}
int isTypeOfIndex = currentTypeName.IndexOf(StorageMslConstructs.IsTypeOf, StringComparison.Ordinal);
if (isTypeOfIndex == 0)
{
currentTypeName = currentTypeName.Substring(StorageMslConstructs.IsTypeOf.Length, (currentTypeName.Length - (StorageMslConstructs.IsTypeOf.Length + 1)));
currentTypeName = GetAliasResolvedValue(currentTypeName);
}
else
{
currentTypeName = GetAliasResolvedValue(currentTypeName);
}
ComplexType complexType;
this.EdmItemCollection.TryGetItem(currentTypeName, out complexType);
if (complexType == null)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Complex_Type_1, currentTypeName,
StorageMappingErrorCode.InvalidComplexType, m_sourceLocation, (IXmlLineInfo)nav, m_parsingErrors);
index = typeName.IndexOf(StorageMslConstructs.TypeNameSperator);
continue;
}
if (isTypeOfIndex == 0)
{
typeMapping.AddIsOfType(complexType);
}
else
{
typeMapping.AddType(complexType);
}
index = typeName.IndexOf(StorageMslConstructs.TypeNameSperator);
} while (typeName.Length != 0);
}
//Now load the children of ComplexTypeMapping
if (nav.MoveToChild(XPathNodeType.Element))
{
do
{
EdmType containerType = typeMapping.GetOwnerType(StorageMappingItemLoader.GetAttributeValue(nav.Clone(), StorageMslConstructs.ComplexPropertyNameAttribute));
switch (nav.LocalName)
{
case StorageMslConstructs.ScalarPropertyElement:
StorageScalarPropertyMapping scalarMap =
LoadScalarPropertyMapping(nav.Clone(), containerType, tableType);
//ScalarMap can be null in case of invalid MSL files
if (scalarMap != null)
{
typeMapping.AddProperty(scalarMap);
}
break;
case StorageMslConstructs.ComplexPropertyElement:
StorageComplexPropertyMapping complexMap =
LoadComplexPropertyMapping(nav.Clone(), containerType, tableType);
//complexMap can be null in case of invalid maps
if (complexMap != null)
{
typeMapping.AddProperty(complexMap);
}
break;
case StorageMslConstructs.ConditionElement:
StorageConditionPropertyMapping conditionMap =
LoadConditionPropertyMapping(nav.Clone(), containerType, tableType);
if (conditionMap != null)
{
typeMapping.AddConditionProperty(conditionMap);
}
break;
default:
throw System.Data.Entity.Error.NotSupported();
}
} while (nav.MoveToNext(XPathNodeType.Element));
}
return typeMapping;
}
///
/// The method loads the EndProperty mapping
/// into the internal datastructures.
///
///
///
///
///
private StorageEndPropertyMapping LoadEndPropertyMapping(XPathNavigator nav, AssociationEndMember end, EntityType tableType)
{
//FutureEnhancement : Change End Property Mapping to not derive from
// StoragePropertyMapping
StorageEndPropertyMapping endMapping = new StorageEndPropertyMapping(null);
endMapping.EndMember = end;
nav.MoveToChild(XPathNodeType.Element);
do
{
switch (nav.LocalName)
{
case StorageMslConstructs.ScalarPropertyElement:
RefType endRef = end.TypeUsage.EdmType as RefType;
Debug.Assert(endRef != null);
EntityTypeBase containerType = endRef.ElementType;
StorageScalarPropertyMapping scalarMap =
LoadScalarPropertyMapping(nav.Clone(), containerType, tableType);
//Scalar Property Mapping can be null
//in case of invalid MSL files.
if (scalarMap != null)
{
endMapping.AddProperty(scalarMap);
}
break;
default:
Debug.Fail("XSD validation should have ensured that End EdmProperty Maps only have Schalar properties");
break;
}
} while (nav.MoveToNext(XPathNodeType.Element));
return endMapping;
}
///
/// The method loads the ConditionProperty mapping
/// into the internal datastructures.
///
///
///
///
///
private StorageConditionPropertyMapping LoadConditionPropertyMapping(XPathNavigator nav, EdmType containerType, EntityType tableType)
{
//Get the CDM side property name.
string propertyName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.ConditionNameAttribute);
//Get the Store side property name from Storeside
string columnName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.ConditionColumnNameAttribute);
IXmlLineInfo navLineInfo = (IXmlLineInfo)nav;
//Either the property name or column name can be specified but both can not be.
if ((propertyName != null) && (columnName != null))
{
AddToSchemaErrors(Strings.Mapping_InvalidContent_ConditionMapping_Both_Members_0,
StorageMappingErrorCode.ConditionError, m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
if ((propertyName == null) && (columnName == null))
{
AddToSchemaErrors(Strings.Mapping_InvalidContent_ConditionMapping_Either_Members_0,
StorageMappingErrorCode.ConditionError, m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
EdmProperty member = null;
//Get the CDM EdmMember reprsented by the name specified.
if (propertyName != null)
{
EdmMember tempMember;
//If container type is null that means we have not found the member in any of the IsOfTypes.
if (containerType != null)
{
((StructuralType)containerType).Members.TryGetValue(propertyName, false /*ignoreCase*/, out tempMember);
member = tempMember as EdmProperty;
}
}
//Get the column EdmMember represented by the column name specified
EdmProperty columnMember = null;
if (columnName != null)
{
tableType.Properties.TryGetValue(columnName, false, out columnMember);
}
//Get the member for which the condition is being specified
EdmProperty conditionMember = (columnMember != null) ? columnMember : member;
if (conditionMember == null)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_ConditionMapping_InvalidMember_1, ((columnName != null) ? columnName : propertyName),
StorageMappingErrorCode.ConditionError, m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
Nullable isNullValue = null;
object value = null;
//Get the attribute value for IsNull attribute
string isNullAttribute = StorageMappingItemLoader.GetAttributeValue(nav.Clone(), StorageMslConstructs.ConditionIsNullAttribute);
//Get strongly Typed value if the condition was specified for a specific condition
EdmType edmType = conditionMember.TypeUsage.EdmType;
if (Helper.IsPrimitiveType(edmType))
{
//Decide if the member is of a type that we would allow a condition on.
//First convert the type to C space, if this is a condition in s space( before checking this).
TypeUsage cspaceTypeUsage;
if (conditionMember.DeclaringType.GetDataSpace() == DataSpace.SSpace)
{
cspaceTypeUsage = StoreItemCollection.StoreProviderManifest.GetEdmType(conditionMember.TypeUsage);
if (cspaceTypeUsage == null)
{
AddToSchemaErrorWithMessage(System.Data.Entity.Strings.Mapping_ProviderReturnsNullType(conditionMember.Name),
StorageMappingErrorCode.MappingStoreProviderReturnsNullEdmType,
m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
}
else
{
cspaceTypeUsage = conditionMember.TypeUsage;
}
PrimitiveType memberType = ((PrimitiveType)cspaceTypeUsage.EdmType);
Type clrMemberType = memberType.ClrEquivalentType;
PrimitiveTypeKind primitiveTypeKind = memberType.PrimitiveTypeKind;
//Only a subset of primitive types can be used in Conditions that are specified over values.
//IsNull conditions can be specified on any primitive types
if ((isNullAttribute == null) && !IsTypeSupportedForCondition(primitiveTypeKind))
{
AddToSchemaErrorWithMemberAndStructure(Strings.Mapping_InvalidContent_ConditionMapping_InvalidPrimitiveTypeKind_2,
conditionMember.Name, memberType.FullName, StorageMappingErrorCode.ConditionError,
m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
Debug.Assert(clrMemberType != null, "Scalar Types should have associated clr type");
//If the value is not compatible with the type, just add an error and return
if(!StorageMappingItemLoader.TryGetTypedAttributeValue(nav.Clone(), StorageMslConstructs.ConditionValueAttribute, clrMemberType, m_sourceLocation, m_parsingErrors, out value))
{
return null;
}
}
else if (Helper.IsEnumType(edmType))
{
// Enumeration type - get the actual value
value = StorageMappingItemLoader.GetEnumAttributeValue(nav.Clone(), StorageMslConstructs.ConditionValueAttribute, (EnumType)edmType, m_sourceLocation, m_parsingErrors);
}
else
{
// Since NullableComplexTypes are not being supported,
// we don't allow conditions on complex types
AddToSchemaErrors(Strings.Mapping_InvalidContent_ConditionMapping_NonScalar_0,
StorageMappingErrorCode.ConditionError, m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
//Either Value or NotNull need to be specifid on the condition mapping but not both
if ((isNullAttribute != null) && (value != null))
{
AddToSchemaErrors(Strings.Mapping_InvalidContent_ConditionMapping_Both_Values_0,
StorageMappingErrorCode.ConditionError, m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
if ((isNullAttribute == null) && (value == null))
{
AddToSchemaErrors(Strings.Mapping_InvalidContent_ConditionMapping_Either_Values_0,
StorageMappingErrorCode.ConditionError, m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
if (isNullAttribute != null)
{
//XSD validation should have guarenteed that the attribute value can only be true or false
Debug.Assert(isNullAttribute == "true" || isNullAttribute == "false");
isNullValue = Convert.ToBoolean(isNullAttribute, System.Globalization.CultureInfo.InvariantCulture);
}
if (columnMember != null && (columnMember.IsStoreGeneratedComputed || columnMember.IsStoreGeneratedIdentity))
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_ConditionMapping_Computed, columnMember.Name,
StorageMappingErrorCode.ConditionError, m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
StorageConditionPropertyMapping conditionPropertyMapping = new StorageConditionPropertyMapping(member, columnMember, value, isNullValue);
return conditionPropertyMapping;
}
internal static bool IsTypeSupportedForCondition(PrimitiveTypeKind primitiveTypeKind)
{
switch (primitiveTypeKind)
{
case PrimitiveTypeKind.Boolean:
case PrimitiveTypeKind.Byte:
case PrimitiveTypeKind.Int16:
case PrimitiveTypeKind.Int32:
case PrimitiveTypeKind.Int64:
case PrimitiveTypeKind.String:
case PrimitiveTypeKind.SByte:
return true;
case PrimitiveTypeKind.Binary:
case PrimitiveTypeKind.DateTime:
case PrimitiveTypeKind.Time:
case PrimitiveTypeKind.DateTimeOffset:
case PrimitiveTypeKind.Double:
case PrimitiveTypeKind.Guid:
case PrimitiveTypeKind.Single:
case PrimitiveTypeKind.Decimal:
return false;
default:
Debug.Fail("New primitive type kind added?");
return false;
}
}
private static XmlSchemaSet GetOrCreateSchemaSet()
{
if (s_mappingXmlSchema == null)
{
//Get the xsd stream for CS MSL Xsd.
Stream xsdStream = typeof(StorageMappingItemLoader).Assembly.GetManifestResourceStream(StorageMslConstructs.ResourceXsdName);
using (XmlReader xsdReader = XmlReader.Create(xsdStream))
{
XmlSchema xmlSchema = XmlSchema.Read(xsdReader, null);
XmlSchemaSet set = new XmlSchemaSet();
set.Add(xmlSchema);
System.Threading.Interlocked.CompareExchange(ref s_mappingXmlSchema, set, null);
}
}
return s_mappingXmlSchema;
}
///
/// Throws a new MappingException giving out the line number and
/// File Name where the error in Mapping specification is present.
///
///
///
///
///
/// Error Collection where the parsing errors are collected
internal static void AddToSchemaErrors(string message, StorageMappingErrorCode errorCode, string location, IXmlLineInfo lineInfo, IList parsingErrors)
{
EdmSchemaError error = new EdmSchemaError(message, (int)errorCode, EdmSchemaErrorSeverity.Error, location, lineInfo.LineNumber, lineInfo.LinePosition);
parsingErrors.Add(error);
}
internal static void AddToSchemaErrorsWithMemberInfo(Func