|
//---------------------------------------------------------------------
// <copyright file="StorageMappingItemLoader.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//
// @owner Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Data.Common.Utils;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Xml.XPath;
using System.Data.Entity;
namespace System.Data.Mapping
{
using Triple = Pair<EntitySetBase, Pair<EntityTypeBase, bool>>;
/// <summary>
/// The class loads an MSL file into memory and exposes CSMappingMetadata interfaces.
/// The primary consumers of the interfaces are view genration and tools.
/// </summary>
/// <example>
/// 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.
/// </example>
/// <remarks>
/// 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
/// </remarks>
internal class StorageMappingItemLoader
{
#region Constructors
/// <summary>
/// Public constructor.
/// For Beta2 we wont support delay loading Mapping information and we would also support
/// only one mapping file for workspace.
/// </summary>
/// <param name="edmCollection"></param>
/// <param name="storeItemCollection"></param>
/// <param name="fileName"></param>
/// <param name="scalarMemberMappings">Dictionary to keep the list of all scalar member mappings</param>
internal StorageMappingItemLoader(XmlReader reader, StorageMappingItemCollection storageMappingItemCollection, string fileName, Dictionary<EdmMember, KeyValuePair<TypeUsage, TypeUsage>> scalarMemberMappings)
{
Debug.Assert(storageMappingItemCollection != null);
Debug.Assert(scalarMemberMappings != null);
this.m_storageMappingItemCollection = storageMappingItemCollection;
this.m_alias = new Dictionary<string, string>(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<EdmSchemaError>();
this.m_scalarMemberMappings = scalarMemberMappings;
m_containerMapping = LoadMappingItems(reader);
if (m_currentNamespaceUri != null)
{
if (m_currentNamespaceUri == StorageMslConstructs.NamespaceUriV1)
{
m_version = StorageMslConstructs.MappingVersionV1;
}
else if (m_currentNamespaceUri == StorageMslConstructs.NamespaceUriV2)
{
m_version = StorageMslConstructs.MappingVersionV2;
}
else
{
Debug.Assert(m_currentNamespaceUri == StorageMslConstructs.NamespaceUriV3, "Did you add a new Namespace?");
m_version = StorageMslConstructs.MappingVersionV3;
}
}
}
#endregion
#region Fields
private Dictionary<string, string> 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<EdmSchemaError> m_parsingErrors;
private Dictionary<EdmMember, KeyValuePair<TypeUsage, TypeUsage>> 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
private string m_currentNamespaceUri;
private StorageEntityContainerMapping m_containerMapping;
private double m_version;
// cached xsd schema
private static XmlSchemaSet s_mappingXmlSchema;
#endregion
#region Properties
internal double MappingVersion
{
get { return m_version; }
}
internal IList<EdmSchemaError> ParsingErrors
{
get { return m_parsingErrors; }
}
internal bool HasQueryViews
{
get { return m_hasQueryViews; }
}
internal StorageEntityContainerMapping ContainerMapping
{
get { return m_containerMapping; }
}
private EdmItemCollection EdmItemCollection
{
get { return m_storageMappingItemCollection.EdmItemCollection; }
}
private StoreItemCollection StoreItemCollection
{
get { return m_storageMappingItemCollection.StoreItemCollection; }
}
#endregion
#region Methods
/// <summary>
/// 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.
/// </summary>
/// <returns></returns>
private 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.Clone());
}
catch (XmlException xmlException)
{
// There must have been a xml parsing exception. Add the exception information to the error list.
EdmSchemaError error = new EdmSchemaError(Strings.Mapping_InvalidMappingSchema_Parsing(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;
}
private StorageEntityContainerMapping LoadMappingItems(XPathNavigator nav)
{
// XSD validation is not validating missing Root element.
if (!MoveToRootElement(nav) || (nav.NodeType != XPathNodeType.Element))
{
StorageMappingItemLoader.AddToSchemaErrors(
Strings.Mapping_Invalid_CSRootElementMissing(
StorageMslConstructs.NamespaceUriV1,
StorageMslConstructs.NamespaceUriV2,
StorageMslConstructs.NamespaceUriV3),
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;
}
private bool MoveToRootElement(XPathNavigator nav)
{
if (nav.MoveToChild(StorageMslConstructs.MappingElement, StorageMslConstructs.NamespaceUriV3))
{
// found v3 schema
m_currentNamespaceUri = StorageMslConstructs.NamespaceUriV3;
return true;
}
else if (nav.MoveToChild(StorageMslConstructs.MappingElement, StorageMslConstructs.NamespaceUriV2))
{
// found v2 schema
m_currentNamespaceUri = StorageMslConstructs.NamespaceUriV2;
return true;
}
else if (nav.MoveToChild(StorageMslConstructs.MappingElement, StorageMslConstructs.NamespaceUriV1))
{
m_currentNamespaceUri = StorageMslConstructs.NamespaceUriV1;
return true;
}
//the xml namespace corresponds to neither v1 namespace nor v2 namespace
return false;
}
/// <summary>
/// The method loads the child nodes for the root Mapping node
/// into the internal datastructures.
/// </summary>
private StorageEntityContainerMapping LoadMappingChildNodes(XPathNavigator 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, m_currentNamespaceUri))
{
// 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, m_currentNamespaceUri));
// 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);
}
// Load entity container mapping if any.
var containerMapping = hasContainerMapping ? LoadEntityContainerMapping(nav.Clone()) : null;
return containerMapping;
}
/// <summary>
/// The method loads and returns the EntityContainer Mapping node.
/// </summary>
private StorageEntityContainerMapping LoadEntityContainerMapping(XPathNavigator nav)
{
IXmlLineInfo navLineInfo = (IXmlLineInfo)nav;
// The element name can only be EntityContainerMapping element name since XSD validation should have guarneteed this.
Debug.Assert(nav.LocalName == StorageMslConstructs.EntityContainerMappingElement);
string entityContainerName = GetAttributeValue(nav.Clone(), StorageMslConstructs.CdmEntityContainerAttribute);
string storageEntityContainerName = GetAttributeValue(nav.Clone(), StorageMslConstructs.StorageEntityContainerAttribute);
bool generateUpdateViews = GetBoolAttributeValue(nav.Clone(), StorageMslConstructs.GenerateUpdateViews, true /* default is true */);
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<StorageEntityContainerMapping>(
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(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, 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,
entityContainerName, StorageMappingErrorCode.InvalidEntityContainer, m_sourceLocation,
navLineInfo, m_parsingErrors);
}
this.StoreItemCollection.TryGetEntityContainer(storageEntityContainerName, out storageEntityContainerType);
if (storageEntityContainerType == null)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_StorageEntityContainer, 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, generateUpdateViews /* make validate same as generateUpdateView*/, generateUpdateViews);
entityContainerMapping.StartLineNumber = navLineInfo.LineNumber;
entityContainerMapping.StartLinePosition = navLineInfo.LinePosition;
}
// Load the child nodes for the created EntityContainerMapping.
LoadEntityContainerMappingChildNodes(nav.Clone(), entityContainerMapping, storageEntityContainerType);
return entityContainerMapping;
}
/// <summary>
/// The method loads the child nodes for the EntityContainer Mapping node
/// into the internal datastructures.
/// </summary>
private void LoadEntityContainerMappingChildNodes(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_Container_SubElement,
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,
entityContainerMapping.EdmEntityContainer.Name, StorageMappingErrorCode.EmptyContainerMapping,
this.m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return;
}
ValidateFunctionAssociationFunctionMappingUnique(nav.Clone(), entityContainerMapping);
ValidateModificationFunctionMappingConsistentForAssociations(nav.Clone(), entityContainerMapping);
ValidateQueryViewsClosure(nav.Clone(), entityContainerMapping);
ValidateEntitySetFunctionMappingClosure(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;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="nav"></param>
/// <param name="entityContainerMapping"></param>
private void ValidateModificationFunctionMappingConsistentForAssociations(XPathNavigator nav, StorageEntityContainerMapping entityContainerMapping)
{
foreach (StorageEntitySetMapping entitySetMapping in entityContainerMapping.EntitySetMaps)
{
if (entitySetMapping.ModificationFunctionMappings.Count > 0)
{
// determine the set of association sets that should be mapped for every operation
Set<AssociationSetEnd> expectedEnds = new Set<AssociationSetEnd>(
entitySetMapping.ImplicitlyMappedAssociationSetEnds).MakeReadOnly();
// check that each operation covers each association set
foreach (StorageEntityTypeModificationFunctionMapping entityTypeMapping in entitySetMapping.ModificationFunctionMappings)
{
if (null != entityTypeMapping.DeleteFunctionMapping)
{
ValidateModificationFunctionMappingConsistentForAssociations(nav, entitySetMapping, entityTypeMapping,
entityTypeMapping.DeleteFunctionMapping,
expectedEnds, StorageMslConstructs.DeleteFunctionElement);
}
if (null != entityTypeMapping.InsertFunctionMapping)
{
ValidateModificationFunctionMappingConsistentForAssociations(nav, entitySetMapping, entityTypeMapping,
entityTypeMapping.InsertFunctionMapping,
expectedEnds, StorageMslConstructs.InsertFunctionElement);
}
if (null != entityTypeMapping.UpdateFunctionMapping)
{
ValidateModificationFunctionMappingConsistentForAssociations(nav, entitySetMapping, entityTypeMapping,
entityTypeMapping.UpdateFunctionMapping,
expectedEnds, StorageMslConstructs.UpdateFunctionElement);
}
}
}
}
}
private void ValidateModificationFunctionMappingConsistentForAssociations(
XPathNavigator nav,
StorageEntitySetMapping entitySetMapping,
StorageEntityTypeModificationFunctionMapping entityTypeMapping,
StorageModificationFunctionMapping functionMapping,
Set<AssociationSetEnd> expectedEnds, string elementName)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
// check that all expected association sets are mapped for in this function mapping
Set<AssociationSetEnd> actualEnds = new Set<AssociationSetEnd>(functionMapping.CollocatedAssociationSetEnds);
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(Strings.Mapping_ModificationFunction_AssociationSetNotMappedForOperation(
entitySetMapping.Set.Name,
expectedEnd.ParentAssociationSet.Name,
elementName,
entityTypeMapping.EntityType.FullName),
StorageMappingErrorCode.InvalidModificationFunctionMappingAssociationSetNotMappedForOperation,
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(Strings.Mapping_ModificationFunction_AssociationEndMappingInvalidForEntityType(
entityTypeMapping.EntityType.FullName,
actualEnd.ParentAssociationSet.Name,
MetadataHelper.GetEntityTypeForEnd(MetadataHelper.GetOppositeEnd(actualEnd).CorrespondingAssociationEndMember).FullName),
StorageMappingErrorCode.InvalidModificationFunctionMappingAssociationEndMappingInvalidForEntityType,
m_sourceLocation,
xmlLineInfoNav,
m_parsingErrors);
}
}
}
/// <summary>
/// Validates that association sets are only mapped once.
/// </summary>
/// <param name="nav"></param>
/// <param name="entityContainerMapping">Container to validate</param>
private void ValidateFunctionAssociationFunctionMappingUnique(XPathNavigator nav, StorageEntityContainerMapping entityContainerMapping)
{
Dictionary<EntitySetBase, int> mappingCounts = new Dictionary<EntitySetBase, int>();
// Walk through all entity set mappings
foreach (StorageEntitySetMapping entitySetMapping in entityContainerMapping.EntitySetMaps)
{
if (entitySetMapping.ModificationFunctionMappings.Count > 0)
{
// Get set of association sets implicitly mapped associations to avoid double counting
Set<EntitySetBase> associationSets = new Set<EntitySetBase>();
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.ModificationFunctionMapping)
{
IncrementCount(mappingCounts, associationSetMapping.Set);
}
}
// Check for redundantly mapped association sets
List<string> violationNames = new List<string>();
foreach (KeyValuePair<EntitySetBase, int> 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_ModificationFunction_AssociationSetAmbiguous,
StringUtil.ToCommaSeparatedString(violationNames), StorageMappingErrorCode.AmbiguousModificationFunctionMappingForAssociationSet,
m_sourceLocation, (IXmlLineInfo)nav, m_parsingErrors);
}
}
private static void IncrementCount<T>(Dictionary<T, int> counts, T key)
{
int count;
if (counts.TryGetValue(key, out count))
{
count++;
}
else
{
count = 1;
}
counts[key] = count;
}
/// <summary>
/// Validates that all or no related extents have function mappings. If an EntitySet or an AssociationSet has a function mapping,
/// then all the sets that touched the same store tableSet must also have function mappings.
/// </summary>
/// <param name="nav"></param>
/// <param name="entityContainerMapping">Container to validate.</param>
private void ValidateEntitySetFunctionMappingClosure(XPathNavigator nav, StorageEntityContainerMapping entityContainerMapping)
{
// here we build a mapping between the tables and the sets,
// setmapping => typemapping => mappingfragments, foreach mappingfragments we have one Tableset,
// then add the tableset with setmapping to the dictionary
KeyToListMap<EntitySet, StorageSetMapping> setMappingPerTable =
new KeyToListMap<EntitySet, StorageSetMapping>(EqualityComparer<EntitySet>.Default);
// Walk through all set mappings
foreach (var setMapping in entityContainerMapping.AllSetMaps)
{
foreach (var typeMapping in setMapping.TypeMappings)
{
foreach (var fragment in typeMapping.MappingFragments)
{
setMappingPerTable.Add(fragment.TableSet, setMapping);
}
}
}
// Get set of association sets implicitly mapped associations to avoid double counting
Set<EntitySetBase> implicitMappedAssociationSets = new Set<EntitySetBase>();
// Walk through all entity set mappings
foreach (StorageEntitySetMapping entitySetMapping in entityContainerMapping.EntitySetMaps)
{
if (entitySetMapping.ModificationFunctionMappings.Count > 0)
{
foreach (AssociationSetEnd end in entitySetMapping.ImplicitlyMappedAssociationSetEnds)
{
implicitMappedAssociationSets.Add(end.ParentAssociationSet);
}
}
}
foreach (var table in setMappingPerTable.Keys)
{
// if any of the sets who touches the same table has modification function,
// then all the sets that touches the same table should have modification function
if (setMappingPerTable.ListForKey(table).Any(s => s.HasModificationFunctionMapping || implicitMappedAssociationSets.Any(aset=> aset == s.Set)) &&
setMappingPerTable.ListForKey(table).Any(s => !s.HasModificationFunctionMapping && !implicitMappedAssociationSets.Any(aset => aset == s.Set)))
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_ModificationFunction_MissingSetClosure,
StringUtil.ToCommaSeparatedString(setMappingPerTable.ListForKey(table)
.Where(s => !s.HasModificationFunctionMapping).Select(s=>s.Set.Name)),
StorageMappingErrorCode.MissingSetClosureInModificationFunctionMapping, m_sourceLocation, (IXmlLineInfo)nav
, m_parsingErrors);
}
}
}
private static void ValidateClosureAmongSets(StorageEntityContainerMapping entityContainerMapping, Set<EntitySetBase> sets, Set<EntitySetBase> additionalSetsInClosure)
{
bool nodeFound;
do
{
nodeFound = false;
List<EntitySetBase> newNodes = new List<EntitySetBase>();
// Register entity sets dependencies for association sets
foreach (EntitySetBase entitySetBase in additionalSetsInClosure)
{
AssociationSet associationSet = entitySetBase as AssociationSet;
//Foreign Key Associations do not add to the dependancies
if (associationSet != null
&& !associationSet.ElementType.IsForeignKey)
{
// 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)
{
AssociationSet associationSet = entitySetBase as AssociationSet;
//Foreign Key Associations do not add to the dependancies
if (associationSet != null
&& !associationSet.ElementType.IsForeignKey)
{
// 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);
}
/// <summary>
/// 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.
/// </summary>
/// <param name="nav"></param>
/// <param name="entityContainerMapping">Container to validate.</param>
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<EntitySetBase> setsWithQueryViews = new Set<EntitySetBase>();
Set<EntitySetBase> setsRequiringQueryViews = new Set<EntitySetBase>();
// 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,
StringUtil.ToCommaSeparatedString(setsRequiringQueryViews),
StorageMappingErrorCode.MissingSetClosureInQueryViews, m_sourceLocation, (IXmlLineInfo)nav
, m_parsingErrors);
}
}
/// <summary>
/// The method loads the child nodes for the EntitySet Mapping node
/// into the internal datastructures.
/// </summary>
/// <param name="nav"></param>
/// <param name="entityContainerMapping"></param>
/// <param name="storageEntityContainerType"></param>
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);
bool distinctFlag = GetBoolAttributeValue(nav.Clone(), StorageMslConstructs.MappingFragmentMakeColumnsDistinctAttribute, false /*default value*/);
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, 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, false /*No distinct flag so far*/, entityContainerMapping.GenerateUpdateViews);
break;
}
case StorageMslConstructs.QueryViewElement:
{
if (!(String.IsNullOrEmpty(tableName)))
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_TableName_QueryView, 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, distinctFlag, entityContainerMapping.GenerateUpdateViews);
}
ValidateAllEntityTypesHaveFunctionMapping(nav.Clone(), setMapping);
//Add a schema error if the set mapping has no content
if (setMapping.HasNoContent)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Emtpty_SetMap, 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<EdmType> functionMappedTypes = new Set<EdmType>();
foreach (StorageEntityTypeModificationFunctionMapping modificationFunctionMapping in setMapping.ModificationFunctionMappings)
{
functionMappedTypes.Add(modificationFunctionMapping.EntityType);
}
if (0 < functionMappedTypes.Count)
{
Set<EdmType> unmappedTypes = new Set<EdmType>(MetadataHelper.GetTypeAndSubtypesOf(setMapping.Set.ElementType, EdmItemCollection, false /*includeAbstractTypes*/));
unmappedTypes.Subtract(functionMappedTypes);
// Remove abstract types
Set<EdmType> abstractTypes = new Set<EdmType>();
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_ModificationFunction_MissingEntityType,
StringUtil.ToCommaSeparatedString(unmappedTypes),
StorageMappingErrorCode.MissingModificationFunctionMappingForEntityType, m_sourceLocation, (IXmlLineInfo)nav
, m_parsingErrors);
}
}
}
private bool TryParseEntityTypeAttribute(
XPathNavigator nav,
EntityType rootEntityType,
Func<EntityType, string> typeNotAssignableMessage,
out Set<EntityType> isOfTypeEntityTypes,
out Set<EntityType> entityTypes)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
string entityTypeAttribute = GetAttributeValue(nav.Clone(), StorageMslConstructs.EntitySetMappingTypeNameAttribute);
isOfTypeEntityTypes = new Set<EntityType>();
entityTypes = new Set<EntityType>();
// 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(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<EntityType>(entityTypeName, out entityType))
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Entity_Type, 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<EdmType> typeAndSubTypes = MetadataHelper.GetTypeAndSubtypesOf(entityType, EdmItemCollection, false /*includeAbstractTypes*/);
if (!typeAndSubTypes.GetEnumerator().MoveNext())
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_AbstractEntity_IsOfType, entityType.FullName,
StorageMappingErrorCode.MappingOfAbstractType, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return false;
}
}
else
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_AbstractEntity_Type, entityType.FullName,
StorageMappingErrorCode.MappingOfAbstractType, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return false;
}
}
// Add type to set
if (isTypeOf)
{
isOfTypeEntityTypes.Add(entityType);
}
else
{
entityTypes.Add(entityType);
}
}
// No failures
return true;
}
/// <summary>
/// The method loads the child nodes for the EntityType Mapping node
/// into the internal datastructures.
/// </summary>
/// <param name="nav"></param>
/// <param name="entitySetMapping"></param>
/// <param name="tableName"></param>
/// <param name="storageEntityContainerType"></param>
private void LoadEntityTypeMapping(XPathNavigator nav, StorageEntitySetMapping entitySetMapping, string tableName, EntityContainer storageEntityContainerType, bool distinctFlagAboveType, bool generateUpdateViews)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
//Create an EntityTypeMapping to hold the information for EntityType mapping.
StorageEntityTypeMapping entityTypeMapping = new StorageEntityTypeMapping(entitySetMapping);
//Get entity types
Set<EntityType> entityTypes;
Set<EntityType> isOfTypeEntityTypes;
EntityType rootEntityType = (EntityType)entitySetMapping.Set.ElementType;
if (!TryParseEntityTypeAttribute(nav.Clone(), rootEntityType,
e => Strings.Mapping_InvalidContent_Entity_Type_For_Entity_Set(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)
{
entitySetMapping.HasModificationFunctionMapping = true;
LoadEntityTypeModificationFunctionMapping(nav.Clone(), entitySetMapping, entityTypeMapping);
}
else if (nav.LocalName != StorageMslConstructs.MappingFragmentElement)
{
AddToSchemaErrors(Strings.Mapping_InvalidContent_Table_Expected,
StorageMappingErrorCode.TableMappingFragmentExpected, m_sourceLocation, xmlLineInfoNav
, m_parsingErrors);
}
else
{
bool distinctFlag = GetBoolAttributeValue(nav.Clone(), StorageMslConstructs.MappingFragmentMakeColumnsDistinctAttribute, false /*default value*/);
if (generateUpdateViews && distinctFlag)
{
AddToSchemaErrors(Strings.Mapping_DistinctFlagInReadWriteContainer,
StorageMappingErrorCode.DistinctFragmentInReadWriteContainer, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
}
tableName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.MappingFragmentStoreEntitySetAttribute);
StorageMappingFragment fragment = LoadMappingFragment(nav.Clone(), entityTypeMapping, tableName, storageEntityContainerType, distinctFlag);
//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_ModificationFunction_In_Table_Context,
StorageMappingErrorCode.InvalidTableNameAttributeWithModificationFunctionMapping,
m_sourceLocation, xmlLineInfoNav
, m_parsingErrors);
}
if (generateUpdateViews && distinctFlagAboveType)
{
AddToSchemaErrors(Strings.Mapping_DistinctFlagInReadWriteContainer,
StorageMappingErrorCode.DistinctFragmentInReadWriteContainer, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
}
StorageMappingFragment fragment = LoadMappingFragment(nav.Clone(), entityTypeMapping, tableName,
storageEntityContainerType, distinctFlagAboveType);
//The fragment can be null in the cases of validation errors.
if (fragment != null)
{
entityTypeMapping.AddFragment(fragment);
}
}
entitySetMapping.AddTypeMapping(entityTypeMapping);
}
/// <summary>
/// Loads modification function mappings for entity type.
/// </summary>
/// <param name="nav"></param>
/// <param name="entitySetMapping"></param>
/// <param name="entityTypeMapping"></param>
private void LoadEntityTypeModificationFunctionMapping(
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_ModificationFunction_Multiple_Types,
StorageMappingErrorCode.InvalidModificationFunctionMappingForMultipleTypes,
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, entityType.FullName,
StorageMappingErrorCode.MappingOfAbstractType, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return;
}
// check that no mapping exists for this entity type already
foreach (StorageEntityTypeModificationFunctionMapping existingMapping in entitySetMapping.ModificationFunctionMappings)
{
if (existingMapping.EntityType.Equals(entityType))
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_ModificationFunction_RedundantEntityTypeMapping,
entityType.Name, StorageMappingErrorCode.RedundantEntityTypeMappingInModificationFunctionMapping, m_sourceLocation, xmlLineInfoNav
, m_parsingErrors);
return;
}
}
// create function loader
ModificationFunctionMappingLoader functionLoader = new ModificationFunctionMappingLoader(this, entitySetMapping.Set);
// Load all function definitions (for insert, delete and update)
StorageModificationFunctionMapping deleteFunctionMapping = null;
StorageModificationFunctionMapping insertFunctionMapping = null;
StorageModificationFunctionMapping updateFunctionMapping = null;
if (nav.MoveToChild(XPathNodeType.Element))
{
do
{
switch (nav.LocalName)
{
case StorageMslConstructs.DeleteFunctionElement:
deleteFunctionMapping = functionLoader.LoadEntityTypeModificationFunctionMapping(nav.Clone(), entitySetMapping.Set, false, true, entityType);
break;
case StorageMslConstructs.InsertFunctionElement:
insertFunctionMapping = functionLoader.LoadEntityTypeModificationFunctionMapping(nav.Clone(), entitySetMapping.Set, true, false, entityType);
break;
case StorageMslConstructs.UpdateFunctionElement:
updateFunctionMapping = functionLoader.LoadEntityTypeModificationFunctionMapping(nav.Clone(), entitySetMapping.Set, true, true, entityType);
break;
}
} while (nav.MoveToNext(XPathNodeType.Element));
}
// 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)
IEnumerable<StorageModificationFunctionParameterBinding> parameterList = new List<StorageModificationFunctionParameterBinding>();
if (null != deleteFunctionMapping)
{
parameterList = Helper.Concat(parameterList, deleteFunctionMapping.ParameterBindings);
}
if (null != insertFunctionMapping)
{
parameterList = Helper.Concat(parameterList, insertFunctionMapping.ParameterBindings);
}
if (null != updateFunctionMapping)
{
parameterList = Helper.Concat(parameterList, updateFunctionMapping.ParameterBindings);
}
var associationEnds = new Dictionary<AssociationSet, AssociationEndMember>();
foreach (StorageModificationFunctionParameterBinding parameterBinding in parameterList)
{
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
AssociationEndMember currentEnd = parameterBinding.MemberPath.AssociationSetEnd.CorrespondingAssociationEndMember;
AssociationEndMember existingEnd;
if (associationEnds.TryGetValue(associationSet, out existingEnd) &&
existingEnd != currentEnd)
{
AddToSchemaErrorWithMessage(Strings.Mapping_ModificationFunction_MultipleEndsOfAssociationMapped(
currentEnd.Name, existingEnd.Name, associationSet.Name),
StorageMappingErrorCode.InvalidModificationFunctionMappingMultipleEndsOfAssociationMapped, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return;
}
else
{
associationEnds[associationSet] = currentEnd;
}
}
}
// Register the function mapping on the entity set mapping
StorageEntityTypeModificationFunctionMapping mapping = new StorageEntityTypeModificationFunctionMapping(
entityType, deleteFunctionMapping, insertFunctionMapping, updateFunctionMapping);
entitySetMapping.AddModificationFunctionMapping(mapping);
}
/// <summary>
/// The method loads the query view for the Set Mapping node
/// into the internal datastructures.
/// </summary>
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,
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<EntityType> entityTypes;
Set<EntityType> isOfTypeEntityTypes;
EntityType rootEntityType = (EntityType)setMapping.Set.ElementType;
if (!TryParseEntityTypeAttribute(nav.Clone(), rootEntityType,
e => Strings.Mapping_InvalidContent_Entity_Type_For_Entity_Set(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,
entityType.Name, setMapping.Set.Name, StorageMappingErrorCode.EmptyQueryView,
m_sourceLocation, (IXmlLineInfo)nav, m_parsingErrors);
return false;
}
else
{
AddToSchemaErrorWithMemberAndStructure(Strings.Mapping_Empty_QueryView_OfTypeOnly,
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<EntityTypeBase, bool>(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;
}
}
/// <summary>
/// The method loads the child nodes for the AssociationSet Mapping node
/// into the internal datastructures.
/// </summary>
/// <param name="nav"></param>
/// <param name="entityContainerMapping"></param>
/// <param name="storageEntityContainerType"></param>
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, 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 (associationSet.ElementType.IsForeignKey)
{
ReferentialConstraint constraint = associationSet.ElementType.ReferentialConstraints.Single();
IEnumerable<EdmMember> dependentKeys = MetadataHelper.GetEntityTypeForEnd((AssociationEndMember)constraint.ToRole).KeyMembers;
if (associationSet.ElementType.ReferentialConstraints.Single().ToProperties.All(p => dependentKeys.Contains(p)))
{
EdmSchemaError error = AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_ForeignKey_Association_Set_PKtoPK, associationSetName,
StorageMappingErrorCode.InvalidAssociationSet, m_sourceLocation, navLineInfo, m_parsingErrors);
//Downgrade to a warning if the foreign key constraint is between keys (for back-compat reasons)
error.Severity = EdmSchemaErrorSeverity.Warning;
}
else
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_ForeignKey_Association_Set, associationSetName,
StorageMappingErrorCode.InvalidAssociationSet, m_sourceLocation, navLineInfo, m_parsingErrors);
}
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, 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, 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, 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,
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, associationSetName,
StorageMappingErrorCode.InvalidContent, m_sourceLocation, navLineInfo, m_parsingErrors);
return;
}
else
{
Debug.Assert(false, "XSD validation should ensure this");
}
}
#region LoadFunctionImportMapping implementation
/// <summary>
/// The method loads a function import mapping element
/// </summary>
/// <param name="nav"></param>
/// <param name="entityContainerMapping"></param>
/// <param name="storageEntityContainerType"></param>
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 composability alignment of function import and target function.
if (!functionImport.IsComposableAttribute && targetFunction.IsComposableAttribute)
{
AddToSchemaErrorWithMessage(Strings.Mapping_FunctionImport_TargetFunctionMustBeNonComposable(functionImport.FullName, targetFunction.FullName),
StorageMappingErrorCode.MappingFunctionImportTargetFunctionMustBeNonComposable,
m_sourceLocation, lineInfo, m_parsingErrors);
return;
}
else if (functionImport.IsComposableAttribute && !targetFunction.IsComposableAttribute)
{
AddToSchemaErrorWithMessage(Strings.Mapping_FunctionImport_TargetFunctionMustBeComposable(functionImport.FullName, targetFunction.FullName),
StorageMappingErrorCode.MappingFunctionImportTargetFunctionMustBeComposable,
m_sourceLocation, lineInfo, m_parsingErrors);
return;
}
// Validate parameters are compatible between the store and model functions
ValidateFunctionImportMappingParameters(nav, targetFunction, functionImport);
// Process type mapping information
var typeMappingsList = new List<List<FunctionImportStructuralTypeMapping>>();
if (nav.MoveToChild(XPathNodeType.Element))
{
int resultSetIndex = 0;
do
{
if (nav.LocalName == StorageMslConstructs.FunctionImportMappingResultMapping)
{
List<FunctionImportStructuralTypeMapping> typeMappings = GetFunctionImportMappingResultMapping(nav.Clone(), lineInfo, targetFunction, functionImport, resultSetIndex, typeMappingsList);
typeMappingsList.Add(typeMappings);
}
resultSetIndex++;
} while (nav.MoveToNext(XPathNodeType.Element));
}
// Verify that there are the right number of result mappings
if (typeMappingsList.Count > 0 && typeMappingsList.Count != functionImport.ReturnParameters.Count)
{
AddToSchemaErrors(Strings.Mapping_FunctionImport_ResultMappingCountDoesNotMatchResultCount(functionImport.Identity),
StorageMappingErrorCode.FunctionResultMappingCountMismatch, m_sourceLocation, lineInfo, m_parsingErrors);
return;
}
if (functionImport.IsComposableAttribute)
{
//
// Add composable function import mapping to the list.
//
// Function mapping is allowed only for TVFs on the s-space.
var cTypeTargetFunction = this.StoreItemCollection.ConvertToCTypeFunction(targetFunction);
var cTypeTvfElementType = System.Data.Common.TypeHelpers.GetTvfReturnType(cTypeTargetFunction);
var sTypeTvfElementType = System.Data.Common.TypeHelpers.GetTvfReturnType(targetFunction);
if (cTypeTvfElementType == null)
{
Debug.Assert(sTypeTvfElementType == null, "sTypeTvfElementType == null");
AddToSchemaErrors(Strings.Mapping_FunctionImport_ResultMapping_InvalidSType(functionImport.Identity),
StorageMappingErrorCode.MappingFunctionImportTVFExpected, m_sourceLocation, lineInfo, m_parsingErrors);
return;
}
Debug.Assert(functionImport.ReturnParameters.Count == 1, "functionImport.ReturnParameters.Count == 1 for a composable function import.");
var typeMappings = typeMappingsList.Count > 0 ? typeMappingsList[0] : new List<FunctionImportStructuralTypeMapping>();
FunctionImportMappingComposable mapping = null;
EdmType resultType;
if (MetadataHelper.TryGetFunctionImportReturnType<EdmType>(functionImport, 0, out resultType))
{
if (Helper.IsStructuralType(resultType))
{
if (!TryCreateFunctionImportMappingComposableWithStructuralResult(
functionImport,
cTypeTargetFunction,
typeMappings,
(StructuralType)resultType,
cTypeTvfElementType,
sTypeTvfElementType,
lineInfo,
out mapping))
{
return;
}
}
else
{
Debug.Assert(TypeSemantics.IsScalarType(resultType), "TypeSemantics.IsScalarType(resultType)");
Debug.Assert(typeMappings.Count == 0, "typeMappings.Count == 0");
if (!TryCreateFunctionImportMappingComposableWithScalarResult(
functionImport,
cTypeTargetFunction,
targetFunction,
resultType,
cTypeTvfElementType,
sTypeTvfElementType,
lineInfo,
out mapping))
{
return;
}
}
}
else
{
Debug.Fail("Composable function import must have return type.");
}
Debug.Assert(mapping != null, "mapping != null");
entityContainerMapping.AddFunctionImportMapping(functionImport, mapping);
}
else
{
//
// Add non-composable function import mapping to the list.
//
var mapping = new FunctionImportMappingNonComposable(functionImport, targetFunction, typeMappingsList, this.EdmItemCollection);
// Verify that all entity types can be produced.
foreach (FunctionImportStructuralTypeMappingKB resultMapping in mapping.ResultMappings)
{
resultMapping.ValidateTypeConditions(/*validateAmbiguity: */false, m_parsingErrors, m_sourceLocation);
}
// Verify that function imports returning abstract types include explicit mappings
for (int i = 0; i < mapping.ResultMappings.Count; i++)
{
EntityType returnEntityType;
if (MetadataHelper.TryGetFunctionImportReturnType<EntityType>(functionImport, i, out returnEntityType) &&
returnEntityType.Abstract &&
mapping.GetResultMapping(i).NormalizedEntityTypeMappings.Count == 0)
{
AddToSchemaErrorWithMemberAndStructure(Strings.Mapping_FunctionImport_ImplicitMappingForAbstractReturnType, returnEntityType.FullName,
functionImport.Identity, StorageMappingErrorCode.MappingOfAbstractType, m_sourceLocation, lineInfo, m_parsingErrors);
}
}
entityContainerMapping.AddFunctionImportMapping(functionImport, mapping);
}
}
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<EdmFunction> functionOverloads = this.StoreItemCollection.GetFunctions(functionName);
if (functionOverloads.Count == 0)
{
AddToSchemaErrorWithMessage(Strings.Mapping_FunctionImport_StoreFunctionDoesNotExist(functionName),
StorageMappingErrorCode.MappingFunctionImportStoreFunctionDoesNotExist,
m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return false;
}
else if (functionOverloads.Count > 1)
{
AddToSchemaErrorWithMessage(Strings.Mapping_FunctionImport_FunctionAmbiguous(functionName),
StorageMappingErrorCode.MappingFunctionImportStoreFunctionAmbiguous,
m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return false;
}
targetFunction = functionOverloads.Single();
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(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(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(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(Strings.Mapping_FunctionImport_IncompatibleParameterMode(targetParameter.Name, targetParameter.Mode, importParameter.Mode),
StorageMappingErrorCode.MappingFunctionImportIncompatibleParameterMode,
m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
}
PrimitiveType importType = Helper.AsPrimitive(importParameter.TypeUsage.EdmType);
Debug.Assert(importType != null, "Function import parameters must be primitive.");
if (Helper.IsSpatialType(importType))
{
importType = Helper.GetSpatialNormalizedPrimitiveType(importType);
}
PrimitiveType cspaceTargetType = (PrimitiveType)StoreItemCollection.StoreProviderManifest.GetEdmType(targetParameter.TypeUsage).EdmType;
if (cspaceTargetType == null)
{
AddToSchemaErrorWithMessage(Strings.Mapping_ProviderReturnsNullType(targetParameter.Name),
StorageMappingErrorCode.MappingStoreProviderReturnsNullEdmType,
m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return;
}
// there are no type facets declared for function parameter types;
// we simply verify the primitive type kind is equivalent.
// for enums we just use the underlying enum type.
if (cspaceTargetType.PrimitiveTypeKind != importType.PrimitiveTypeKind)
{
var schemaErrorMessage = Helper.IsEnumType(importParameter.TypeUsage.EdmType) ?
Strings.Mapping_FunctionImport_IncompatibleEnumParameterType(
targetParameter.Name,
cspaceTargetType.Name,
importParameter.TypeUsage.EdmType.FullName,
Helper.GetUnderlyingEdmTypeForEnumType(importParameter.TypeUsage.EdmType).Name) :
Strings.Mapping_FunctionImport_IncompatibleParameterType(
targetParameter.Name,
cspaceTargetType.Name,
importType.Name);
AddToSchemaErrorWithMessage(
schemaErrorMessage,
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(Strings.Mapping_FunctionImport_ImportParameterHasNoCorrespondingTargetParameter(importParameter.Name),
StorageMappingErrorCode.MappingFunctionImportImportParameterHasNoCorrespondingTargetParameter,
m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
}
}
}
private List<FunctionImportStructuralTypeMapping> GetFunctionImportMappingResultMapping(
XPathNavigator nav,
IXmlLineInfo functionImportMappingLineInfo,
EdmFunction targetFunction,
EdmFunction functionImport,
int resultSetIndex,
List<List<FunctionImportStructuralTypeMapping>> typeMappingsList)
{
List<FunctionImportStructuralTypeMapping> typeMappings = new List<FunctionImportStructuralTypeMapping>();
if (nav.MoveToChild(XPathNodeType.Element))
{
do
{
EntitySet entitySet = functionImport.EntitySets.Count > resultSetIndex ?
functionImport.EntitySets[resultSetIndex] : null;
if (nav.LocalName == StorageMslConstructs.EntityTypeMappingElement)
{
EntityType resultEntityType;
if (MetadataHelper.TryGetFunctionImportReturnType<EntityType>(functionImport, resultSetIndex, out resultEntityType))
{
// Cannot specify an entity type mapping for a function import that does not return members of an entity set.
if (entitySet == null)
{
AddToSchemaErrors(Strings.Mapping_FunctionImport_EntityTypeMappingForFunctionNotReturningEntitySet(
StorageMslConstructs.EntityTypeMappingElement, functionImport.Identity),
StorageMappingErrorCode.MappingFunctionImportEntityTypeMappingForFunctionNotReturningEntitySet,
m_sourceLocation, functionImportMappingLineInfo, m_parsingErrors);
}
FunctionImportEntityTypeMapping typeMapping;
if (TryLoadFunctionImportEntityTypeMapping(
nav.Clone(),
resultEntityType,
(EntityType e) => Strings.Mapping_FunctionImport_InvalidContentEntityTypeForEntitySet(e.FullName,
resultEntityType.FullName,
entitySet.Name,
functionImport.Identity),
out typeMapping))
{
typeMappings.Add(typeMapping);
}
}
else
{
AddToSchemaErrors(Strings.Mapping_FunctionImport_ResultMapping_InvalidCTypeETExpected(functionImport.Identity),
StorageMappingErrorCode.MappingFunctionImportUnexpectedEntityTypeMapping,
m_sourceLocation, functionImportMappingLineInfo, m_parsingErrors);
}
}
else if (nav.LocalName == StorageMslConstructs.ComplexTypeMappingElement)
{
ComplexType resultComplexType;
if (MetadataHelper.TryGetFunctionImportReturnType<ComplexType>(functionImport, resultSetIndex, out resultComplexType))
{
Debug.Assert(entitySet == null, "entitySet == null for complex type mapping in function imports.");
FunctionImportComplexTypeMapping typeMapping;
if (TryLoadFunctionImportComplexTypeMapping(nav.Clone(), resultComplexType, functionImport, out typeMapping))
{
typeMappings.Add(typeMapping);
}
}
else
{
AddToSchemaErrors(Strings.Mapping_FunctionImport_ResultMapping_InvalidCTypeCTExpected(functionImport.Identity),
StorageMappingErrorCode.MappingFunctionImportUnexpectedComplexTypeMapping,
m_sourceLocation, functionImportMappingLineInfo, m_parsingErrors);
}
}
}
while (nav.MoveToNext(XPathNodeType.Element));
}
return typeMappings;
}
private bool TryLoadFunctionImportComplexTypeMapping(
XPathNavigator nav,
ComplexType resultComplexType,
EdmFunction functionImport,
out FunctionImportComplexTypeMapping typeMapping)
{
typeMapping = null;
var lineInfo = new LineInfo(nav);
ComplexType complexType;
if (!TryParseComplexTypeAttribute(nav, resultComplexType, functionImport, out complexType))
{
return false;
}
Collection<FunctionImportReturnTypePropertyMapping> columnRenameMappings = new Collection<FunctionImportReturnTypePropertyMapping>();
if (!LoadFunctionImportStructuralType(nav.Clone(), new List<StructuralType>() { complexType }, columnRenameMappings, null))
{
return false;
}
typeMapping = new FunctionImportComplexTypeMapping(complexType, columnRenameMappings, lineInfo);
return true;
}
private bool TryParseComplexTypeAttribute(XPathNavigator nav, ComplexType resultComplexType, EdmFunction functionImport, out ComplexType complexType)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
string complexTypeName = GetAttributeValue(nav.Clone(), StorageMslConstructs.ComplexTypeMappingTypeNameAttribute);
complexTypeName = GetAliasResolvedValue(complexTypeName);
if (!this.EdmItemCollection.TryGetItem<ComplexType>(complexTypeName, out complexType))
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Complex_Type, complexTypeName,
StorageMappingErrorCode.InvalidComplexType, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return false;
}
if (!Helper.IsAssignableFrom(resultComplexType, complexType))
{
IXmlLineInfo lineInfo = xmlLineInfoNav;
AddToSchemaErrorWithMessage(
Strings.Mapping_FunctionImport_ResultMapping_MappedTypeDoesNotMatchReturnType(functionImport.Identity, complexType.FullName),
StorageMappingErrorCode.InvalidComplexType, m_sourceLocation, xmlLineInfoNav, m_parsingErrors);
return false;
}
return true;
}
private bool TryLoadFunctionImportEntityTypeMapping(
XPathNavigator nav,
EntityType resultEntityType,
Func<EntityType, string> registerEntityTypeMismatchError,
out FunctionImportEntityTypeMapping typeMapping)
{
typeMapping = null;
var lineInfo = new LineInfo(nav);
// Process entity type.
string entityTypeString = GetAttributeValue(nav.Clone(), StorageMslConstructs.EntitySetMappingTypeNameAttribute);
Set<EntityType> isOfTypeEntityTypes;
Set<EntityType> entityTypes;
{
// Verify the entity type is appropriate to the function import's result entity type.
if (!TryParseEntityTypeAttribute(nav.Clone(), resultEntityType, registerEntityTypeMismatchError, out isOfTypeEntityTypes, out entityTypes))
{
return false;
}
}
IEnumerable<StructuralType> currentTypesInHierachy = isOfTypeEntityTypes.Concat(entityTypes).Distinct().OfType<StructuralType>();
Collection<FunctionImportReturnTypePropertyMapping> columnRenameMappings = new Collection<FunctionImportReturnTypePropertyMapping>();
// Process all conditions and column renames.
List<FunctionImportEntityTypeMappingCondition> conditions = new List<FunctionImportEntityTypeMappingCondition>();
if (!LoadFunctionImportStructuralType(nav.Clone(), currentTypesInHierachy, columnRenameMappings, conditions))
{
return false;
}
typeMapping = new FunctionImportEntityTypeMapping(isOfTypeEntityTypes, entityTypes, conditions, columnRenameMappings, lineInfo);
return true;
}
private bool LoadFunctionImportStructuralType(
XPathNavigator nav,
IEnumerable<StructuralType> currentTypes,
Collection<FunctionImportReturnTypePropertyMapping> columnRenameMappings,
List<FunctionImportEntityTypeMappingCondition> conditions)
{
Debug.Assert(null != columnRenameMappings, "columnRenameMappings cannot be null");
Debug.Assert(null != nav, "nav cannot be null");
Debug.Assert(null != currentTypes, "currentTypes cannot be null");
IXmlLineInfo lineInfo = (IXmlLineInfo)(nav.Clone());
if (nav.MoveToChild(XPathNodeType.Element))
{
do
{
if (nav.LocalName == StorageMslConstructs.ScalarPropertyElement)
{
LoadFunctionImportStructuralTypeMappingScalarProperty(nav, columnRenameMappings, currentTypes);
}
if (nav.LocalName == StorageMslConstructs.ConditionElement)
{
LoadFunctionImportEntityTypeMappingCondition(nav, conditions);
}
}
while (nav.MoveToNext(XPathNodeType.Element));
}
bool errorFound = false;
if (null != conditions)
{
// make sure a single condition is specified per column
HashSet<string> columnsWithConditions = new HashSet<string>();
foreach (var condition in conditions)
{
if (!columnsWithConditions.Add(condition.ColumnName))
{
AddToSchemaErrorWithMessage(
Strings.Mapping_InvalidContent_Duplicate_Condition_Member(condition.ColumnName),
StorageMappingErrorCode.ConditionError,
m_sourceLocation, lineInfo, m_parsingErrors);
errorFound = true;
}
}
}
return !errorFound;
}
private void LoadFunctionImportStructuralTypeMappingScalarProperty(
XPathNavigator nav,
Collection<FunctionImportReturnTypePropertyMapping> columnRenameMappings,
IEnumerable<StructuralType> currentTypes)
{
var lineInfo = new LineInfo(nav);
string memberName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.ScalarPropertyNameAttribute);
string columnName = GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.ScalarPropertyColumnNameAttribute);
// Negative case: the property name is invalid
if (!currentTypes.All(t=>t.Members.Contains(memberName)))
{
AddToSchemaErrorWithMessage(
Strings.Mapping_InvalidContent_Cdm_Member(memberName),
StorageMappingErrorCode.InvalidEdmMember,
m_sourceLocation, lineInfo, m_parsingErrors);
}
if (columnRenameMappings.Any(m => m.CMember == memberName))
{
// Negative case: duplicate member name mapping in one type rename mapping
AddToSchemaErrorWithMessage(
Strings.Mapping_InvalidContent_Duplicate_Cdm_Member(memberName),
StorageMappingErrorCode.DuplicateMemberMapping,
m_sourceLocation, lineInfo, m_parsingErrors);
}
else
{
columnRenameMappings.Add(new FunctionImportReturnTypeScalarPropertyMapping(memberName, columnName, lineInfo));
}
}
private bool TryCreateFunctionImportMappingComposableWithStructuralResult(
EdmFunction functionImport,
EdmFunction cTypeTargetFunction,
List<FunctionImportStructuralTypeMapping> typeMappings,
StructuralType structuralResultType,
RowType cTypeTvfElementType,
RowType sTypeTvfElementType,
IXmlLineInfo lineInfo,
out FunctionImportMappingComposable mapping)
{
mapping = null;
// If it is an implicit structural type mapping, add a type mapping fragment for the return type of the function import,
// unless it is an abstract type.
if (typeMappings.Count == 0)
{
StructuralType resultType;
if (MetadataHelper.TryGetFunctionImportReturnType<StructuralType>(functionImport, 0, out resultType))
{
if (resultType.Abstract)
{
AddToSchemaErrorWithMemberAndStructure(Strings.Mapping_FunctionImport_ImplicitMappingForAbstractReturnType,
resultType.FullName, functionImport.Identity,
StorageMappingErrorCode.MappingOfAbstractType, m_sourceLocation, lineInfo, m_parsingErrors);
return false;
}
if (resultType.BuiltInTypeKind == BuiltInTypeKind.EntityType)
{
typeMappings.Add(new FunctionImportEntityTypeMapping(
Enumerable.Empty<EntityType>(),
new EntityType[] { (EntityType)resultType },
Enumerable.Empty<FunctionImportEntityTypeMappingCondition>(),
new Collection<FunctionImportReturnTypePropertyMapping>(),
new LineInfo(lineInfo)));
}
else
{
Debug.Assert(resultType.BuiltInTypeKind == BuiltInTypeKind.ComplexType, "resultType.BuiltInTypeKind == BuiltInTypeKind.ComplexType");
typeMappings.Add(new FunctionImportComplexTypeMapping(
(ComplexType)resultType,
new Collection<FunctionImportReturnTypePropertyMapping>(),
new LineInfo(lineInfo)));
}
}
}
// Validate and convert FunctionImportEntityTypeMapping elements into structure suitable for composable function import mapping.
var functionImportKB = new FunctionImportStructuralTypeMappingKB(typeMappings, this.EdmItemCollection);
var structuralTypeMappings = new List<Tuple<StructuralType, List<StorageConditionPropertyMapping>, List<StoragePropertyMapping>>>();
EdmProperty[] targetFunctionKeys = null;
if (functionImportKB.MappedEntityTypes.Count > 0)
{
// Validate TPH ambiguity.
if (!functionImportKB.ValidateTypeConditions(/*validateAmbiguity: */true, m_parsingErrors, m_sourceLocation))
{
return false;
}
// For each mapped entity type, prepare list of conditions and list of property mappings.
for (int i = 0; i < functionImportKB.MappedEntityTypes.Count; ++i)
{
List<StorageConditionPropertyMapping> typeConditions;
List<StoragePropertyMapping> propertyMappings;
if (TryConvertToEntityTypeConditionsAndPropertyMappings(
functionImport,
functionImportKB,
i,
cTypeTvfElementType,
sTypeTvfElementType,
lineInfo, out typeConditions, out propertyMappings))
{
structuralTypeMappings.Add(Tuple.Create((StructuralType)functionImportKB.MappedEntityTypes[i], typeConditions, propertyMappings));
}
}
if (structuralTypeMappings.Count < functionImportKB.MappedEntityTypes.Count)
{
// Some of the entity types produced errors during conversion, exit.
return false;
}
// Infer target function keys based on the c-space entity types.
if (!TryInferTVFKeys(structuralTypeMappings, out targetFunctionKeys))
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_FunctionImport_CannotInferTargetFunctionKeys, functionImport.Identity,
StorageMappingErrorCode.MappingFunctionImportCannotInferTargetFunctionKeys, m_sourceLocation, lineInfo, m_parsingErrors);
return false;
}
}
else
{
ComplexType resultComplexType;
if (MetadataHelper.TryGetFunctionImportReturnType<ComplexType>(functionImport, 0, out resultComplexType))
{
// Gather and validate complex type property mappings.
List<StoragePropertyMapping> propertyMappings;
if (!TryConvertToProperyMappings(resultComplexType, cTypeTvfElementType, sTypeTvfElementType, functionImport, functionImportKB, lineInfo, out propertyMappings))
{
return false;
}
structuralTypeMappings.Add(Tuple.Create((StructuralType)resultComplexType, new List<StorageConditionPropertyMapping>(), propertyMappings));
}
else
{
Debug.Fail("Function import return type is expected to be a collection of complex type.");
}
}
mapping = new FunctionImportMappingComposable(
functionImport,
cTypeTargetFunction,
structuralTypeMappings,
targetFunctionKeys,
m_storageMappingItemCollection,
m_sourceLocation,
new LineInfo(lineInfo));
return true;
}
/// <summary>
/// Attempts to infer key columns of the target function based on the function import mapping.
/// </summary>
internal static bool TryInferTVFKeys(List<Tuple<StructuralType, List<StorageConditionPropertyMapping>, List<StoragePropertyMapping>>> structuralTypeMappings, out EdmProperty[] keys)
{
keys = null;
Debug.Assert(structuralTypeMappings.Count > 0, "Function import returning entities must have non-empty structuralTypeMappings.");
foreach (var typeMapping in structuralTypeMappings)
{
EdmProperty[] currentKeys;
if (!TryInferTVFKeysForEntityType((EntityType)typeMapping.Item1, typeMapping.Item3, out currentKeys))
{
keys = null;
return false;
}
if (keys == null)
{
keys = currentKeys;
}
else
{
// Make sure all keys are mapped to the same columns.
Debug.Assert(keys.Length == currentKeys.Length, "All subtypes must have the same number of keys.");
for (int i = 0; i < keys.Length; ++i)
{
if (!keys[i].EdmEquals(currentKeys[i]))
{
keys = null;
return false;
}
}
}
}
// Make sure columns are non-nullable, otherwise it shouldn't be considered a key.
for (int i = 0; i < keys.Length; ++i)
{
if (keys[i].Nullable)
{
keys = null;
return false;
}
}
return true;
}
private static bool TryInferTVFKeysForEntityType(EntityType entityType, List<StoragePropertyMapping> propertyMappings, out EdmProperty[] keys)
{
keys = new EdmProperty[entityType.KeyMembers.Count];
for (int i = 0; i < keys.Length; ++i)
{
var mapping = propertyMappings[entityType.Properties.IndexOf((EdmProperty)entityType.KeyMembers[i])] as StorageScalarPropertyMapping;
if (mapping == null)
{
keys = null;
return false;
}
keys[i] = mapping.ColumnProperty;
}
return true;
}
private bool TryCreateFunctionImportMappingComposableWithScalarResult(
EdmFunction functionImport,
EdmFunction cTypeTargetFunction,
EdmFunction sTypeTargetFunction,
EdmType scalarResultType,
RowType cTypeTvfElementType,
RowType sTypeTvfElementType,
IXmlLineInfo lineInfo,
out FunctionImportMappingComposable mapping)
{
mapping = null;
// Make sure that TVF returns exactly one column
if (cTypeTvfElementType.Properties.Count > 1)
{
AddToSchemaErrors(Strings.Mapping_FunctionImport_ScalarMappingToMulticolumnTVF(functionImport.Identity, sTypeTargetFunction.Identity),
StorageMappingErrorCode.MappingFunctionImportScalarMappingToMulticolumnTVF, m_sourceLocation, lineInfo, m_parsingErrors);
return false;
}
// Make sure that scalarResultType agrees with the column type.
if (!ValidateFunctionImportMappingResultTypeCompatibility(TypeUsage.Create(scalarResultType), cTypeTvfElementType.Properties[0].TypeUsage))
{
AddToSchemaErrors(Strings.Mapping_FunctionImport_ScalarMappingTypeMismatch(
functionImport.ReturnParameter.TypeUsage.EdmType.FullName,
functionImport.Identity,
sTypeTargetFunction.ReturnParameter.TypeUsage.EdmType.FullName,
sTypeTargetFunction.Identity),
StorageMappingErrorCode.MappingFunctionImportScalarMappingTypeMismatch, m_sourceLocation, lineInfo, m_parsingErrors);
return false;
}
mapping = new FunctionImportMappingComposable(
functionImport,
cTypeTargetFunction,
null,
null,
m_storageMappingItemCollection,
m_sourceLocation,
new LineInfo(lineInfo));
return true;
}
private bool ValidateFunctionImportMappingResultTypeCompatibility(TypeUsage cSpaceMemberType, TypeUsage sSpaceMemberType)
{
// Function result data flows from S-side to C-side.
var fromType = sSpaceMemberType;
var toType = ResolveTypeUsageForEnums(cSpaceMemberType);
bool directlyPromotable = TypeSemantics.IsStructurallyEqualOrPromotableTo(fromType, toType);
bool inverselyPromotable = TypeSemantics.IsStructurallyEqualOrPromotableTo(toType, fromType);
// We are quite lax here. We only require that values belong to the same class (can flow in one or the other direction).
// We could require precisely s-type to be promotable to c-type, but in this case it won't be possible to reuse the same
// c-types for mapped functions and entity sets, because entity sets (read-write) require c-types to be promotable to s-types.
return directlyPromotable || inverselyPromotable;
}
private void LoadFunctionImportEntityTypeMappingCondition(XPathNavigator nav, List<FunctionImportEntityTypeMappingCondition> conditions)
{
var lineInfo = new LineInfo(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,
StorageMappingErrorCode.ConditionError, m_sourceLocation, lineInfo, m_parsingErrors);
}
else if ((isNull == null) && (value == null))
{
AddToSchemaErrors(Strings.Mapping_InvalidContent_ConditionMapping_Either_Values,
StorageMappingErrorCode.ConditionError, m_sourceLocation, lineInfo, m_parsingErrors);
}
else
{
if (isNull != null)
{
bool isNullValue = Convert.ToBoolean(isNull, CultureInfo.InvariantCulture);
conditions.Add(new FunctionImportEntityTypeMappingConditionIsNull(columnName, isNullValue, lineInfo));
}
else
{
XPathNavigator columnValue = nav.Clone();
columnValue.MoveToAttribute(StorageMslConstructs.ConditionValueAttribute, string.Empty);
conditions.Add(new FunctionImportEntityTypeMappingConditionValue(columnName, columnValue, lineInfo));
}
}
}
private bool TryConvertToEntityTypeConditionsAndPropertyMappings(
EdmFunction functionImport,
FunctionImportStructuralTypeMappingKB functionImportKB,
int typeID,
RowType cTypeTvfElementType,
RowType sTypeTvfElementType,
IXmlLineInfo navLineInfo,
out List<StorageConditionPropertyMapping> typeConditions,
out List<StoragePropertyMapping> propertyMappings)
{
var entityType = functionImportKB.MappedEntityTypes[typeID];
typeConditions = new List<StorageConditionPropertyMapping>();
bool errorFound = false;
// Gather and validate entity type conditions from the type-producing fragments.
foreach (var entityTypeMapping in functionImportKB.NormalizedEntityTypeMappings.Where(f => f.ImpliedEntityTypes[typeID]))
{
foreach (var condition in entityTypeMapping.ColumnConditions.Where(c => c != null))
{
EdmProperty column;
if (sTypeTvfElementType.Properties.TryGetValue(condition.ColumnName, false, out column))
{
object value;
bool? isNull;
if (condition.ConditionValue.IsSentinel)
{
value = null;
if (condition.ConditionValue == ValueCondition.IsNull)
{
isNull = true;
}
else
{
Debug.Assert(condition.ConditionValue == ValueCondition.IsNotNull, "Only IsNull or IsNotNull condition values are expected.");
isNull = false;
}
}
else
{
var cTypeColumn = cTypeTvfElementType.Properties[column.Name];
Debug.Assert(cTypeColumn != null, "cTypeColumn != null");
Debug.Assert(Helper.IsPrimitiveType(cTypeColumn.TypeUsage.EdmType), "S-space columns are expected to be of a primitive type.");
var cPrimitiveType = (PrimitiveType)cTypeColumn.TypeUsage.EdmType;
Debug.Assert(cPrimitiveType.ClrEquivalentType != null, "Scalar Types should have associated clr type");
Debug.Assert(condition is FunctionImportEntityTypeMappingConditionValue, "Non-sentinel condition is expected to be of type FunctionImportEntityTypeMappingConditionValue.");
value = ((FunctionImportEntityTypeMappingConditionValue)condition).GetConditionValue(
cPrimitiveType.ClrEquivalentType,
handleTypeNotComparable: () =>
{
AddToSchemaErrorWithMemberAndStructure(
Strings.Mapping_InvalidContent_ConditionMapping_InvalidPrimitiveTypeKind, column.Name, column.TypeUsage.EdmType.FullName,
StorageMappingErrorCode.ConditionError,
m_sourceLocation, condition.LineInfo, m_parsingErrors);
},
handleInvalidConditionValue: () =>
{
AddToSchemaErrors(
Strings.Mapping_ConditionValueTypeMismatch,
StorageMappingErrorCode.ConditionError,
m_sourceLocation, condition.LineInfo, m_parsingErrors);
});
if (value == null)
{
errorFound = true;
continue;
}
isNull = null;
}
typeConditions.Add(new StorageConditionPropertyMapping(null, column, value, isNull));
}
else
{
AddToSchemaErrorsWithMemberInfo(
Strings.Mapping_InvalidContent_Column, condition.ColumnName,
StorageMappingErrorCode.InvalidStorageMember,
m_sourceLocation, condition.LineInfo, m_parsingErrors);
}
}
}
// Gather and validate entity type property mappings.
errorFound |= !TryConvertToProperyMappings(entityType, cTypeTvfElementType, sTypeTvfElementType, functionImport, functionImportKB, navLineInfo, out propertyMappings);
return !errorFound;
}
private bool TryConvertToProperyMappings(
StructuralType structuralType,
RowType cTypeTvfElementType,
RowType sTypeTvfElementType,
EdmFunction functionImport,
FunctionImportStructuralTypeMappingKB functionImportKB,
IXmlLineInfo navLineInfo,
out List<StoragePropertyMapping> propertyMappings)
{
propertyMappings = new List<StoragePropertyMapping>();
// Gather and validate structuralType property mappings.
bool errorFound = false;
foreach (EdmProperty property in Common.TypeHelpers.GetAllStructuralMembers(structuralType))
{
// Only scalar property mappings are supported at the moment.
if (!Helper.IsScalarType(property.TypeUsage.EdmType))
{
EdmSchemaError error = new EdmSchemaError(
Strings.Mapping_Invalid_CSide_ScalarProperty(property.Name),
(int)StorageMappingErrorCode.InvalidTypeInScalarProperty,
EdmSchemaErrorSeverity.Error,
m_sourceLocation, navLineInfo.LineNumber, navLineInfo.LinePosition);
m_parsingErrors.Add(error);
errorFound = true;
continue;
}
string columnName = null;
IXmlLineInfo columnMappingLineInfo = null;
FunctionImportReturnTypeStructuralTypeColumnRenameMapping columnRenameMapping;
bool explicitPropertyMapping;
if (functionImportKB.ReturnTypeColumnsRenameMapping.TryGetValue(property.Name, out columnRenameMapping))
{
explicitPropertyMapping = true;
columnName = columnRenameMapping.GetRename(structuralType, out columnMappingLineInfo);
}
else
{
explicitPropertyMapping = false;
columnName = property.Name;
}
columnMappingLineInfo = columnMappingLineInfo != null && columnMappingLineInfo.HasLineInfo() ? columnMappingLineInfo : navLineInfo;
EdmProperty column;
if (sTypeTvfElementType.Properties.TryGetValue(columnName, false, out column))
{
Debug.Assert(cTypeTvfElementType.Properties.Contains(columnName), "cTypeTvfElementType.Properties.Contains(columnName)");
var cTypeColumn = cTypeTvfElementType.Properties[columnName];
if (ValidateFunctionImportMappingResultTypeCompatibility(property.TypeUsage, cTypeColumn.TypeUsage))
{
propertyMappings.Add(new StorageScalarPropertyMapping(property, column));
}
else
{
EdmSchemaError error = new EdmSchemaError(
GetInvalidMemberMappingErrorMessage(property, column),
(int)StorageMappingErrorCode.IncompatibleMemberMapping,
EdmSchemaErrorSeverity.Error,
m_sourceLocation, columnMappingLineInfo.LineNumber, columnMappingLineInfo.LinePosition);
m_parsingErrors.Add(error);
}
}
else
{
if (explicitPropertyMapping)
{
AddToSchemaErrorsWithMemberInfo(
Strings.Mapping_InvalidContent_Column, columnName,
StorageMappingErrorCode.InvalidStorageMember,
m_sourceLocation, columnMappingLineInfo, m_parsingErrors);
}
else
{
var error = new EdmSchemaError(
Strings.Mapping_FunctionImport_PropertyNotMapped(property.Name, structuralType.FullName, functionImport.Identity),
(int)StorageMappingErrorCode.MappingFunctionImportReturnTypePropertyNotMapped,
EdmSchemaErrorSeverity.Error,
m_sourceLocation, columnMappingLineInfo.LineNumber, columnMappingLineInfo.LinePosition);
m_parsingErrors.Add(error);
errorFound = true;
}
}
}
// Make sure that propertyMappings is in the order of properties of the structuredType.
// The rest of the code depends on it.
Debug.Assert(errorFound ||
Common.TypeHelpers.GetAllStructuralMembers(structuralType).Count == propertyMappings.Count &&
Common.TypeHelpers.GetAllStructuralMembers(structuralType).Cast<EdmMember>().Zip(propertyMappings)
.All(ppm => ppm.Key.EdmEquals(ppm.Value.EdmProperty)), "propertyMappings order does not correspond to the order of properties in the structuredType.");
return !errorFound;
}
#endregion
/// <summary>
/// The method loads the child nodes for the AssociationType Mapping node
/// into the internal datastructures.
/// </summary>
/// <param name="nav"></param>
/// <param name="associationSetMapping"></param>
/// <param name="associationTypeName"></param>
/// <param name="tableName"></param>
/// <param name="storageEntityContainerType"></param>
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<AssociationType>(associationTypeName, out associationType);
if (associationType == null)
{
//There is no point in continuing loading if the AssociationType is null
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Association_Type, 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(Strings.Mapping_Invalid_Association_Type_For_Association_Set(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, 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);
}
}
}
/// <summary>
/// Loads function mappings for the entity type.
/// </summary>
/// <param name="associationSetMapping"></param>
/// <param name="associationTypeMapping"></param>
/// <param name="nav"></param>
private void LoadAssociationTypeModificationFunctionMapping(
XPathNavigator nav,
StorageAssociationSetMapping associationSetMapping,
StorageAssociationTypeMapping associationTypeMapping)
{
// create function loader
ModificationFunctionMappingLoader functionLoader = new ModificationFunctionMappingLoader(this, associationSetMapping.Set);
// Load all function definitions (for insert, delete and update)
StorageModificationFunctionMapping deleteFunctionMapping = null;
StorageModificationFunctionMapping insertFunctionMapping = null;
if (nav.MoveToChild(XPathNodeType.Element))
{
do
{
switch (nav.LocalName)
{
case StorageMslConstructs.DeleteFunctionElement:
deleteFunctionMapping = functionLoader.LoadAssociationSetModificationFunctionMapping(nav.Clone(), associationSetMapping.Set, false);
break;
case StorageMslConstructs.InsertFunctionElement:
insertFunctionMapping = functionLoader.LoadAssociationSetModificationFunctionMapping(nav.Clone(), associationSetMapping.Set, true);
break;
}
} while (nav.MoveToNext(XPathNodeType.Element));
}
// register function mapping information
associationSetMapping.ModificationFunctionMapping = new StorageAssociationSetModificationFunctionMapping(
(AssociationSet)associationSetMapping.Set, deleteFunctionMapping, insertFunctionMapping);
}
/// <summary>
/// The method loads the child nodes for the TableMappingFragment under the EntityType node
/// into the internal datastructures.
/// </summary>
private StorageMappingFragment LoadMappingFragment(
XPathNavigator nav,
StorageEntityTypeMapping typeMapping,
string tableName,
EntityContainer storageEntityContainerType,
bool distinctFlag)
{
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, 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, 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, distinctFlag);
//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.Properties);
if (scalarMap != null)
{
//scalarMap can be null in invalid cases
fragment.AddProperty(scalarMap);
}
break;
case StorageMslConstructs.ComplexPropertyElement:
StorageComplexPropertyMapping complexMap =
LoadComplexPropertyMapping(nav.Clone(), containerType, tableType.Properties);
//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.Properties);
//conditionMap can be null in cases of invalid Map
if (conditionMap != null)
{
fragment.AddConditionProperty(conditionMap, duplicateMemberConditionError: (member) =>
{
AddToSchemaErrorsWithMemberInfo(
Strings.Mapping_InvalidContent_Duplicate_Condition_Member, member.Name,
StorageMappingErrorCode.ConditionError,
m_sourceLocation, navLineInfo, m_parsingErrors);
});
}
break;
default:
AddToSchemaErrors(Strings.Mapping_InvalidContent_General,
StorageMappingErrorCode.InvalidContent, m_sourceLocation, navLineInfo, m_parsingErrors);
break;
}
} while (nav.MoveToNext(XPathNodeType.Element));
}
nav.MoveToChild(XPathNodeType.Element);
return fragment;
}
/// <summary>
/// The method loads the child nodes for the TableMappingFragment under the AssociationType node
/// into the internal datastructures.
/// </summary>
/// <param name="nav"></param>
/// <param name="typeMapping"></param>
/// <param name="setMapping"></param>
/// <param name="tableName"></param>
/// <param name="storageEntityContainerType"></param>
/// <returns></returns>
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, 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, false /*No distinct flag*/);
//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, 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, 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, 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.Properties);
//conditionMap can be null in cases of invalid Map
if (conditionMap != null)
{
fragment.AddConditionProperty(conditionMap, duplicateMemberConditionError: (member) =>
{
AddToSchemaErrorsWithMemberInfo(
Strings.Mapping_InvalidContent_Duplicate_Condition_Member, member.Name,
StorageMappingErrorCode.ConditionError,
m_sourceLocation, navLineInfo, m_parsingErrors);
});
}
break;
case StorageMslConstructs.ModificationFunctionMappingElement:
setMapping.HasModificationFunctionMapping = true;
LoadAssociationTypeModificationFunctionMapping(nav.Clone(), setMapping, typeMapping);
break;
default:
AddToSchemaErrors(Strings.Mapping_InvalidContent_General,
StorageMappingErrorCode.InvalidContent, m_sourceLocation, navLineInfo, m_parsingErrors);
break;
}
} while (nav.MoveToNext(XPathNodeType.Element));
return fragment;
}
/// <summary>
/// The method loads the ScalarProperty mapping
/// into the internal datastructures.
/// </summary>
/// <param name="nav"></param>
/// <param name="containerType"></param>
/// <param name="tableType"></param>
/// <returns></returns>
private StorageScalarPropertyMapping LoadScalarPropertyMapping(XPathNavigator nav, EdmType containerType, ReadOnlyMetadataCollection<EdmProperty> tableProperties)
{
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, 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;
tableProperties.TryGetValue(columnName, false, out columnMember);
if (columnMember == null)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Column, 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.IsScalarType(member.TypeUsage.EdmType))
{
EdmSchemaError error = new EdmSchemaError(
Strings.Mapping_Invalid_CSide_ScalarProperty(
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;
}
/// <summary>
/// The method loads the ComplexProperty mapping into the internal datastructures.
/// </summary>
private StorageComplexPropertyMapping LoadComplexPropertyMapping(XPathNavigator nav, EdmType containerType, ReadOnlyMetadataCollection<EdmProperty> tableProperties)
{
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)
{
if (containerStructuralType != null)
{
EdmMember tempMember;
containerStructuralType.Members.TryGetValue(propertyName, false /*ignoreCase*/, out tempMember);
member = tempMember as EdmProperty;
if (member == null)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Cdm_Member, propertyName,
StorageMappingErrorCode.InvalidEdmMember, m_sourceLocation, navLineInfo, m_parsingErrors);
}
memberType = member.TypeUsage.EdmType;
}
else
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Cdm_Member, propertyName,
StorageMappingErrorCode.InvalidEdmMember, m_sourceLocation, navLineInfo, m_parsingErrors);
}
}
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, propertyName,
StorageMappingErrorCode.InvalidEdmMember, m_sourceLocation, navLineInfo, m_parsingErrors);
}
this.EdmItemCollection.TryGetItem<EdmType>(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, 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, tableProperties));
}
while (nav.MoveToNext(XPathNodeType.Element));
}
else
{
complexPropertyMapping.AddTypeMapping(LoadComplexTypeMapping(nav.Clone(), memberType, tableProperties));
}
return complexPropertyMapping;
}
private StorageComplexTypeMapping LoadComplexTypeMapping(XPathNavigator nav, EdmType type, ReadOnlyMetadataCollection<EdmProperty> 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<ComplexType>(currentTypeName, out complexType);
if (complexType == null)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_Complex_Type, 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, duplicateMemberConditionError: (member) =>
{
AddToSchemaErrorsWithMemberInfo(
Strings.Mapping_InvalidContent_Duplicate_Condition_Member, member.Name,
StorageMappingErrorCode.ConditionError,
m_sourceLocation, (IXmlLineInfo)nav, m_parsingErrors);
});
}
break;
default:
throw System.Data.Entity.Error.NotSupported();
}
} while (nav.MoveToNext(XPathNodeType.Element));
}
return typeMapping;
}
/// <summary>
/// The method loads the EndProperty mapping
/// into the internal datastructures.
/// </summary>
/// <param name="nav"></param>
/// <param name="end"></param>
/// <param name="tableType"></param>
/// <returns></returns>
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.Properties);
//Scalar Property Mapping can be null
//in case of invalid MSL files.
if (scalarMap != null)
{
//Make sure that the properties mapped as part of EndProperty maps are the key properties.
//If any other property is mapped, we should raise an error.
if (!containerType.KeyMembers.Contains(scalarMap.EdmProperty))
{
IXmlLineInfo navLineInfo = (IXmlLineInfo)nav;
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_InvalidContent_EndProperty, scalarMap.EdmProperty.Name,
StorageMappingErrorCode.InvalidEdmMember, m_sourceLocation, navLineInfo, m_parsingErrors);
return 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;
}
/// <summary>
/// The method loads the ConditionProperty mapping
/// into the internal datastructures.
/// </summary>
/// <param name="nav"></param>
/// <param name="containerType"></param>
/// <param name="tableType"></param>
/// <returns></returns>
private StorageConditionPropertyMapping LoadConditionPropertyMapping(XPathNavigator nav, EdmType containerType, ReadOnlyMetadataCollection<EdmProperty> tableProperties)
{
//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,
StorageMappingErrorCode.ConditionError, m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
if ((propertyName == null) && (columnName == null))
{
AddToSchemaErrors(Strings.Mapping_InvalidContent_ConditionMapping_Either_Members,
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)
{
tableProperties.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, ((columnName != null) ? columnName : propertyName),
StorageMappingErrorCode.ConditionError, m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
Nullable<bool> 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.DataSpace == DataSpace.SSpace)
{
cspaceTypeUsage = StoreItemCollection.StoreProviderManifest.GetEdmType(conditionMember.TypeUsage);
if (cspaceTypeUsage == null)
{
AddToSchemaErrorWithMessage(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,
conditionMember.Name, edmType.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,
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,
StorageMappingErrorCode.ConditionError, m_sourceLocation, navLineInfo, m_parsingErrors);
return null;
}
if ((isNullAttribute == null) && (value == null))
{
AddToSchemaErrors(Strings.Mapping_InvalidContent_ConditionMapping_Either_Values,
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.
XmlSchemaSet set = new XmlSchemaSet();
AddResourceXsdToSchemaSet(set, StorageMslConstructs.ResourceXsdNameV1);
AddResourceXsdToSchemaSet(set, StorageMslConstructs.ResourceXsdNameV2);
AddResourceXsdToSchemaSet(set, StorageMslConstructs.ResourceXsdNameV3);
System.Threading.Interlocked.CompareExchange(ref s_mappingXmlSchema, set, null);
}
return s_mappingXmlSchema;
}
private static void AddResourceXsdToSchemaSet(XmlSchemaSet set, string resourceName)
{
using (XmlReader xsdReader = System.Data.Common.DbProviderServices.GetXmlResource(resourceName))
{
XmlSchema xmlSchema = XmlSchema.Read(xsdReader, null);
set.Add(xmlSchema);
}
}
/// <summary>
/// Throws a new MappingException giving out the line number and
/// File Name where the error in Mapping specification is present.
/// </summary>
/// <param name="message"></param>
/// <param name="errorCode"></param>
/// <param name="uri"></param>
/// <param name="lineInfo"></param>
/// <param name="parsingErrors">Error Collection where the parsing errors are collected</param>
private static void AddToSchemaErrors(string message, StorageMappingErrorCode errorCode, string location, IXmlLineInfo lineInfo, IList<EdmSchemaError> parsingErrors)
{
EdmSchemaError error = new EdmSchemaError(message, (int)errorCode, EdmSchemaErrorSeverity.Error, location, lineInfo.LineNumber, lineInfo.LinePosition);
parsingErrors.Add(error);
}
private static EdmSchemaError AddToSchemaErrorsWithMemberInfo(Func<object, string> messageFormat, string errorMember, StorageMappingErrorCode errorCode, string location, IXmlLineInfo lineInfo, IList<EdmSchemaError> parsingErrors)
{
EdmSchemaError error = new EdmSchemaError(messageFormat(errorMember), (int)errorCode, EdmSchemaErrorSeverity.Error, location, lineInfo.LineNumber, lineInfo.LinePosition);
parsingErrors.Add(error);
return error;
}
private static void AddToSchemaErrorWithMemberAndStructure(Func<object, object, string> messageFormat, string errorMember,
string errorStructure, StorageMappingErrorCode errorCode, string location, IXmlLineInfo lineInfo, IList<EdmSchemaError> parsingErrors)
{
EdmSchemaError error = new EdmSchemaError(
messageFormat(errorMember, errorStructure)
, (int)errorCode, EdmSchemaErrorSeverity.Error, location, lineInfo.LineNumber, lineInfo.LinePosition);
parsingErrors.Add(error);
}
private static void AddToSchemaErrorWithMessage(string errorMessage, StorageMappingErrorCode errorCode, string location, IXmlLineInfo lineInfo, IList<EdmSchemaError> parsingErrors)
{
EdmSchemaError error = new EdmSchemaError(errorMessage, (int)errorCode, EdmSchemaErrorSeverity.Error, location, lineInfo.LineNumber, lineInfo.LinePosition);
parsingErrors.Add(error);
}
/// <summary>
/// Resolve the attribute value based on the aliases provided as part of MSL file.
/// </summary>
/// <param name="nav"></param>
/// <param name="attributeName"></param>
/// <returns></returns>
private string GetAliasResolvedAttributeValue(XPathNavigator nav, string attributeName)
{
return GetAliasResolvedValue(StorageMappingItemLoader.GetAttributeValue(nav, attributeName));
}
/// <summary>
///
/// </summary>
/// <param name="nav"></param>
/// <param name="attributeName"></param>
/// <returns></returns>
private bool GetBoolAttributeValue(XPathNavigator nav, string attributeName, bool defaultValue)
{
bool boolValue = defaultValue;
object boolObj = Helper.GetTypedAttributeValue(nav, attributeName, typeof(bool));
if (boolObj != null)
{
boolValue = (bool)boolObj;
}
return boolValue;
}
/// <summary>
/// The method simply calls the helper method on Helper class with the
/// namespaceURI that is default for CSMapping.
/// </summary>
/// <param name="nav"></param>
/// <param name="attributeName"></param>
/// <returns></returns>
private static string GetAttributeValue(XPathNavigator nav, string attributeName)
{
return Helper.GetAttributeValue(nav, attributeName);
}
/// <summary>
/// The method simply calls the helper method on Helper class with the
/// namespaceURI that is default for CSMapping.
/// </summary>
/// <param name="nav"></param>
/// <param name="attributeName"></param>
/// <param name="clrType"></param>
/// <param name="uri"></param>
/// <param name="parsingErrors">Error Collection where the parsing errors are collected</param>
/// <returns></returns>
private static bool TryGetTypedAttributeValue(XPathNavigator nav, string attributeName, Type clrType, string sourceLocation, IList<EdmSchemaError> parsingErrors, out object value)
{
value = null;
try
{
value = Helper.GetTypedAttributeValue(nav, attributeName, clrType);
}
catch (FormatException)
{
StorageMappingItemLoader.AddToSchemaErrors(Strings.Mapping_ConditionValueTypeMismatch,
StorageMappingErrorCode.ConditionError, sourceLocation, (IXmlLineInfo)nav, parsingErrors);
return false;
}
return true;
}
/// <summary>
/// Returns the enum EdmMember corresponding to attribute name in enumType.
/// </summary>
/// <param name="nav"></param>
/// <param name="attributeName"></param>
/// <param name="enumType"></param>
/// <param name="uri"></param>
/// <param name="parsingErrors">Error Collection where the parsing errors are collected</param>
/// <returns></returns>
private static EnumMember GetEnumAttributeValue(XPathNavigator nav, string attributeName, EnumType enumType, string sourceLocation, IList<EdmSchemaError> parsingErrors)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
string value = GetAttributeValue(nav, attributeName);
if (String.IsNullOrEmpty(value))
{
StorageMappingItemLoader.AddToSchemaErrorsWithMemberInfo(Strings.Mapping_Enum_EmptyValue, enumType.FullName,
StorageMappingErrorCode.InvalidEnumValue, sourceLocation, xmlLineInfoNav, parsingErrors);
}
EnumMember result;
bool found = enumType.Members.TryGetValue(value, false, out result);
if (!found)
{
StorageMappingItemLoader.AddToSchemaErrorsWithMemberInfo(Strings.Mapping_Enum_InvalidValue, value,
StorageMappingErrorCode.InvalidEnumValue, sourceLocation, xmlLineInfoNav, parsingErrors);
}
return result;
}
/// <summary>
/// Resolve the string value based on the aliases provided as part of MSL file.
/// </summary>
/// <param name="aliasedString"></param>
/// <returns></returns>
private string GetAliasResolvedValue(string aliasedString)
{
if ((aliasedString == null) || (aliasedString.Length == 0))
return aliasedString;
//For now all attributes have no namespace
int aliasIndex = aliasedString.LastIndexOf('.');
//If no '.' in the string, than obviously the string is not aliased
if (aliasIndex == -1)
return aliasedString;
string aliasKey = aliasedString.Substring(0, aliasIndex);
string aliasValue;
m_alias.TryGetValue(aliasKey, out aliasValue);
if (aliasValue != null)
{
aliasedString = aliasValue + aliasedString.Substring(aliasIndex);
}
return aliasedString;
}
/// <summary>
/// Creates Xml Reader with settings required for
/// XSD validation.
/// </summary>
/// <param name="innerReader"></param>
private XmlReader GetSchemaValidatingReader(XmlReader innerReader)
{
//Create the reader setting that will be used while
//loading the MSL.
XmlReaderSettings readerSettings = GetXmlReaderSettings();
XmlReader reader = XmlReader.Create(innerReader, readerSettings);
return reader;
}
private XmlReaderSettings GetXmlReaderSettings()
{
XmlReaderSettings readerSettings = System.Data.EntityModel.SchemaObjectModel.Schema.CreateEdmStandardXmlReaderSettings();
readerSettings.ValidationFlags |= System.Xml.Schema.XmlSchemaValidationFlags.ReportValidationWarnings;
readerSettings.ValidationEventHandler += this.XsdValidationCallBack;
readerSettings.ValidationType = ValidationType.Schema;
readerSettings.Schemas = GetOrCreateSchemaSet();
return readerSettings;
}
/// <summary>
/// The method is called by the XSD validation event handler when
/// ever there are warnings or errors.
/// We ignore the warnings but the errors will result in exception.
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
private void XsdValidationCallBack(object sender, ValidationEventArgs args)
{
if (args.Severity != XmlSeverityType.Warning)
{
string sourceLocation = null;
if (!string.IsNullOrEmpty(args.Exception.SourceUri))
{
sourceLocation = Helper.GetFileNameFromUri(new Uri(args.Exception.SourceUri));
}
EdmSchemaErrorSeverity severity = EdmSchemaErrorSeverity.Error;
if (args.Severity == XmlSeverityType.Warning)
severity = EdmSchemaErrorSeverity.Warning;
EdmSchemaError error = new EdmSchemaError(Strings.Mapping_InvalidMappingSchema_validation(args.Exception.Message)
, (int)StorageMappingErrorCode.XmlSchemaValidationError, severity, sourceLocation, args.Exception.LineNumber, args.Exception.LinePosition);
m_parsingErrors.Add(error);
}
}
/// <summary>
/// Validate the scalar property mapping - makes sure that the cspace type is promotable to the store side and updates
/// the store type usage
/// </summary>
/// <param name="member"></param>
/// <param name="columnMember"></param>
/// <param name="lineInfo"></param>
private void ValidateAndUpdateScalarMemberMapping(EdmProperty member, EdmProperty columnMember, IXmlLineInfo lineInfo)
{
Debug.Assert(
Helper.IsScalarType(member.TypeUsage.EdmType),
"c-space member type must be of primitive or enumeration type");
Debug.Assert(Helper.IsPrimitiveType(columnMember.TypeUsage.EdmType), "s-space column type must be primitive");
KeyValuePair<TypeUsage, TypeUsage> memberMappingInfo;
if (!m_scalarMemberMappings.TryGetValue(member, out memberMappingInfo))
{
int errorCount = m_parsingErrors.Count;
// Validates that the CSpace member type is promotable to the SSpace member types and returns a typeUsage which contains
// the store equivalent type for the CSpace member type.
// For e.g. If a CSpace member of type Edm.Int32 maps to SqlServer.Int64, the return type usage will contain SqlServer.int
// which is store equivalent type for Edm.Int32
TypeUsage storeEquivalentTypeUsage = Helper.ValidateAndConvertTypeUsage(member,
columnMember, lineInfo, m_sourceLocation, m_parsingErrors, StoreItemCollection);
// If the cspace type is not compatible with the store type, add a schema error and return
if (storeEquivalentTypeUsage == null)
{
if (errorCount == m_parsingErrors.Count)
{
EdmSchemaError error = new EdmSchemaError(
GetInvalidMemberMappingErrorMessage(member, columnMember),
(int)StorageMappingErrorCode.IncompatibleMemberMapping, EdmSchemaErrorSeverity.Error,
m_sourceLocation, lineInfo.LineNumber,
lineInfo.LinePosition);
m_parsingErrors.Add(error);
}
}
else
{
m_scalarMemberMappings.Add(member, new KeyValuePair<TypeUsage, TypeUsage>(storeEquivalentTypeUsage, columnMember.TypeUsage));
}
}
else
{
// Get the store member type to which the cspace member was mapped to previously
TypeUsage storeMappedTypeUsage = memberMappingInfo.Value;
TypeUsage modelColumnMember = columnMember.TypeUsage.GetModelTypeUsage();
if (!Object.ReferenceEquals(columnMember.TypeUsage.EdmType, storeMappedTypeUsage.EdmType))
{
EdmSchemaError error = new EdmSchemaError(
Strings.Mapping_StoreTypeMismatch_ScalarPropertyMapping(
member.Name,
storeMappedTypeUsage.EdmType.Name),
(int)StorageMappingErrorCode.CSpaceMemberMappedToMultipleSSpaceMemberWithDifferentTypes,
EdmSchemaErrorSeverity.Error,
m_sourceLocation,
lineInfo.LineNumber,
lineInfo.LinePosition);
m_parsingErrors.Add(error);
}
// Check if the cspace facets are promotable to the new store type facets
else if (!TypeSemantics.IsSubTypeOf(ResolveTypeUsageForEnums(member.TypeUsage), modelColumnMember))
{
EdmSchemaError error = new EdmSchemaError(
GetInvalidMemberMappingErrorMessage(member, columnMember),
(int)StorageMappingErrorCode.IncompatibleMemberMapping, EdmSchemaErrorSeverity.Error,
m_sourceLocation, lineInfo.LineNumber,
lineInfo.LinePosition);
m_parsingErrors.Add(error);
}
}
}
private string GetInvalidMemberMappingErrorMessage(EdmMember cSpaceMember, EdmMember sSpaceMember)
{
return Strings.Mapping_Invalid_Member_Mapping(
cSpaceMember.TypeUsage.EdmType + GetFacetsForDisplay(cSpaceMember.TypeUsage),
cSpaceMember.Name,
cSpaceMember.DeclaringType.FullName,
sSpaceMember.TypeUsage.EdmType + GetFacetsForDisplay(sSpaceMember.TypeUsage),
sSpaceMember.Name,
sSpaceMember.DeclaringType.FullName);
}
private string GetFacetsForDisplay(TypeUsage typeUsage)
{
Debug.Assert(typeUsage != null);
ReadOnlyMetadataCollection<Facet> facets = typeUsage.Facets;
if (facets == null || facets.Count == 0)
{
return string.Empty;
}
int numFacets = facets.Count;
StringBuilder facetDisplay = new StringBuilder("[");
for (int i = 0; i < numFacets-1; ++i)
{
facetDisplay.AppendFormat("{0}={1},", facets[i].Name, facets[i].Value ?? string.Empty);
}
facetDisplay.AppendFormat("{0}={1}]", facets[numFacets - 1].Name, facets[numFacets-1].Value ?? string.Empty);
return facetDisplay.ToString();
}
#endregion
#region Nested types
/// <summary>
/// Encapsulates state and functionality for loading a modification function mapping.
/// </summary>
private class ModificationFunctionMappingLoader
{
// Storage mapping loader
private readonly StorageMappingItemLoader m_parentLoader;
// Mapped function
private EdmFunction m_function;
// Entity set mapped by this function (may be null)
private readonly EntitySet m_entitySet;
// Association set mapped by this function (may be null)
private readonly AssociationSet m_associationSet;
// Model entity container (used to resolve set names)
private readonly EntityContainer m_modelContainer;
// Item collection (used to resolve function and type names)
private readonly EdmItemCollection m_edmItemCollection;
// Item collection (used to resolve function and type names)
private readonly StoreItemCollection m_storeItemCollection;
// Indicates whether the function can be bound to "current"
// versions of properties (i.e., inserts and updates)
private bool m_allowCurrentVersion;
// Indicates whether the function can be bound to "original"
// versions of properties (i.e., deletes and updates)
private bool m_allowOriginalVersion;
// Tracks which function parameters have been seen so far.
private readonly Set<FunctionParameter> m_seenParameters;
// Tracks members navigated to arrive at the current element
private readonly Stack<EdmMember> m_members;
// When set, indicates we are interpreting a navigation property on the given set.
private AssociationSet m_associationSetNavigation;
// Initialize loader
internal ModificationFunctionMappingLoader(
StorageMappingItemLoader parentLoader,
EntitySetBase extent)
{
m_parentLoader = EntityUtil.CheckArgumentNull(parentLoader, "parentLoader");
// initialize member fields
m_modelContainer = EntityUtil.CheckArgumentNull<EntitySetBase>(extent, "extent").EntityContainer;
m_edmItemCollection = parentLoader.EdmItemCollection;
m_storeItemCollection = parentLoader.StoreItemCollection;
m_entitySet = extent as EntitySet;
if (null == m_entitySet)
{
// do a cast here since the extent must either be an entity set
// or an association set
m_associationSet = (AssociationSet)extent;
}
m_seenParameters = new Set<FunctionParameter>();
m_members = new Stack<EdmMember>();
}
internal StorageModificationFunctionMapping LoadEntityTypeModificationFunctionMapping(XPathNavigator nav, EntitySetBase entitySet, bool allowCurrentVersion, bool allowOriginalVersion, EntityType entityType)
{
FunctionParameter rowsAffectedParameter;
m_function = LoadAndValidateFunctionMetadata(nav.Clone(), out rowsAffectedParameter);
if (m_function == null)
{
return null;
}
m_allowCurrentVersion = allowCurrentVersion;
m_allowOriginalVersion = allowOriginalVersion;
// Load all parameter bindings and result bindings
IEnumerable<StorageModificationFunctionParameterBinding> parameters = LoadParameterBindings(nav.Clone(), entityType);
IEnumerable<StorageModificationFunctionResultBinding> resultBindings = LoadResultBindings(nav.Clone(), entityType);
StorageModificationFunctionMapping functionMapping = new StorageModificationFunctionMapping(entitySet, entityType, m_function, parameters, rowsAffectedParameter, resultBindings);
return functionMapping;
}
// Loads a function mapping for an association set
internal StorageModificationFunctionMapping LoadAssociationSetModificationFunctionMapping(XPathNavigator nav, EntitySetBase entitySet, bool isInsert)
{
FunctionParameter rowsAffectedParameter;
m_function = LoadAndValidateFunctionMetadata(nav.Clone(), out rowsAffectedParameter);
if (m_function == null)
{
return null;
}
if (isInsert)
{
m_allowCurrentVersion = true;
m_allowOriginalVersion = false;
}
else
{
m_allowCurrentVersion = false;
m_allowOriginalVersion = true;
}
// Load all parameter bindings
IEnumerable<StorageModificationFunctionParameterBinding> parameters = LoadParameterBindings(nav.Clone(), m_associationSet.ElementType);
StorageModificationFunctionMapping mapping = new StorageModificationFunctionMapping(entitySet, entitySet.ElementType, m_function, parameters, rowsAffectedParameter, null);
return mapping;
}
// Loads all result bindings.
private IEnumerable<StorageModificationFunctionResultBinding> LoadResultBindings(XPathNavigator nav, EntityType entityType)
{
List<StorageModificationFunctionResultBinding> resultBindings = new List<StorageModificationFunctionResultBinding>();
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
// walk through all children, filtering on result bindings
if (nav.MoveToChild(XPathNodeType.Element))
{
do
{
if (nav.LocalName == StorageMslConstructs.ResultBindingElement)
{
// retrieve attributes
string propertyName = m_parentLoader.GetAliasResolvedAttributeValue(nav.Clone(),
StorageMslConstructs.ResultBindingPropertyNameAttribute);
string columnName = m_parentLoader.GetAliasResolvedAttributeValue(nav.Clone(),
StorageMslConstructs.ScalarPropertyColumnNameAttribute);
// resolve metadata
EdmProperty property = null;
if (null == propertyName ||
!entityType.Properties.TryGetValue(propertyName, false, out property))
{
// add a schema error and return if the property does not exist
StorageMappingItemLoader.AddToSchemaErrorWithMemberAndStructure(
Strings.Mapping_ModificationFunction_PropertyNotFound,
propertyName, entityType.Name,
StorageMappingErrorCode.InvalidEdmMember, m_parentLoader.m_sourceLocation,
xmlLineInfoNav, m_parentLoader.m_parsingErrors);
return new List<StorageModificationFunctionResultBinding>();
}
// construct element binding (no type checking is required at mapping load time)
StorageModificationFunctionResultBinding resultBinding = new StorageModificationFunctionResultBinding(columnName, property);
resultBindings.Add(resultBinding);
}
} while (nav.MoveToNext(XPathNodeType.Element));
}
// check for duplicate mappings of single properties
KeyToListMap<EdmProperty, string> propertyToColumnNamesMap = new KeyToListMap<EdmProperty, string>(EqualityComparer<EdmProperty>.Default);
foreach (StorageModificationFunctionResultBinding resultBinding in resultBindings)
{
propertyToColumnNamesMap.Add(resultBinding.Property, resultBinding.ColumnName);
}
foreach (EdmProperty property in propertyToColumnNamesMap.Keys)
{
ReadOnlyCollection<string> columnNames = propertyToColumnNamesMap.ListForKey(property);
if (1 < columnNames.Count)
{
StorageMappingItemLoader.AddToSchemaErrorWithMemberAndStructure(
Strings.Mapping_ModificationFunction_AmbiguousResultBinding,
property.Name, StringUtil.ToCommaSeparatedString(columnNames),
StorageMappingErrorCode.AmbiguousResultBindingInModificationFunctionMapping,
m_parentLoader.m_sourceLocation, xmlLineInfoNav,
m_parentLoader.m_parsingErrors);
return new List<StorageModificationFunctionResultBinding>();
}
}
return resultBindings;
}
// Loads parameter bindings from the given node, validating bindings:
// - All parameters are covered
// - Referenced names exist in type
// - Parameter and scalar type are compatible
// - Legal versions are given
private IEnumerable<StorageModificationFunctionParameterBinding> LoadParameterBindings(XPathNavigator nav, StructuralType type)
{
// recursively retrieve bindings (current member path is empty)
// immediately construct a list of bindings to force execution of the LoadParameterBindings
// yield method
List<StorageModificationFunctionParameterBinding> parameterBindings = new List<StorageModificationFunctionParameterBinding>(
LoadParameterBindings(nav.Clone(), type, restrictToKeyMembers: false));
// check that all parameters have been mapped
Set<FunctionParameter> unmappedParameters = new Set<FunctionParameter>(m_function.Parameters);
unmappedParameters.Subtract(m_seenParameters);
if (0 != unmappedParameters.Count)
{
AddToSchemaErrorWithMemberAndStructure(Strings.Mapping_ModificationFunction_MissingParameter,
m_function.FullName, StringUtil.ToCommaSeparatedString(unmappedParameters),
StorageMappingErrorCode.InvalidParameterInModificationFunctionMapping,
m_parentLoader.m_sourceLocation, (IXmlLineInfo)nav,
m_parentLoader.m_parsingErrors);
return new List<StorageModificationFunctionParameterBinding>();
}
return parameterBindings;
}
private IEnumerable<StorageModificationFunctionParameterBinding> LoadParameterBindings(XPathNavigator nav, StructuralType type,
bool restrictToKeyMembers)
{
// walk through all child bindings
if (nav.MoveToChild(XPathNodeType.Element))
{
do
{
switch (nav.LocalName)
{
case StorageMslConstructs.ScalarPropertyElement:
{
StorageModificationFunctionParameterBinding binding = LoadScalarPropertyParameterBinding(
nav.Clone(), type, restrictToKeyMembers);
if (binding != null)
{
yield return binding;
}
else
{
yield break;
}
}
break;
case StorageMslConstructs.ComplexPropertyElement:
{
ComplexType complexType;
EdmMember property = LoadComplexTypeProperty(
nav.Clone(), type, out complexType);
if (property != null)
{
// recursively retrieve mappings
m_members.Push(property);
foreach (StorageModificationFunctionParameterBinding binding in
LoadParameterBindings(nav.Clone(), complexType, restrictToKeyMembers))
{
yield return binding;
}
m_members.Pop();
}
}
break;
case StorageMslConstructs.AssociationEndElement:
{
AssociationSetEnd toEnd = LoadAssociationEnd(nav.Clone());
if (toEnd != null)
{
// translate the bindings for the association end
m_members.Push(toEnd.CorrespondingAssociationEndMember);
m_associationSetNavigation = toEnd.ParentAssociationSet;
foreach (StorageModificationFunctionParameterBinding binding in
LoadParameterBindings(nav.Clone(), toEnd.EntitySet.ElementType, true /* restrictToKeyMembers */))
{
yield return binding;
}
m_associationSetNavigation = null;
m_members.Pop();
}
}
break;
case StorageMslConstructs.EndPropertyMappingElement:
{
AssociationSetEnd end = LoadEndProperty(nav.Clone());
if (end != null)
{
// translate the bindings for the end property
m_members.Push(end.CorrespondingAssociationEndMember);
foreach (StorageModificationFunctionParameterBinding binding in
LoadParameterBindings(nav.Clone(), end.EntitySet.ElementType, true /* restrictToKeyMembers */))
{
yield return binding;
}
m_members.Pop();
}
}
break;
}
} while (nav.MoveToNext(XPathNodeType.Element));
}
}
private AssociationSetEnd LoadAssociationEnd(XPathNavigator nav)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
// retrieve element attributes
string associationSetName = m_parentLoader.GetAliasResolvedAttributeValue(
nav.Clone(), StorageMslConstructs.AssociationSetAttribute);
string fromRole = m_parentLoader.GetAliasResolvedAttributeValue(
nav.Clone(), StorageMslConstructs.FromAttribute);
string toRole = m_parentLoader.GetAliasResolvedAttributeValue(
nav.Clone(), StorageMslConstructs.ToAttribute);
// retrieve metadata
RelationshipSet relationshipSet = null;
AssociationSet associationSet;
// validate the association set exists
if (null == associationSetName ||
!m_modelContainer.TryGetRelationshipSetByName(associationSetName, false, out relationshipSet) ||
BuiltInTypeKind.AssociationSet != relationshipSet.BuiltInTypeKind)
{
StorageMappingItemLoader.AddToSchemaErrorsWithMemberInfo(
Strings.Mapping_ModificationFunction_AssociationSetDoesNotExist,
associationSetName, StorageMappingErrorCode.InvalidAssociationSet,
m_parentLoader.m_sourceLocation, xmlLineInfoNav,
m_parentLoader.m_parsingErrors);
return null;
}
associationSet = (AssociationSet)relationshipSet;
// validate the from end exists
AssociationSetEnd fromEnd = null;
if (null == fromRole ||
!associationSet.AssociationSetEnds.TryGetValue(fromRole, false, out fromEnd))
{
StorageMappingItemLoader.AddToSchemaErrorsWithMemberInfo(
Strings.Mapping_ModificationFunction_AssociationSetRoleDoesNotExist,
fromRole, StorageMappingErrorCode.InvalidAssociationSetRoleInModificationFunctionMapping,
m_parentLoader.m_sourceLocation, xmlLineInfoNav, m_parentLoader.m_parsingErrors);
return null;
}
// validate the to end exists
AssociationSetEnd toEnd = null;
if (null == toRole ||
!associationSet.AssociationSetEnds.TryGetValue(toRole, false, out toEnd))
{
StorageMappingItemLoader.AddToSchemaErrorsWithMemberInfo(
Strings.Mapping_ModificationFunction_AssociationSetRoleDoesNotExist,
toRole, StorageMappingErrorCode.InvalidAssociationSetRoleInModificationFunctionMapping,
m_parentLoader.m_sourceLocation, xmlLineInfoNav, m_parentLoader.m_parsingErrors);
return null;
}
// validate ends reference the current entity set
if (!fromEnd.EntitySet.Equals(m_entitySet))
{
StorageMappingItemLoader.AddToSchemaErrorsWithMemberInfo(
Strings.Mapping_ModificationFunction_AssociationSetFromRoleIsNotEntitySet,
fromRole, StorageMappingErrorCode.InvalidAssociationSetRoleInModificationFunctionMapping,
m_parentLoader.m_sourceLocation, xmlLineInfoNav, m_parentLoader.m_parsingErrors);
return null;
}
// validate cardinality of to end (can be at most one)
if (toEnd.CorrespondingAssociationEndMember.RelationshipMultiplicity != RelationshipMultiplicity.One &&
toEnd.CorrespondingAssociationEndMember.RelationshipMultiplicity != RelationshipMultiplicity.ZeroOrOne)
{
StorageMappingItemLoader.AddToSchemaErrorsWithMemberInfo(
Strings.Mapping_ModificationFunction_AssociationSetCardinality,
toRole, StorageMappingErrorCode.InvalidAssociationSetCardinalityInModificationFunctionMapping,
m_parentLoader.m_sourceLocation, xmlLineInfoNav, m_parentLoader.m_parsingErrors);
return null;
}
// if this is a FK, raise an error or a warning if the mapping would have been allowed in V1
// (all dependent properties are part of the primary key)
if (associationSet.ElementType.IsForeignKey)
{
ReferentialConstraint constraint = associationSet.ElementType.ReferentialConstraints.Single();
EdmSchemaError error = StorageMappingItemLoader.AddToSchemaErrorsWithMemberInfo(
Strings.Mapping_ModificationFunction_AssociationEndMappingForeignKeyAssociation,
toRole, StorageMappingErrorCode.InvalidModificationFunctionMappingAssociationEndForeignKey, m_parentLoader.m_sourceLocation,
xmlLineInfoNav, m_parentLoader.m_parsingErrors);
if (fromEnd.CorrespondingAssociationEndMember == constraint.ToRole &&
constraint.ToProperties.All(p => m_entitySet.ElementType.KeyMembers.Contains(p)))
{
// Just a warning...
error.Severity = EdmSchemaErrorSeverity.Warning;
}
else
{
return null;
}
}
return toEnd;
}
private AssociationSetEnd LoadEndProperty(XPathNavigator nav)
{
// retrieve element attributes
string role = m_parentLoader.GetAliasResolvedAttributeValue(
nav.Clone(), StorageMslConstructs.EndPropertyMappingNameAttribute);
// validate the role exists
AssociationSetEnd end = null;
if (null == role ||
!m_associationSet.AssociationSetEnds.TryGetValue(role, false, out end))
{
StorageMappingItemLoader.AddToSchemaErrorsWithMemberInfo(
Strings.Mapping_ModificationFunction_AssociationSetRoleDoesNotExist,
role, StorageMappingErrorCode.InvalidAssociationSetRoleInModificationFunctionMapping,
m_parentLoader.m_sourceLocation, (IXmlLineInfo)nav, m_parentLoader.m_parsingErrors);
return null;
}
return end;
}
private EdmMember LoadComplexTypeProperty(XPathNavigator nav, StructuralType type, out ComplexType complexType)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
// retrieve element attributes
string propertyName = m_parentLoader.GetAliasResolvedAttributeValue(
nav.Clone(), StorageMslConstructs.ComplexPropertyNameAttribute);
string typeName = m_parentLoader.GetAliasResolvedAttributeValue(
nav.Clone(), StorageMslConstructs.ComplexTypeMappingTypeNameAttribute);
// retrieve metadata
EdmMember property = null;
if (null == propertyName ||
!type.Members.TryGetValue(propertyName, false, out property))
{
// raise exception if the property does not exist
StorageMappingItemLoader.AddToSchemaErrorWithMemberAndStructure(
Strings.Mapping_ModificationFunction_PropertyNotFound,
propertyName, type.Name, StorageMappingErrorCode.InvalidEdmMember,
m_parentLoader.m_sourceLocation, xmlLineInfoNav, m_parentLoader.m_parsingErrors);
complexType = null;
return null;
}
complexType = null;
if (null == typeName ||
!m_edmItemCollection.TryGetItem<ComplexType>(typeName, out complexType))
{
// raise exception if the type does not exist
StorageMappingItemLoader.AddToSchemaErrorsWithMemberInfo(
Strings.Mapping_ModificationFunction_ComplexTypeNotFound,
typeName, StorageMappingErrorCode.InvalidComplexType,
m_parentLoader.m_sourceLocation, xmlLineInfoNav
, m_parentLoader.m_parsingErrors);
return null;
}
if (!property.TypeUsage.EdmType.Equals(complexType) &&
!Helper.IsSubtypeOf(property.TypeUsage.EdmType, complexType))
{
// raise exception if the complex type is incorrect
StorageMappingItemLoader.AddToSchemaErrorWithMemberAndStructure(
Strings.Mapping_ModificationFunction_WrongComplexType,
typeName, property.Name, StorageMappingErrorCode.InvalidComplexType,
m_parentLoader.m_sourceLocation, xmlLineInfoNav
, m_parentLoader.m_parsingErrors);
return null;
}
return property;
}
private StorageModificationFunctionParameterBinding LoadScalarPropertyParameterBinding(XPathNavigator nav, StructuralType type, bool restrictToKeyMembers)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
// get attribute values
string parameterName = m_parentLoader.GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.ParameterNameAttribute);
string propertyName = m_parentLoader.GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.ScalarPropertyNameAttribute);
string version = m_parentLoader.GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.ParameterVersionAttribute);
// determine version
bool isCurrent = false;
if (null == version)
{
// use default
if (!m_allowOriginalVersion)
{
isCurrent = true;
}
else if (!m_allowCurrentVersion)
{
isCurrent = false;
}
else
{
// add a schema error and return as there is no default
StorageMappingItemLoader.AddToSchemaErrors(
Strings.Mapping_ModificationFunction_MissingVersion,
StorageMappingErrorCode.MissingVersionInModificationFunctionMapping, m_parentLoader.m_sourceLocation,
xmlLineInfoNav, m_parentLoader.m_parsingErrors);
return null;
}
}
else
{
// check the value given by the user
isCurrent = version == StorageMslConstructs.ParameterVersionAttributeCurrentValue;
}
if (isCurrent && !m_allowCurrentVersion)
{
//Add a schema error and return since the 'current' property version is not available
StorageMappingItemLoader.AddToSchemaErrors(
Strings.Mapping_ModificationFunction_VersionMustBeOriginal,
StorageMappingErrorCode.InvalidVersionInModificationFunctionMapping,
m_parentLoader.m_sourceLocation, xmlLineInfoNav
, m_parentLoader.m_parsingErrors);
return null;
}
if (!isCurrent && !m_allowOriginalVersion)
{
// Add a schema error and return since the 'original' property version is not available
StorageMappingItemLoader.AddToSchemaErrors(
Strings.Mapping_ModificationFunction_VersionMustBeCurrent,
StorageMappingErrorCode.InvalidVersionInModificationFunctionMapping,
m_parentLoader.m_sourceLocation, xmlLineInfoNav
, m_parentLoader.m_parsingErrors);
return null;
}
// retrieve metadata
FunctionParameter parameter = null;
if (null == parameterName ||
!m_function.Parameters.TryGetValue(parameterName, false, out parameter))
{
//Add a schema error and return if the parameter does not exist
StorageMappingItemLoader.AddToSchemaErrorWithMemberAndStructure(
Strings.Mapping_ModificationFunction_ParameterNotFound,
parameterName, m_function.Name,
StorageMappingErrorCode.InvalidParameterInModificationFunctionMapping,
m_parentLoader.m_sourceLocation, xmlLineInfoNav
, m_parentLoader.m_parsingErrors);
return null;
}
EdmMember property = null;
if (restrictToKeyMembers)
{
if (null == propertyName ||
!((EntityType)type).KeyMembers.TryGetValue(propertyName, false, out property))
{
// raise exception if the property does not exist
StorageMappingItemLoader.AddToSchemaErrorWithMemberAndStructure(
Strings.Mapping_ModificationFunction_PropertyNotKey,
propertyName, type.Name,
StorageMappingErrorCode.InvalidEdmMember,
m_parentLoader.m_sourceLocation, xmlLineInfoNav, m_parentLoader.m_parsingErrors);
return null;
}
}
else
{
if (null == propertyName ||
!type.Members.TryGetValue(propertyName, false, out property))
{
// raise exception if the property does not exist
StorageMappingItemLoader.AddToSchemaErrorWithMemberAndStructure(
Strings.Mapping_ModificationFunction_PropertyNotFound,
propertyName, type.Name,
StorageMappingErrorCode.InvalidEdmMember,
m_parentLoader.m_sourceLocation, xmlLineInfoNav, m_parentLoader.m_parsingErrors);
return null;
}
}
// check that the parameter hasn't already been seen
if (m_seenParameters.Contains(parameter))
{
StorageMappingItemLoader.AddToSchemaErrorsWithMemberInfo(
Strings.Mapping_ModificationFunction_ParameterBoundTwice,
parameterName, StorageMappingErrorCode.ParameterBoundTwiceInModificationFunctionMapping,
m_parentLoader.m_sourceLocation, xmlLineInfoNav, m_parentLoader.m_parsingErrors);
return null;
}
int errorCount = m_parentLoader.m_parsingErrors.Count;
TypeUsage mappedStoreType = Helper.ValidateAndConvertTypeUsage(property,
xmlLineInfoNav,
m_parentLoader.m_sourceLocation,
property.TypeUsage,
parameter.TypeUsage,
m_parentLoader.m_parsingErrors,
m_storeItemCollection);
// validate type compatibility
if (mappedStoreType == null && errorCount == m_parentLoader.m_parsingErrors.Count)
{
AddToSchemaErrorWithMessage(
Strings.Mapping_ModificationFunction_PropertyParameterTypeMismatch(
property.TypeUsage.EdmType,
property.Name,
property.DeclaringType.FullName,
parameter.TypeUsage.EdmType,
parameter.Name,
m_function.FullName),
StorageMappingErrorCode.InvalidModificationFunctionMappingPropertyParameterTypeMismatch,
m_parentLoader.m_sourceLocation,
xmlLineInfoNav,
m_parentLoader.m_parsingErrors);
}
// create the binding object
m_members.Push(property);
// if the member path includes a FK relationship, remap to the corresponding FK property
IEnumerable<EdmMember> members = m_members;
AssociationSet associationSetNavigation = m_associationSetNavigation;
if (m_members.Last().BuiltInTypeKind == BuiltInTypeKind.AssociationEndMember)
{
AssociationEndMember targetEnd = (AssociationEndMember)m_members.Last();
AssociationType associationType = (AssociationType)targetEnd.DeclaringType;
if (associationType.IsForeignKey)
{
ReferentialConstraint constraint = associationType.ReferentialConstraints.Single();
if (constraint.FromRole == targetEnd)
{
int ordinal = constraint.FromProperties.IndexOf((EdmProperty)m_members.First());
// rebind to the foreign key (no longer an association set navigation)
members = new EdmMember[] { constraint.ToProperties[ordinal], };
associationSetNavigation = null;
}
}
}
StorageModificationFunctionParameterBinding binding = new StorageModificationFunctionParameterBinding(parameter, new StorageModificationFunctionMemberPath(
members, associationSetNavigation), isCurrent);
m_members.Pop();
// remember that we've seen a binding for this parameter
m_seenParameters.Add(parameter);
return binding;
}
/// <summary>
/// Loads function metadata and ensures the function is supportable for function mapping.
/// </summary>
private EdmFunction LoadAndValidateFunctionMetadata(XPathNavigator nav, out FunctionParameter rowsAffectedParameter)
{
IXmlLineInfo xmlLineInfoNav = (IXmlLineInfo)nav;
// Different operations may be mapped to the same function (e.g. both INSERT and UPDATE are handled by a single
// UPSERT function). Between loading functions, we can clear the set of seen parameters, because we may see them
// again and don't want to claim there's a collision in such cases.
m_seenParameters.Clear();
// retrieve function attributes from the current element
string functionName = m_parentLoader.GetAliasResolvedAttributeValue(nav.Clone(), StorageMslConstructs.FunctionNameAttribute);
rowsAffectedParameter = null;
// find function metadata
System.Collections.ObjectModel.ReadOnlyCollection<EdmFunction> functionOverloads =
m_storeItemCollection.GetFunctions(functionName);
if (functionOverloads.Count == 0)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_ModificationFunction_UnknownFunction, functionName,
StorageMappingErrorCode.InvalidModificationFunctionMappingUnknownFunction, m_parentLoader.m_sourceLocation,
xmlLineInfoNav, m_parentLoader.m_parsingErrors);
return null;
}
if (1 < functionOverloads.Count)
{
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_ModificationFunction_AmbiguousFunction, functionName,
StorageMappingErrorCode.InvalidModificationFunctionMappingAmbiguousFunction, m_parentLoader.m_sourceLocation,
xmlLineInfoNav, m_parentLoader.m_parsingErrors);
return null;
}
EdmFunction function = functionOverloads[0];
// check function is legal for function mapping
if (MetadataHelper.IsComposable(function))
{ // only non-composable functions are permitted
AddToSchemaErrorsWithMemberInfo(Strings.Mapping_ModificationFunction_NotValidFunction, functionName,
StorageMappingErrorCode.InvalidModificationFunctionMappingNotValidFunction, m_parentLoader.m_sourceLocation,
xmlLineInfoNav, m_parentLoader.m_parsingErrors);
return null;
}
// check for parameter
string rowsAffectedParameterName = GetAttributeValue(nav, StorageMslConstructs.RowsAffectedParameterAttribute);
if (!string.IsNullOrEmpty(rowsAffectedParameterName))
{
// check that the parameter exists
if (!function.Parameters.TryGetValue(rowsAffectedParameterName, false, out rowsAffectedParameter))
{
AddToSchemaErrorWithMessage(Strings.Mapping_FunctionImport_RowsAffectedParameterDoesNotExist(
rowsAffectedParameterName, function.FullName),
StorageMappingErrorCode.MappingFunctionImportRowsAffectedParameterDoesNotExist,
m_parentLoader.m_sourceLocation, xmlLineInfoNav, m_parentLoader.m_parsingErrors);
return null;
}
// check that the parameter is an out parameter
if (ParameterMode.Out != rowsAffectedParameter.Mode && ParameterMode.InOut != rowsAffectedParameter.Mode)
{
AddToSchemaErrorWithMessage(Strings.Mapping_FunctionImport_RowsAffectedParameterHasWrongMode(
rowsAffectedParameterName, rowsAffectedParameter.Mode, ParameterMode.Out, ParameterMode.InOut),
StorageMappingErrorCode.MappingFunctionImportRowsAffectedParameterHasWrongMode,
m_parentLoader.m_sourceLocation, xmlLineInfoNav, m_parentLoader.m_parsingErrors);
return null;
}
// check that the parameter type is an integer type
PrimitiveType rowsAffectedParameterType = (PrimitiveType)rowsAffectedParameter.TypeUsage.EdmType;
if (!TypeSemantics.IsIntegerNumericType(rowsAffectedParameter.TypeUsage))
{
AddToSchemaErrorWithMessage(Strings.Mapping_FunctionImport_RowsAffectedParameterHasWrongType(
rowsAffectedParameterName, rowsAffectedParameterType.PrimitiveTypeKind),
StorageMappingErrorCode.MappingFunctionImportRowsAffectedParameterHasWrongType,
m_parentLoader.m_sourceLocation, xmlLineInfoNav, m_parentLoader.m_parsingErrors);
return null;
}
m_seenParameters.Add(rowsAffectedParameter);
}
// check that all parameters are allowed
foreach (FunctionParameter parameter in function.Parameters)
{
if (ParameterMode.In != parameter.Mode && rowsAffectedParameterName != parameter.Name)
{ // rows affected is 'out' not 'in'
AddToSchemaErrorWithMessage(Strings.Mapping_ModificationFunction_NotValidFunctionParameter(functionName,
parameter.Name, StorageMslConstructs.RowsAffectedParameterAttribute), StorageMappingErrorCode.InvalidModificationFunctionMappingNotValidFunctionParameter,
m_parentLoader.m_sourceLocation, xmlLineInfoNav, m_parentLoader.m_parsingErrors);
return null;
}
}
return function;
}
}
#endregion
/// <summary>
/// Checks whether the <paramref name="typeUsage"/> represents a type usage for an enumeration type and if
/// this is the case creates a new type usage built using the underlying type of the enumeration type.
/// </summary>
/// <param name="typeUsage">TypeUsage to resolve.</param>
/// <returns>
/// If <paramref name="typeUsage"/> represents a TypeUsage for enumeration type the method returns a new
/// TypeUsage instance created using the underlying type of the enumeration type. Otherwise the method
/// returns <paramref name="typeUsage"/>.
/// </returns>
private static TypeUsage ResolveTypeUsageForEnums(TypeUsage typeUsage)
{
Debug.Assert(typeUsage != null, "typeUsage != null");
return Helper.IsEnumType(typeUsage.EdmType) ?
TypeUsage.Create(Helper.GetUnderlyingEdmTypeForEnumType(typeUsage.EdmType), typeUsage.Facets) :
typeUsage;
}
}
}
|