1164 lines
57 KiB
C#
1164 lines
57 KiB
C#
//---------------------------------------------------------------------
|
|
// <copyright file="EntityModelSchemaGenerator.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//
|
|
// @owner [....]
|
|
// @backupOwner [....]
|
|
//---------------------------------------------------------------------
|
|
|
|
using System.Diagnostics;
|
|
using System.Collections.Generic;
|
|
using System.Xml;
|
|
using System.Data.Common;
|
|
using System.Data.Common.Utils;
|
|
using System.Data.Metadata.Edm;
|
|
using System.Data.Entity.Design.Common;
|
|
using System.Data.Entity.Design.SsdlGenerator;
|
|
using System.Reflection;
|
|
using System.Data.Entity.Design.PluralizationServices;
|
|
using Microsoft.Build.Utilities;
|
|
using System.Runtime.Versioning;
|
|
using System.Linq;
|
|
|
|
namespace System.Data.Entity.Design
|
|
{
|
|
/// <summary>
|
|
/// The class creates a default CCMapping between an EntityContainer in S space
|
|
/// and an EntityContainer in C space. The Mapping will be created based on the
|
|
/// declared types of extents. So Inheritance does not work.
|
|
/// </summary>
|
|
public sealed class EntityModelSchemaGenerator
|
|
{
|
|
private const string ENTITY_CONTAINER_NAME_SUFFIX = "Context";
|
|
private const string NAMESPACE_NAME_SUFFIX = "Model";
|
|
private const string DEFAULT_NAMESPACE_NAME = "Application";
|
|
|
|
#region Constructors
|
|
/// <summary>
|
|
/// Constructs an EntityModelGenerator
|
|
/// </summary>
|
|
/// <param name="storeEntityContainer"></param>
|
|
public EntityModelSchemaGenerator(EntityContainer storeEntityContainer)
|
|
{
|
|
Initialize(storeEntityContainer, null, null, null);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs an EntityModelGenerator
|
|
/// </summary>
|
|
/// <param name="storeEntityContainer">The Store EntityContainer to create the Model Metadata from.</param>
|
|
/// <param name="namespaceName">The name to give the namespace. If null, the name of the storeEntityContainer will be used.</param>
|
|
/// <param name="modelEntityContainerName">The name to give the Model EntityContainer. If null, a modified version of the namespace of the of a type referenced in storeEntityContainer will be used.</param>
|
|
public EntityModelSchemaGenerator(EntityContainer storeEntityContainer, string namespaceName, string modelEntityContainerName)
|
|
{
|
|
EDesignUtil.CheckArgumentNull(namespaceName, "namespaceName");
|
|
EDesignUtil.CheckArgumentNull(modelEntityContainerName, "modelEntityContainerName");
|
|
Initialize(storeEntityContainer, null, namespaceName, modelEntityContainerName);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs an EntityModelGenerator
|
|
/// </summary>
|
|
/// <param name="storeItemCollection">The StoreItemCollection that contains an EntityContainer and other items to create the Model Metadata from.</param>
|
|
/// <param name="namespaceName">The name to give the namespace. If null, the name of the storeEntityContainer will be used.</param>
|
|
/// <param name="modelEntityContainerName">The name to give the Model EntityContainer. If null, a modified version of the namespace of the of a type referenced in storeEntityContainer will be used.</param>
|
|
[CLSCompliant(false)]
|
|
public EntityModelSchemaGenerator(StoreItemCollection storeItemCollection, string namespaceName, string modelEntityContainerName)
|
|
{
|
|
EDesignUtil.CheckArgumentNull(storeItemCollection, "storeItemCollection");
|
|
EDesignUtil.CheckArgumentNull(namespaceName, "namespaceName");
|
|
EDesignUtil.CheckArgumentNull(modelEntityContainerName, "modelEntityContainerName");
|
|
|
|
var storeContainers = storeItemCollection.GetItems<EntityContainer>().ToArray();
|
|
if (storeContainers.Length != 1)
|
|
{
|
|
throw EDesignUtil.SingleStoreEntityContainerExpected("storeItemCollection");
|
|
}
|
|
|
|
Initialize(
|
|
storeContainers[0],
|
|
storeItemCollection.GetItems<EdmFunction>().Where(f => f.IsFromProviderManifest == false &&
|
|
f.IsComposableAttribute == true &&
|
|
f.AggregateAttribute == false),
|
|
namespaceName,
|
|
modelEntityContainerName);
|
|
}
|
|
|
|
private void Initialize(EntityContainer storeEntityContainer, IEnumerable<EdmFunction> storeFunctions, string namespaceName, string modelEntityContainerName)
|
|
{
|
|
EDesignUtil.CheckArgumentNull(storeEntityContainer, "storeEntityContainer");
|
|
if (!MetadataUtil.IsStoreType(storeEntityContainer))
|
|
{
|
|
throw EDesignUtil.InvalidStoreEntityContainer(storeEntityContainer.Name, "storeEntityContainer");
|
|
}
|
|
|
|
if (namespaceName != null)
|
|
{
|
|
EDesignUtil.CheckStringArgument(namespaceName, "namespaceName");
|
|
string adjustedNamespaceName = CreateValildModelNamespaceName(namespaceName);
|
|
if (adjustedNamespaceName != namespaceName)
|
|
{
|
|
// the user gave us a bad namespace name
|
|
throw EDesignUtil.InvalidNamespaceNameArgument(namespaceName);
|
|
}
|
|
}
|
|
|
|
if (modelEntityContainerName != null)
|
|
{
|
|
EDesignUtil.CheckStringArgument(modelEntityContainerName, "modelEntityContainerName");
|
|
string adjustedEntityContainerName = CreateModelName(modelEntityContainerName);
|
|
if (adjustedEntityContainerName != modelEntityContainerName)
|
|
{
|
|
throw EDesignUtil.InvalidEntityContainerNameArgument(modelEntityContainerName);
|
|
}
|
|
if (modelEntityContainerName == storeEntityContainer.Name)
|
|
{
|
|
throw EDesignUtil.DuplicateEntityContainerName(modelEntityContainerName, storeEntityContainer.Name);
|
|
}
|
|
}
|
|
|
|
_storeEntityContainer = storeEntityContainer;
|
|
_storeFunctions = storeFunctions != null ? storeFunctions.ToArray() : null;
|
|
_namespaceName = namespaceName;
|
|
_modelEntityContainerName = modelEntityContainerName;
|
|
this._pluralizationServiceHandler = new EntityDesignPluralizationHandler(null);
|
|
|
|
SetupFields();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Fields
|
|
private EntityContainer _storeEntityContainer;
|
|
private EdmFunction[] _storeFunctions;
|
|
private EntityContainer _modelEntityContainer = null;
|
|
private OneToOneMappingSerializer.MappingLookups _mappingLookups = null;
|
|
string _namespaceName = null;
|
|
string _modelEntityContainerName = null;
|
|
private EdmItemCollection _edmItemCollection;
|
|
private EntityDesignPluralizationHandler _pluralizationServiceHandler = null;
|
|
private Version _targetEntityFrameworkVersion;
|
|
private bool _hasAnnotationNamespace;
|
|
#endregion
|
|
|
|
#region Properties
|
|
/// <summary>
|
|
/// Gets the EntityContainer that was created
|
|
/// </summary>
|
|
public EntityContainer EntityContainer
|
|
{
|
|
get
|
|
{
|
|
return _modelEntityContainer;
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Gets the EntityContainer that was created
|
|
/// </summary>
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")]
|
|
[CLSCompliant(false)]
|
|
public EdmItemCollection EdmItemCollection
|
|
{
|
|
get
|
|
{
|
|
return _edmItemCollection;
|
|
}
|
|
}
|
|
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Pluralization")]
|
|
public PluralizationService PluralizationService
|
|
{
|
|
get
|
|
{
|
|
Debug.Assert(this._pluralizationServiceHandler != null, "all constructor should call initialize() to set the handler");
|
|
return this._pluralizationServiceHandler.Service;
|
|
}
|
|
set
|
|
{
|
|
this._pluralizationServiceHandler = new EntityDesignPluralizationHandler(value);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Indicates whether foreign key properties should be exposed on entity types.
|
|
/// </summary>
|
|
public bool GenerateForeignKeyProperties
|
|
{
|
|
get;
|
|
set;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region public methods
|
|
/// <summary>
|
|
/// This method reads the s-space metadata objects and creates
|
|
/// corresponding c-space metadata objects
|
|
/// </summary>
|
|
public IList<EdmSchemaError> GenerateMetadata()
|
|
{
|
|
// default the newest version
|
|
_targetEntityFrameworkVersion = EntityFrameworkVersions.Latest;
|
|
return InternalGenerateMetadata();
|
|
}
|
|
|
|
/// <summary>
|
|
/// This method reads the s-space metadata objects and creates
|
|
/// corresponding c-space metadata objects
|
|
/// </summary>
|
|
public IList<EdmSchemaError> GenerateMetadata(Version targetEntityFrameworkVersion)
|
|
{
|
|
EDesignUtil.CheckTargetEntityFrameworkVersionArgument(targetEntityFrameworkVersion, "targetEntityFrameworkVersion");
|
|
_targetEntityFrameworkVersion = targetEntityFrameworkVersion;
|
|
return InternalGenerateMetadata();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the Schema to xml
|
|
/// </summary>
|
|
/// <param name="outputFileName">The name of the file to write the xml to.</param>
|
|
public void WriteModelSchema(string outputFileName)
|
|
{
|
|
EDesignUtil.CheckStringArgument(outputFileName, "outputFileName");
|
|
CheckValidSchema();
|
|
|
|
XmlWriterSettings settings = new XmlWriterSettings();
|
|
settings.Indent = true;
|
|
using (XmlWriter writer = XmlWriter.Create(outputFileName, settings))
|
|
{
|
|
InternalWriteModelSchema(writer);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the Schema to xml.
|
|
/// </summary>
|
|
/// <param name="writer">The XmlWriter to write the xml to.</param>
|
|
public void WriteModelSchema(XmlWriter writer)
|
|
{
|
|
EDesignUtil.CheckArgumentNull(writer, "writer");
|
|
CheckValidSchema();
|
|
InternalWriteModelSchema(writer);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the cs mapping Schema to xml
|
|
/// </summary>
|
|
/// <param name="outputFileName">The name of the file to write the xml to.</param>
|
|
public void WriteStorageMapping(string outputFileName)
|
|
{
|
|
EDesignUtil.CheckStringArgument(outputFileName, "outputFileName");
|
|
CheckValidSchema();
|
|
|
|
XmlWriterSettings settings = new XmlWriterSettings();
|
|
settings.Indent = true;
|
|
using (XmlWriter writer = XmlWriter.Create(outputFileName, settings))
|
|
{
|
|
InternalWriteStorageMapping(writer);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Writes the Schema to xml.
|
|
/// </summary>
|
|
/// <param name="writer">The XmlWriter to write the xml to.</param>
|
|
public void WriteStorageMapping(XmlWriter writer)
|
|
{
|
|
EDesignUtil.CheckArgumentNull(writer, "writer");
|
|
CheckValidSchema();
|
|
InternalWriteStorageMapping(writer);
|
|
}
|
|
#endregion
|
|
|
|
// responsible for holding all the
|
|
// state for a single execution of the Load
|
|
// method
|
|
private class LoadMethodSessionState
|
|
{
|
|
public EdmItemCollection EdmItemCollection;
|
|
public IList<EdmSchemaError> Errors = new List<EdmSchemaError>();
|
|
public UniqueIdentifierService UsedGlobalModelTypeNames = new UniqueIdentifierService(false);
|
|
public UniqueIdentifierService UsedEntityContainerItemNames = new UniqueIdentifierService(false);
|
|
public Dictionary<EdmProperty, AssociationType> FkProperties = new Dictionary<EdmProperty, AssociationType>();
|
|
public Dictionary<EntitySet, OneToOneMappingSerializer.CollapsedEntityAssociationSet> CandidateCollapsedAssociations = new Dictionary<EntitySet, OneToOneMappingSerializer.CollapsedEntityAssociationSet>();
|
|
public OneToOneMappingSerializer.MappingLookups MappingLookups = new OneToOneMappingSerializer.MappingLookups();
|
|
|
|
internal void AddError(string message, ModelBuilderErrorCode errorCode, EdmSchemaErrorSeverity severity, Exception e)
|
|
{
|
|
Debug.Assert(message != null, "message parameter is null");
|
|
if (null == e)
|
|
{
|
|
Errors.Add(new EdmSchemaError(message, (int)errorCode, severity));
|
|
}
|
|
else
|
|
{
|
|
Errors.Add(new EdmSchemaError(message, (int)errorCode, severity, e));
|
|
}
|
|
}
|
|
}
|
|
|
|
[ResourceExposure(ResourceScope.None)] //No resource is exposed.
|
|
[ResourceConsumption(ResourceScope.Machine, ResourceScope.Machine)] //For EdmItemCollection constructor call.
|
|
//Since we pass in empty collection for paths, we do not have any resource exposure here.
|
|
private IList<EdmSchemaError> InternalGenerateMetadata()
|
|
{
|
|
if (_modelEntityContainer != null)
|
|
{
|
|
_modelEntityContainer = null;
|
|
_mappingLookups = null;
|
|
_edmItemCollection = null;
|
|
}
|
|
|
|
LoadMethodSessionState session = new LoadMethodSessionState();
|
|
|
|
try
|
|
{
|
|
session.EdmItemCollection = new EdmItemCollection();
|
|
if (this.GenerateForeignKeyProperties && this._targetEntityFrameworkVersion < EntityFrameworkVersions.Version2)
|
|
{
|
|
session.AddError(Strings.UnableToGenerateForeignKeyPropertiesForV1, ModelBuilderErrorCode.UnableToGenerateForeignKeyPropertiesForV1, EdmSchemaErrorSeverity.Error, null);
|
|
return session.Errors;
|
|
}
|
|
|
|
List<AssociationSet> storeAssociationSets = new List<AssociationSet>();
|
|
CollectAllFkProperties(session);
|
|
|
|
EntityContainer modelEntityContainer = new EntityContainer(_modelEntityContainerName, DataSpace.CSpace);
|
|
|
|
// create the EntityTypes and EntitySets, and save up the AssociationSets for later.
|
|
foreach (EntitySetBase storeSet in _storeEntityContainer.BaseEntitySets)
|
|
{
|
|
switch (storeSet.BuiltInTypeKind)
|
|
{
|
|
case BuiltInTypeKind.AssociationSet:
|
|
// save these, and create them after the EntityTypes and EntitySets have been created
|
|
string errorMessage;
|
|
if (this.GenerateForeignKeyProperties || !EntityStoreSchemaGenerator.IsFkPartiallyContainedInPK(((AssociationSet)storeSet).ElementType, out errorMessage))
|
|
{
|
|
storeAssociationSets.Add((AssociationSet)storeSet);
|
|
}
|
|
else
|
|
{
|
|
session.AddError(errorMessage, ModelBuilderErrorCode.UnsupportedForeinKeyPattern, EdmSchemaErrorSeverity.Error, null);
|
|
}
|
|
break;
|
|
case BuiltInTypeKind.EntitySet:
|
|
EntitySet set = (EntitySet)storeSet;
|
|
session.CandidateCollapsedAssociations.Add(set, new OneToOneMappingSerializer.CollapsedEntityAssociationSet(set));
|
|
break;
|
|
default:
|
|
// error
|
|
throw EDesignUtil.MissingGenerationPatternForType(storeSet.BuiltInTypeKind);
|
|
}
|
|
}
|
|
|
|
foreach (AssociationSet storeAssociationSet in storeAssociationSets)
|
|
{
|
|
SaveAssociationForCollapsedAssociationCandidate(session, storeAssociationSet);
|
|
}
|
|
|
|
Set<AssociationSet> associationSetsFromCollapseCandidateRejects = new Set<AssociationSet>();
|
|
IEnumerable<OneToOneMappingSerializer.CollapsedEntityAssociationSet> invalidCandidates = FindAllInvalidCollapsedAssociationCandidates(session);
|
|
|
|
// now that we have gone through all of the association sets,
|
|
foreach (OneToOneMappingSerializer.CollapsedEntityAssociationSet collapsed in invalidCandidates)
|
|
{
|
|
session.CandidateCollapsedAssociations.Remove(collapsed.EntitySet);
|
|
|
|
// just create the entity set and save the association set to be added later
|
|
EntitySet entitySet = CreateModelEntitySet(session, collapsed.EntitySet);
|
|
modelEntityContainer.AddEntitySetBase(entitySet);
|
|
associationSetsFromCollapseCandidateRejects.AddRange(collapsed.AssociationSets);
|
|
}
|
|
|
|
// create all the associations for the invalid collapsed entity association candidates
|
|
foreach (AssociationSet storeAssociationSet in (IEnumerable<AssociationSet>)associationSetsFromCollapseCandidateRejects)
|
|
{
|
|
if (!IsAssociationPartOfCandidateCollapsedAssociation(session, storeAssociationSet))
|
|
{
|
|
AssociationSet set = CreateModelAssociationSet(session, storeAssociationSet);
|
|
modelEntityContainer.AddEntitySetBase(set);
|
|
}
|
|
}
|
|
|
|
// save the set that needs to be created and mapped
|
|
session.MappingLookups.CollapsedEntityAssociationSets.AddRange(session.CandidateCollapsedAssociations.Values);
|
|
|
|
// do this in a seperate loop so we are sure all the necessary EntitySets have been created
|
|
foreach (OneToOneMappingSerializer.CollapsedEntityAssociationSet collapsed in session.MappingLookups.CollapsedEntityAssociationSets)
|
|
{
|
|
AssociationSet set = CreateModelAssociationSet(session, collapsed);
|
|
modelEntityContainer.AddEntitySetBase(set);
|
|
}
|
|
if (this._targetEntityFrameworkVersion >= EntityFrameworkVersions.Version2)
|
|
{
|
|
Debug.Assert(EntityFrameworkVersions.Latest == EntityFrameworkVersions.Version3, "Did you add a new framework version");
|
|
// add LazyLoadingEnabled=true to the EntityContainer
|
|
MetadataProperty lazyLoadingAttribute =
|
|
new MetadataProperty(
|
|
DesignXmlConstants.EdmAnnotationNamespace + ":" + DesignXmlConstants.LazyLoadingEnabled,
|
|
TypeUsage.CreateStringTypeUsage(
|
|
PrimitiveType.GetEdmPrimitiveType(
|
|
PrimitiveTypeKind.String),
|
|
false,
|
|
false),
|
|
true);
|
|
modelEntityContainer.AddMetadataProperties(new List<MetadataProperty>() { lazyLoadingAttribute });
|
|
this._hasAnnotationNamespace = true;
|
|
}
|
|
|
|
// Map store functions to function imports.
|
|
MapFunctions(session, modelEntityContainer);
|
|
|
|
if (!EntityStoreSchemaGenerator.HasErrorSeverityErrors(session.Errors))
|
|
{
|
|
// add them to the collection so they will work if someone wants to use the collection
|
|
foreach (EntityType type in session.MappingLookups.StoreEntityTypeToModelEntityType.Values)
|
|
{
|
|
type.SetReadOnly();
|
|
session.EdmItemCollection.AddInternal(type);
|
|
}
|
|
|
|
foreach (AssociationType type in session.MappingLookups.StoreAssociationTypeToModelAssociationType.Values)
|
|
{
|
|
type.SetReadOnly();
|
|
session.EdmItemCollection.AddInternal(type);
|
|
}
|
|
|
|
foreach (OneToOneMappingSerializer.CollapsedEntityAssociationSet set in session.MappingLookups.CollapsedEntityAssociationSets)
|
|
{
|
|
set.ModelAssociationSet.ElementType.SetReadOnly();
|
|
session.EdmItemCollection.AddInternal(set.ModelAssociationSet.ElementType);
|
|
}
|
|
modelEntityContainer.SetReadOnly();
|
|
session.EdmItemCollection.AddInternal(modelEntityContainer);
|
|
|
|
_modelEntityContainer = modelEntityContainer;
|
|
_mappingLookups = session.MappingLookups;
|
|
_edmItemCollection = session.EdmItemCollection;
|
|
}
|
|
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
if (MetadataUtil.IsCatchableExceptionType(e))
|
|
{
|
|
// an exception in the code is definitely an error
|
|
string message = EDesignUtil.GetMessagesFromEntireExceptionChain(e);
|
|
session.AddError(message,
|
|
ModelBuilderErrorCode.UnknownError,
|
|
EdmSchemaErrorSeverity.Error,
|
|
e);
|
|
}
|
|
else
|
|
{
|
|
throw;
|
|
}
|
|
|
|
}
|
|
return session.Errors;
|
|
}
|
|
|
|
private void InternalWriteModelSchema(XmlWriter writer)
|
|
{
|
|
Debug.Assert(writer != null, "writer != null");
|
|
Debug.Assert(_edmItemCollection != null, "_edmItemCollection != null");
|
|
Debug.Assert(_namespaceName != null, "_namespaceName != null");
|
|
Debug.Assert(_targetEntityFrameworkVersion != null, "_targetEntityFrameworkVersion != null");
|
|
|
|
if (this._hasAnnotationNamespace)
|
|
{
|
|
KeyValuePair<string, string> namespacesPrefix = new KeyValuePair<string, string>(DesignXmlConstants.AnnotationPrefix, DesignXmlConstants.EdmAnnotationNamespace);
|
|
MetadataItemSerializer.WriteXml(writer, _edmItemCollection, _namespaceName, _targetEntityFrameworkVersion, namespacesPrefix);
|
|
}
|
|
else
|
|
{
|
|
MetadataItemSerializer.WriteXml(writer, _edmItemCollection, _namespaceName, _targetEntityFrameworkVersion);
|
|
}
|
|
}
|
|
|
|
private void InternalWriteStorageMapping(XmlWriter writer)
|
|
{
|
|
Debug.Assert(writer != null, "writer != null");
|
|
Debug.Assert(_mappingLookups != null, "_mappingLookups != null");
|
|
Debug.Assert(_storeEntityContainer != null, "_storeEntityContainer != null");
|
|
Debug.Assert(_modelEntityContainer != null, "_modelEntityContainer != null");
|
|
Debug.Assert(_targetEntityFrameworkVersion != null, "_targetEntityFrameworkVersion != null");
|
|
OneToOneMappingSerializer serializer = new OneToOneMappingSerializer(_mappingLookups, _storeEntityContainer, _modelEntityContainer, _targetEntityFrameworkVersion);
|
|
serializer.WriteXml(writer);
|
|
}
|
|
|
|
private void MapFunctions(LoadMethodSessionState session, EntityContainer modelEntityContainer)
|
|
{
|
|
if (_storeFunctions == null || _storeFunctions.Length == 0 || _targetEntityFrameworkVersion < EntityFrameworkVersions.Version3)
|
|
{
|
|
return;
|
|
}
|
|
|
|
//
|
|
// Create function imports and appropriate complex types for return parameters and add them to the item collection (session.EdmItemCollection).
|
|
// Create and add function mappings.
|
|
//
|
|
|
|
var interestingStoreFunctions = _storeFunctions.Where(
|
|
f => f.IsComposableAttribute &&
|
|
!f.AggregateAttribute &&
|
|
f.Parameters.All(p => p.Mode == ParameterMode.In));
|
|
foreach (var storeFunction in interestingStoreFunctions)
|
|
{
|
|
RowType tvfReturnType = TypeHelpers.GetTvfReturnType(storeFunction);
|
|
if (tvfReturnType == null)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Create function import name.
|
|
string functionImportName = CreateModelName(storeFunction.Name, session.UsedEntityContainerItemNames);
|
|
|
|
// Create function import parameters.
|
|
UniqueIdentifierService usedParameterNames = new UniqueIdentifierService(false);
|
|
var parameters = storeFunction.Parameters.Select(p => CreateFunctionImportParameter(p, usedParameterNames)).ToArray();
|
|
var failedStoreParameterName = storeFunction.Parameters.Select(p => p.Name).Except(parameters.Select(p => p.Name)).FirstOrDefault();
|
|
if (failedStoreParameterName != null)
|
|
{
|
|
session.AddError(Strings.UnableToGenerateFunctionImportParameterName(failedStoreParameterName, storeFunction.Identity),
|
|
ModelBuilderErrorCode.UnableToGenerateFunctionImportParameterName, EdmSchemaErrorSeverity.Warning, null);
|
|
continue;
|
|
}
|
|
|
|
// Create new complex type and register it in the item collection.
|
|
var complexType = CreateModelComplexTypeForTvfResult(session, functionImportName, tvfReturnType);
|
|
complexType.SetReadOnly();
|
|
session.EdmItemCollection.AddInternal(complexType);
|
|
|
|
var collectionType = complexType.GetCollectionType();
|
|
collectionType.SetReadOnly();
|
|
var returnTypeUsage = TypeUsage.Create(collectionType);
|
|
|
|
// Create function import and register it in the item collection.
|
|
var functionImport = new EdmFunction(functionImportName, _modelEntityContainerName, DataSpace.CSpace, new EdmFunctionPayload()
|
|
{
|
|
Name = functionImportName,
|
|
NamespaceName = _namespaceName,
|
|
ReturnParameters = new FunctionParameter[] {new FunctionParameter(EdmConstants.ReturnType, returnTypeUsage, ParameterMode.ReturnValue)},
|
|
Parameters = parameters,
|
|
DataSpace = DataSpace.CSpace,
|
|
IsComposable = true,
|
|
IsFunctionImport = true
|
|
});
|
|
functionImport.SetReadOnly();
|
|
modelEntityContainer.AddFunctionImport(functionImport);
|
|
|
|
// Add mapping tuple.
|
|
session.MappingLookups.StoreFunctionToFunctionImport.Add(Tuple.Create(storeFunction, functionImport));
|
|
}
|
|
}
|
|
|
|
private FunctionParameter CreateFunctionImportParameter(FunctionParameter storeParameter, UniqueIdentifierService usedParameterNames)
|
|
{
|
|
Debug.Assert(storeParameter.Mode == ParameterMode.In, "Function import mapping is supported only for 'In' parameters.");
|
|
string name = CreateModelName(storeParameter.Name, usedParameterNames);
|
|
TypeUsage cspaceTypeUsage = storeParameter.TypeUsage.GetModelTypeUsage();
|
|
var modelParameter = new FunctionParameter(name, cspaceTypeUsage, storeParameter.Mode);
|
|
return modelParameter;
|
|
}
|
|
|
|
private ComplexType CreateModelComplexTypeForTvfResult(LoadMethodSessionState session, string functionImportName, RowType tvfReturnType)
|
|
{
|
|
Debug.Assert(MetadataUtil.IsStoreType(tvfReturnType), "this is not a store type");
|
|
|
|
// create all the properties
|
|
List<EdmMember> members = new List<EdmMember>();
|
|
UniqueIdentifierService usedPropertyNames = new UniqueIdentifierService(false);
|
|
|
|
string name = CreateModelName(functionImportName + "_Result", session.UsedGlobalModelTypeNames);
|
|
|
|
// Don't want to have a property with the same name as the complex type
|
|
usedPropertyNames.RegisterUsedIdentifier(name);
|
|
foreach (EdmProperty storeProperty in tvfReturnType.Properties)
|
|
{
|
|
EdmProperty property = CreateModelProperty(session, storeProperty, usedPropertyNames);
|
|
members.Add(property);
|
|
}
|
|
|
|
var complexType = new ComplexType(name, _namespaceName, DataSpace.CSpace);
|
|
foreach (var m in members)
|
|
{
|
|
complexType.AddMember(m);
|
|
}
|
|
return complexType;
|
|
}
|
|
|
|
private IEnumerable<OneToOneMappingSerializer.CollapsedEntityAssociationSet> FindAllInvalidCollapsedAssociationCandidates(LoadMethodSessionState session)
|
|
{
|
|
Set<OneToOneMappingSerializer.CollapsedEntityAssociationSet> invalid = new Set<OneToOneMappingSerializer.CollapsedEntityAssociationSet>();
|
|
Dictionary<EntitySet, OneToOneMappingSerializer.CollapsedEntityAssociationSet> newCandidates = new Dictionary<EntitySet,OneToOneMappingSerializer.CollapsedEntityAssociationSet>();
|
|
foreach (OneToOneMappingSerializer.CollapsedEntityAssociationSet collapsed in session.CandidateCollapsedAssociations.Values)
|
|
{
|
|
if (!collapsed.MeetsRequirementsForCollapsableAssociation)
|
|
{
|
|
invalid.Add(collapsed);
|
|
}
|
|
else
|
|
{
|
|
newCandidates.Add(collapsed.EntitySet, collapsed);
|
|
}
|
|
}
|
|
|
|
|
|
foreach (OneToOneMappingSerializer.CollapsedEntityAssociationSet collapsed in newCandidates.Values)
|
|
{
|
|
foreach (AssociationSet set in collapsed.AssociationSets)
|
|
{
|
|
EntitySet end0Set = set.AssociationSetEnds[0].EntitySet;
|
|
EntitySet end1Set = set.AssociationSetEnds[1].EntitySet;
|
|
|
|
// if both ends of the association are candidates throw both candidates out
|
|
// because we can't collapse two entities out
|
|
// and we don't know which entity we should collapse
|
|
// so we won't collapse either
|
|
if (newCandidates.ContainsKey(end0Set) &&
|
|
newCandidates.ContainsKey(end1Set))
|
|
{
|
|
OneToOneMappingSerializer.CollapsedEntityAssociationSet collapsed0 = session.CandidateCollapsedAssociations[end0Set];
|
|
OneToOneMappingSerializer.CollapsedEntityAssociationSet collapsed1 = session.CandidateCollapsedAssociations[end1Set];
|
|
invalid.Add(collapsed0);
|
|
invalid.Add(collapsed1);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return invalid;
|
|
}
|
|
|
|
private bool IsAssociationPartOfCandidateCollapsedAssociation(LoadMethodSessionState session, AssociationSet storeAssociationSet)
|
|
{
|
|
foreach (AssociationSetEnd end in storeAssociationSet.AssociationSetEnds)
|
|
{
|
|
if (session.CandidateCollapsedAssociations.ContainsKey(end.EntitySet))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
private void SaveAssociationForCollapsedAssociationCandidate(LoadMethodSessionState session, AssociationSet storeAssociationSet)
|
|
{
|
|
foreach (AssociationSetEnd end in storeAssociationSet.AssociationSetEnds)
|
|
{
|
|
OneToOneMappingSerializer.CollapsedEntityAssociationSet collapsed;
|
|
if (session.CandidateCollapsedAssociations.TryGetValue(end.EntitySet, out collapsed))
|
|
{
|
|
collapsed.AssociationSets.Add(storeAssociationSet);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CollectAllFkProperties(LoadMethodSessionState session)
|
|
{
|
|
foreach (EntitySetBase storeSet in _storeEntityContainer.BaseEntitySets)
|
|
{
|
|
if (storeSet.BuiltInTypeKind == BuiltInTypeKind.AssociationSet)
|
|
{
|
|
ReferentialConstraint constraint = OneToOneMappingSerializer.GetReferentialConstraint(((AssociationSet)storeSet));
|
|
foreach (EdmProperty property in constraint.ToProperties)
|
|
{
|
|
if (!session.FkProperties.ContainsKey(property))
|
|
{
|
|
session.FkProperties.Add(property, ((AssociationSet)storeSet).ElementType);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get ModelSchemaNamespace name
|
|
private void SetupFields()
|
|
{
|
|
if( _modelEntityContainerName != null && _namespaceName != null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
string targetSchemaNamespace = null;
|
|
|
|
//Try and get the target schema namespace from one of the types or functions defined in the schema
|
|
foreach(EntitySetBase type in _storeEntityContainer.BaseEntitySets)
|
|
{
|
|
targetSchemaNamespace = type.ElementType.NamespaceName;
|
|
break;
|
|
}
|
|
|
|
|
|
if (string.IsNullOrEmpty(targetSchemaNamespace))
|
|
{
|
|
// the default
|
|
targetSchemaNamespace = DEFAULT_NAMESPACE_NAME;
|
|
}
|
|
|
|
|
|
if(_namespaceName == null)
|
|
{
|
|
// if the schema namespace has 'Target' then replace it with '.Model
|
|
int index = targetSchemaNamespace.IndexOf("Target", StringComparison.OrdinalIgnoreCase);
|
|
if (index > 0)
|
|
{
|
|
_namespaceName = CreateValildModelNamespaceName(targetSchemaNamespace.Substring(0, index) + NAMESPACE_NAME_SUFFIX);
|
|
}
|
|
else
|
|
{
|
|
// else just append .Model to the name
|
|
_namespaceName = CreateValildModelNamespaceName(targetSchemaNamespace + NAMESPACE_NAME_SUFFIX);
|
|
}
|
|
}
|
|
|
|
|
|
if(_modelEntityContainerName == null)
|
|
{
|
|
// get default container name
|
|
int dotIndex = targetSchemaNamespace.IndexOf('.');
|
|
if (dotIndex > 0)
|
|
{
|
|
_modelEntityContainerName = CreateModelName(targetSchemaNamespace.Substring(0, dotIndex) + ENTITY_CONTAINER_NAME_SUFFIX);
|
|
}
|
|
else
|
|
{
|
|
_modelEntityContainerName = CreateModelName(targetSchemaNamespace + ENTITY_CONTAINER_NAME_SUFFIX);
|
|
}
|
|
|
|
int targetIndex = _modelEntityContainerName.IndexOf("Target", StringComparison.OrdinalIgnoreCase);
|
|
if (targetIndex > 0)
|
|
{
|
|
_modelEntityContainerName = CreateModelName(targetSchemaNamespace.Substring(0, targetIndex) + ENTITY_CONTAINER_NAME_SUFFIX);
|
|
}
|
|
}
|
|
}
|
|
|
|
private void CheckValidSchema()
|
|
{
|
|
if (_modelEntityContainer == null)
|
|
{
|
|
throw EDesignUtil.EntityModelGeneratorSchemaNotLoaded();
|
|
}
|
|
}
|
|
|
|
private EntitySet CreateModelEntitySet(LoadMethodSessionState session, EntitySet storeEntitySet)
|
|
{
|
|
EntityType entity = CreateModelEntityType(session, storeEntitySet.ElementType);
|
|
|
|
string name = CreateModelName(this._pluralizationServiceHandler.GetEntitySetName(storeEntitySet.Name), session.UsedEntityContainerItemNames);
|
|
|
|
EntitySet set = new EntitySet(name, null, null, null, entity);
|
|
session.MappingLookups.StoreEntitySetToModelEntitySet.Add(storeEntitySet, set);
|
|
return set;
|
|
}
|
|
|
|
private EntityType CreateModelEntityType(LoadMethodSessionState session, EntityType storeEntityType)
|
|
{
|
|
Debug.Assert(MetadataUtil.IsStoreType(storeEntityType), "this is not a store type");
|
|
Debug.Assert(storeEntityType.BaseType == null, "we are assuming simple generation from a database where no types will have a base type");
|
|
|
|
EntityType foundEntity;
|
|
if (session.MappingLookups.StoreEntityTypeToModelEntityType.TryGetValue(storeEntityType, out foundEntity))
|
|
{
|
|
// this entity type is used in two different entity sets
|
|
return foundEntity;
|
|
}
|
|
|
|
// create all the properties
|
|
List<EdmMember> members = new List<EdmMember>();
|
|
List<String> keyMemberNames = new List<string>();
|
|
UniqueIdentifierService usedPropertyNames = new UniqueIdentifierService(false);
|
|
|
|
string name = CreateModelName(this._pluralizationServiceHandler.GetEntityTypeName(storeEntityType.Name), session.UsedGlobalModelTypeNames);
|
|
|
|
// Don't want to have a property with the same name as the entity type
|
|
usedPropertyNames.RegisterUsedIdentifier(name);
|
|
foreach (EdmProperty storeProperty in storeEntityType.Properties)
|
|
{
|
|
// add fk properties only if requested
|
|
EdmMember member;
|
|
bool isKey = storeEntityType.KeyMembers.TryGetValue(storeProperty.Name, false, out member);
|
|
|
|
AssociationType association;
|
|
if (isKey || this.GenerateForeignKeyProperties || !session.FkProperties.TryGetValue(storeProperty, out association))
|
|
{
|
|
EdmProperty property = CreateModelProperty(session, storeProperty, usedPropertyNames);
|
|
members.Add(property);
|
|
if (isKey)
|
|
{
|
|
keyMemberNames.Add(property.Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
var entityType = new EntityType(name, _namespaceName, DataSpace.CSpace, keyMemberNames, members);
|
|
session.MappingLookups.StoreEntityTypeToModelEntityType.Add(storeEntityType, entityType);
|
|
return entityType;
|
|
}
|
|
|
|
private EdmProperty CreateModelProperty(LoadMethodSessionState session, EdmProperty storeProperty, UniqueIdentifierService usedPropertyNames)
|
|
{
|
|
string name = CreateModelName(storeProperty.Name, usedPropertyNames);
|
|
TypeUsage cspaceTypeUsage = storeProperty.TypeUsage.GetModelTypeUsage();
|
|
|
|
EdmProperty property = new EdmProperty(name, cspaceTypeUsage);
|
|
|
|
this.AddStoreGeneratedPatternAnnoation(property, storeProperty.TypeUsage);
|
|
|
|
session.MappingLookups.StoreEdmPropertyToModelEdmProperty.Add(storeProperty, property);
|
|
return property;
|
|
}
|
|
|
|
private void AddStoreGeneratedPatternAnnoation(EdmProperty cSpaceProperty, TypeUsage storeTypeUsage)
|
|
{
|
|
if (storeTypeUsage.Facets.Contains(DesignXmlConstants.StoreGeneratedPattern))
|
|
{
|
|
List<MetadataProperty> annotation = new List<MetadataProperty>();
|
|
annotation.Add(
|
|
new MetadataProperty(
|
|
DesignXmlConstants.EdmAnnotationNamespace + ":" + DesignXmlConstants.StoreGeneratedPattern,
|
|
TypeUsage.CreateStringTypeUsage(
|
|
PrimitiveType.GetEdmPrimitiveType(PrimitiveTypeKind.String),
|
|
false/*isUnicode*/, false/*isFixLength*/),
|
|
storeTypeUsage.Facets.GetValue(DesignXmlConstants.StoreGeneratedPattern, false).Value));
|
|
|
|
cSpaceProperty.AddMetadataProperties(annotation);
|
|
this._hasAnnotationNamespace = true;
|
|
}
|
|
}
|
|
|
|
private AssociationSet CreateModelAssociationSet(LoadMethodSessionState session, OneToOneMappingSerializer.CollapsedEntityAssociationSet collapsedAssociationSet)
|
|
{
|
|
// create the association
|
|
string associationName = CreateModelName(collapsedAssociationSet.EntitySet.Name, session.UsedGlobalModelTypeNames);
|
|
AssociationType association = new AssociationType(associationName,
|
|
_namespaceName, false, DataSpace.CSpace);
|
|
|
|
// create the association set
|
|
string associationSetName = CreateModelName(collapsedAssociationSet.EntitySet.Name, session.UsedEntityContainerItemNames);
|
|
AssociationSet set = new AssociationSet(associationSetName, association);
|
|
|
|
// create the association and association set end members
|
|
UniqueIdentifierService usedEndMemberNames = new UniqueIdentifierService(false);
|
|
for(int i = 0; i < collapsedAssociationSet.AssociationSets.Count; i++)
|
|
{
|
|
AssociationSetEnd storeEnd;
|
|
RelationshipMultiplicity multiplicity;
|
|
OperationAction deleteBehavior;
|
|
collapsedAssociationSet.GetStoreAssociationSetEnd(i, out storeEnd, out multiplicity, out deleteBehavior);
|
|
AssociationEndMember end = CreateAssociationEndMember(session, storeEnd.CorrespondingAssociationEndMember, usedEndMemberNames, multiplicity, deleteBehavior);
|
|
association.AddMember(end);
|
|
|
|
EntitySet entitySet = session.MappingLookups.StoreEntitySetToModelEntitySet[storeEnd.EntitySet];
|
|
AssociationSetEnd setEnd = new AssociationSetEnd(entitySet, set, end);
|
|
set.AddAssociationSetEnd(setEnd);
|
|
session.MappingLookups.StoreAssociationSetEndToModelAssociationSetEnd.Add(storeEnd, setEnd);
|
|
}
|
|
|
|
// don't need a referential constraint
|
|
|
|
CreateModelNavigationProperties(session, association);
|
|
|
|
collapsedAssociationSet.ModelAssociationSet = set;
|
|
|
|
return set;
|
|
}
|
|
|
|
private AssociationSet CreateModelAssociationSet(LoadMethodSessionState session, AssociationSet storeAssociationSet)
|
|
{
|
|
|
|
AssociationType association;
|
|
// we will get a value when the same association is used for multiple association sets
|
|
if (! session.MappingLookups.StoreAssociationTypeToModelAssociationType.TryGetValue(storeAssociationSet.ElementType, out association))
|
|
{
|
|
association = CreateModelAssociationType(session, storeAssociationSet.ElementType);
|
|
session.MappingLookups.StoreAssociationTypeToModelAssociationType.Add(storeAssociationSet.ElementType, association);
|
|
}
|
|
|
|
string name = CreateModelName(storeAssociationSet.Name, session.UsedEntityContainerItemNames);
|
|
AssociationSet set = new AssociationSet(name, association);
|
|
|
|
foreach(AssociationSetEnd storeEnd in storeAssociationSet.AssociationSetEnds)
|
|
{
|
|
AssociationSetEnd end = CreateModelAssociationSetEnd(session, storeEnd, set);
|
|
session.MappingLookups.StoreAssociationSetEndToModelAssociationSetEnd.Add(storeEnd, end);
|
|
set.AddAssociationSetEnd(end);
|
|
}
|
|
session.MappingLookups.StoreAssociationSetToModelAssociationSet.Add(storeAssociationSet, set);
|
|
return set;
|
|
}
|
|
|
|
private AssociationSetEnd CreateModelAssociationSetEnd(LoadMethodSessionState session, AssociationSetEnd storeEnd, AssociationSet parentModelAssociationSet)
|
|
{
|
|
AssociationEndMember associationEnd = session.MappingLookups.StoreAssociationEndMemberToModelAssociationEndMember[storeEnd.CorrespondingAssociationEndMember];
|
|
EntitySet entitySet = session.MappingLookups.StoreEntitySetToModelEntitySet[storeEnd.EntitySet];
|
|
string role = associationEnd.Name;
|
|
AssociationSetEnd end = new AssociationSetEnd(entitySet, parentModelAssociationSet, associationEnd);
|
|
return end;
|
|
}
|
|
|
|
private AssociationType CreateModelAssociationType(LoadMethodSessionState session, AssociationType storeAssociationType)
|
|
{
|
|
UniqueIdentifierService usedEndMemberNames = new UniqueIdentifierService(false);
|
|
string name = CreateModelName(storeAssociationType.Name, session.UsedGlobalModelTypeNames);
|
|
|
|
bool isFkAssociation = false;
|
|
if (_targetEntityFrameworkVersion > EntityFrameworkVersions.Version1)
|
|
{
|
|
isFkAssociation = this.GenerateForeignKeyProperties || RequiresModelReferentialConstraint(storeAssociationType);
|
|
}
|
|
AssociationType association = new AssociationType(name,
|
|
_namespaceName,
|
|
isFkAssociation,
|
|
DataSpace.CSpace);
|
|
KeyValuePair<string, RelationshipMultiplicity> endMultiplicityOverride = CreateEndMultiplicityOverride(session, storeAssociationType, association);
|
|
|
|
foreach (AssociationEndMember storeEndMember in storeAssociationType.RelationshipEndMembers)
|
|
{
|
|
AssociationEndMember end = CreateAssociationEndMember(session, storeEndMember, endMultiplicityOverride, usedEndMemberNames);
|
|
session.MappingLookups.StoreAssociationEndMemberToModelAssociationEndMember.Add(storeEndMember, end);
|
|
association.AddMember(end);
|
|
}
|
|
|
|
ReferentialConstraint constraint = CreateReferentialConstraint(session, storeAssociationType);
|
|
if (constraint != null)
|
|
{
|
|
association.AddReferentialConstraint(constraint);
|
|
}
|
|
|
|
CreateModelNavigationProperties(session, association);
|
|
|
|
return association;
|
|
}
|
|
|
|
private KeyValuePair<string, RelationshipMultiplicity> CreateEndMultiplicityOverride(LoadMethodSessionState session, AssociationType storeAssociation, AssociationType modelAssociation)
|
|
{
|
|
// does the store have any constraints
|
|
if (storeAssociation.ReferentialConstraints.Count == 0)
|
|
{
|
|
return new KeyValuePair<string, RelationshipMultiplicity>();
|
|
}
|
|
|
|
ReferentialConstraint storeConstraint = storeAssociation.ReferentialConstraints[0];
|
|
|
|
//For foreign key associations, having any nullable columns will imply 0..1
|
|
//multiplicity, while for independent associations, all columns must be non-nullable for
|
|
//0..1 association.
|
|
bool nullableColumnsImplyingOneToOneMultiplicity = false;
|
|
if (this.GenerateForeignKeyProperties)
|
|
{
|
|
nullableColumnsImplyingOneToOneMultiplicity = storeConstraint.ToProperties.All(p => p.Nullable == false);
|
|
}
|
|
else
|
|
{
|
|
nullableColumnsImplyingOneToOneMultiplicity = storeConstraint.ToProperties.Any(p => p.Nullable == false);
|
|
}
|
|
|
|
if (storeConstraint.FromRole.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne &&
|
|
storeConstraint.ToRole.RelationshipMultiplicity == RelationshipMultiplicity.Many &&
|
|
nullableColumnsImplyingOneToOneMultiplicity)
|
|
{
|
|
return new KeyValuePair<string, RelationshipMultiplicity>(storeConstraint.FromRole.Name, RelationshipMultiplicity.One);
|
|
}
|
|
|
|
return new KeyValuePair<string, RelationshipMultiplicity>();
|
|
}
|
|
|
|
private ReferentialConstraint CreateReferentialConstraint(LoadMethodSessionState session, AssociationType storeAssociation)
|
|
{
|
|
Debug.Assert(session != null, "session parameter is null");
|
|
Debug.Assert(storeAssociation != null, "storeAssociation parameter is null");
|
|
Debug.Assert(storeAssociation.ReferentialConstraints.Count <= 1, "We don't have a reason to have more than one constraint yet");
|
|
|
|
// does the store have any constraints
|
|
if (storeAssociation.ReferentialConstraints.Count == 0)
|
|
{
|
|
return null;
|
|
}
|
|
|
|
ReferentialConstraint storeConstraint = storeAssociation.ReferentialConstraints[0];
|
|
Debug.Assert(storeConstraint.FromProperties.Count == storeConstraint.ToProperties.Count, "FromProperties and ToProperties have different counts");
|
|
Debug.Assert(storeConstraint.FromProperties.Count != 0, "No properties in the constraint, why does the constraint exist?");
|
|
Debug.Assert(storeConstraint.ToProperties[0].DeclaringType.BuiltInTypeKind == BuiltInTypeKind.EntityType, "The property is not from an EntityType");
|
|
|
|
EntityType toType = (EntityType)storeConstraint.ToProperties[0].DeclaringType;
|
|
// If we are generating foreign keys, there is always a referential constraint. Otherwise, check
|
|
// if the dependent end includes key properties. If so, this implies that there is a referential
|
|
// constraint. Otherwise, it is assumed that the foreign key properties are not defined in the
|
|
// entity (verified ealier).
|
|
if (!this.GenerateForeignKeyProperties && !RequiresModelReferentialConstraint(storeConstraint, toType))
|
|
{
|
|
return null;
|
|
}
|
|
// we need a constraint so lets build it
|
|
int count = storeConstraint.FromProperties.Count;
|
|
EdmProperty[] fromProperties = new EdmProperty[count];
|
|
EdmProperty[] toProperties = new EdmProperty[count];
|
|
AssociationEndMember fromRole = session.MappingLookups.StoreAssociationEndMemberToModelAssociationEndMember[(AssociationEndMember)storeConstraint.FromRole];
|
|
AssociationEndMember toRole = session.MappingLookups.StoreAssociationEndMemberToModelAssociationEndMember[(AssociationEndMember)storeConstraint.ToRole];
|
|
for (int index = 0; index < count; index++)
|
|
{
|
|
fromProperties[index] = session.MappingLookups.StoreEdmPropertyToModelEdmProperty[storeConstraint.FromProperties[index]];
|
|
toProperties[index] = session.MappingLookups.StoreEdmPropertyToModelEdmProperty[storeConstraint.ToProperties[index]];
|
|
}
|
|
|
|
ReferentialConstraint newConstraint = new ReferentialConstraint(
|
|
fromRole,
|
|
toRole,
|
|
fromProperties,
|
|
toProperties);
|
|
|
|
return newConstraint;
|
|
}
|
|
|
|
private static bool RequiresModelReferentialConstraint(AssociationType storeAssociation)
|
|
{
|
|
// does the store have any constraints
|
|
if (storeAssociation.ReferentialConstraints.Count == 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
ReferentialConstraint storeConstraint = storeAssociation.ReferentialConstraints[0];
|
|
Debug.Assert(storeConstraint.FromProperties.Count == storeConstraint.ToProperties.Count, "FromProperties and ToProperties have different counts");
|
|
Debug.Assert(storeConstraint.FromProperties.Count != 0, "No properties in the constraint, why does the constraint exist?");
|
|
Debug.Assert(storeConstraint.ToProperties[0].DeclaringType.BuiltInTypeKind == BuiltInTypeKind.EntityType, "The property is not from an EntityType");
|
|
|
|
EntityType toType = (EntityType)storeConstraint.ToProperties[0].DeclaringType;
|
|
return RequiresModelReferentialConstraint(storeConstraint, toType);
|
|
}
|
|
|
|
private static bool RequiresModelReferentialConstraint(ReferentialConstraint storeConstraint, EntityType toType)
|
|
{
|
|
return toType.KeyMembers.Contains(storeConstraint.ToProperties[0]);
|
|
}
|
|
|
|
private void CreateModelNavigationProperties(LoadMethodSessionState session, AssociationType association)
|
|
{
|
|
Debug.Assert(association.Members.Count == 2, "this code assumes two ends");
|
|
AssociationEndMember end1 = (AssociationEndMember)association.Members[0];
|
|
AssociationEndMember end2 = (AssociationEndMember)association.Members[1];
|
|
|
|
CreateModelNavigationProperty(session, end1, end2);
|
|
CreateModelNavigationProperty(session, end2, end1);
|
|
}
|
|
|
|
private void CreateModelNavigationProperty(LoadMethodSessionState session, AssociationEndMember from, AssociationEndMember to)
|
|
{
|
|
EntityType entityType = (EntityType)((RefType)from.TypeUsage.EdmType).ElementType;
|
|
UniqueIdentifierService usedMemberNames = new UniqueIdentifierService(false);
|
|
LoadNameLookupWithUsedMemberNames(entityType, usedMemberNames);
|
|
string name = CreateModelName(this._pluralizationServiceHandler.GetNavigationPropertyName(to, to.Name), usedMemberNames);
|
|
NavigationProperty navigationProperty = new NavigationProperty(name, to.TypeUsage);
|
|
navigationProperty.RelationshipType = (AssociationType)to.DeclaringType;
|
|
navigationProperty.ToEndMember = to;
|
|
navigationProperty.FromEndMember = from;
|
|
entityType.AddMember(navigationProperty);
|
|
}
|
|
|
|
private void LoadNameLookupWithUsedMemberNames(EntityType entityType, UniqueIdentifierService usedMemberNames)
|
|
{
|
|
// a property should not have the same name as its entity
|
|
usedMemberNames.RegisterUsedIdentifier(entityType.Name);
|
|
foreach (EdmMember member in entityType.Members)
|
|
{
|
|
usedMemberNames.RegisterUsedIdentifier(member.Name);
|
|
}
|
|
}
|
|
|
|
private AssociationEndMember CreateAssociationEndMember(LoadMethodSessionState session, AssociationEndMember storeEndMember, KeyValuePair<string, RelationshipMultiplicity> endMultiplicityOverride, UniqueIdentifierService usedEndMemberNames)
|
|
{
|
|
RelationshipMultiplicity multiplicity = storeEndMember.RelationshipMultiplicity;
|
|
if (endMultiplicityOverride.Key != null && endMultiplicityOverride.Key == storeEndMember.Name)
|
|
{
|
|
multiplicity = endMultiplicityOverride.Value;
|
|
}
|
|
return CreateAssociationEndMember(session, storeEndMember, usedEndMemberNames, multiplicity, storeEndMember.DeleteBehavior);
|
|
}
|
|
|
|
private AssociationEndMember CreateAssociationEndMember(LoadMethodSessionState session, AssociationEndMember storeEndMember, UniqueIdentifierService usedEndMemberNames, RelationshipMultiplicity multiplicityOverride, OperationAction deleteBehaviorOverride)
|
|
{
|
|
Debug.Assert(storeEndMember.TypeUsage.EdmType.BuiltInTypeKind == BuiltInTypeKind.RefType, "The type is not a ref type");
|
|
Debug.Assert(((RefType)storeEndMember.TypeUsage.EdmType).ElementType.BuiltInTypeKind == BuiltInTypeKind.EntityType, "the ref is not holding on to an EntityType");
|
|
|
|
EntityType storeEntityType = ((EntityType)((RefType)storeEndMember.TypeUsage.EdmType).ElementType);
|
|
EntityType modelEntityType = session.MappingLookups.StoreEntityTypeToModelEntityType[storeEntityType];
|
|
|
|
string name = CreateModelName(storeEndMember.Name, usedEndMemberNames);
|
|
AssociationEndMember end = new AssociationEndMember(name, modelEntityType.GetReferenceType(), multiplicityOverride);
|
|
end.DeleteBehavior = deleteBehaviorOverride;
|
|
return end;
|
|
}
|
|
|
|
private string CreateModelName(string storeName, UniqueIdentifierService usedNames)
|
|
{
|
|
string newName = CreateModelName(storeName);
|
|
newName = usedNames.AdjustIdentifier(newName);
|
|
return newName;
|
|
}
|
|
|
|
private static string CreateValildModelNamespaceName(string storeNamespaceName)
|
|
{
|
|
return CreateValidNamespaceName(storeNamespaceName, 'C');
|
|
}
|
|
|
|
internal static string CreateValidNamespaceName(string storeNamespaceName, char appendToFrontIfFirstCharIsInvalid)
|
|
{
|
|
List<string> namespaceParts = new List<string>();
|
|
foreach (string sPart in storeNamespaceName.Split(new char[] { '.' }))
|
|
{
|
|
// each part of a namespace needs to be a valid
|
|
// cspace name
|
|
namespaceParts.Add(CreateValidEcmaName(sPart, appendToFrontIfFirstCharIsInvalid));
|
|
}
|
|
|
|
string modelNamespaceName = "";
|
|
for (int i = 0; i < namespaceParts.Count - 1; i++)
|
|
{
|
|
modelNamespaceName += namespaceParts[i] + ".";
|
|
}
|
|
modelNamespaceName += namespaceParts[namespaceParts.Count - 1];
|
|
|
|
|
|
// We might get a clash in names if ssdl has two types named #table and $table. Both will generate C_table
|
|
// We leave it to the calling method to resolve any name clashes
|
|
return modelNamespaceName;
|
|
}
|
|
|
|
//This method maps invalid characters such as &^%,etc in order to generate valid names
|
|
private static string CreateModelName(string storeName)
|
|
{
|
|
return CreateValidEcmaName(storeName, 'C');
|
|
}
|
|
|
|
internal static string CreateValidEcmaName(string name, char appendToFrontIfFirstCharIsInvalid)
|
|
{
|
|
char[] ecmaNameArray = name.ToCharArray();
|
|
for (int i = 0; i < ecmaNameArray.Length; i++)
|
|
{
|
|
// replace non -(letters or digits) with _ ( underscore )
|
|
if (!char.IsLetterOrDigit(ecmaNameArray[i]))
|
|
{
|
|
ecmaNameArray[i] = '_';
|
|
}
|
|
}
|
|
|
|
string ecmaName = new string(ecmaNameArray);
|
|
// the first letter in a part should only be a char
|
|
// if the part is empty then implies that we have the situation like ".abc", "abc.", "ab..c",
|
|
// neither of them are accepted by the schema
|
|
if (string.IsNullOrEmpty(name) || !char.IsLetter(ecmaName[0]))
|
|
{
|
|
ecmaName = appendToFrontIfFirstCharIsInvalid + ecmaName;
|
|
}
|
|
|
|
return ecmaName;
|
|
}
|
|
}
|
|
}
|