//--------------------------------------------------------------------- // // Copyright (c) Microsoft Corporation. All rights reserved. // // // @owner Microsoft // @backupOwner Microsoft //--------------------------------------------------------------------- namespace System.Data.Metadata.Edm { using System; using System.Collections.Generic; using System.Data.Common; using System.Data.Common.CommandTrees; using System.Data.Common.Utils; using System.Data.Entity; using System.Data.EntityModel.SchemaObjectModel; using System.Data.Objects.ELinq; using System.Diagnostics; using System.Linq; using System.Runtime.Versioning; using System.Text; using System.Threading; using System.Xml; /// /// Class for representing a collection of items in Edm space. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")] [CLSCompliant(false)] public sealed class EdmItemCollection : ItemCollection { #region Constructors /// /// constructor that loads the metadata files from the specified xmlReaders, and returns the list of errors /// encountered during load as the out parameter errors. /// /// xmlReaders where the CDM schemas are loaded /// Paths (URIs)to the CSDL files or resources /// An out parameter to return the collection of errors encountered while loading // referenced by System.Data.Entity.Design.dll [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal EdmItemCollection(IEnumerable xmlReaders, System.Collections.ObjectModel.ReadOnlyCollection filePaths, out IList errors) : base(DataSpace.CSpace) { // we will check the parameters for this internal ctor becuase // it is pretty much publicly exposed through the MetadataItemCollectionFactory // in System.Data.Entity.Design // // we are intentionally not checking for an empty enumerable EntityUtil.CheckArgumentNull(xmlReaders, "xmlReaders"); EntityUtil.CheckArgumentContainsNull(ref xmlReaders, "xmlReaders"); // filePaths is allowed to be null errors = this.Init(xmlReaders, filePaths, false /*throwOnErrors*/); } /// /// constructor that loads the metadata files from the specified schemas /// /// list of schemas to be loaded into the ItemCollection // referenced by System.Data.Entity.Design.dll [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] internal EdmItemCollection(IList schemas) : base(DataSpace.CSpace) { this.Init(); LoadItems(MetadataItem.EdmProviderManifest, schemas, this); } /// /// constructor that loads the metadata files from the specified xmlReaders /// /// xmlReaders where the CDM schemas are loaded /// Paths (URIs)to the CSDL files or resources internal EdmItemCollection(IEnumerable xmlReaders, IEnumerable filePaths) : base(DataSpace.CSpace) { this.Init(xmlReaders, filePaths, true /*throwOnErrors*/); } /// /// Public constructor that loads the metadata files from the specified XmlReaders /// /// XmlReader objects where the EDM schemas are loaded public EdmItemCollection(IEnumerable xmlReaders) : base(DataSpace.CSpace) { EntityUtil.CheckArgumentNull(xmlReaders, "xmlReaders"); EntityUtil.CheckArgumentContainsNull(ref xmlReaders, "xmlReaders"); MetadataArtifactLoader composite = MetadataArtifactLoader.CreateCompositeFromXmlReaders(xmlReaders); this.Init(composite.GetReaders(), composite.GetPaths(), true /*throwOnError*/); } /// /// Constructs the new instance of EdmItemCollection /// with the list of CDM files provided. /// /// paths where the CDM schemas are loaded /// Thrown if path name is not valid /// thrown if paths argument is null /// For errors related to invalid schemas. [ResourceExposure(ResourceScope.Machine)] //Exposes the file path names which are a Machine resource [ResourceConsumption(ResourceScope.Machine)] //For MetadataArtifactLoader.CreateCompositeFromFilePaths method call but we do not create the file paths in this method public EdmItemCollection(params string[] filePaths) : base(DataSpace.CSpace) { EntityUtil.CheckArgumentNull(filePaths, "filePaths"); // Wrap the file paths in instances of the MetadataArtifactLoader class, which provides // an abstraction and a uniform interface over a diverse set of metadata artifacts. // MetadataArtifactLoader composite = null; List readers = null; try { composite = MetadataArtifactLoader.CreateCompositeFromFilePaths(filePaths, XmlConstants.CSpaceSchemaExtension); readers = composite.CreateReaders(DataSpace.CSpace); this.Init(readers, composite.GetPaths(DataSpace.CSpace), true /*throwOnError*/); } finally { if (readers != null) { Helper.DisposeXmlReaders(readers); } } } // the most basic initialization private void Init() { // Load the EDM primitive types LoadEdmPrimitiveTypesAndFunctions(); } /// /// Public constructor that loads the metadata files from the specified XmlReaders, and /// returns the list of errors encountered during load as the out parameter 'errors'. /// /// XmlReader objects where the EDM schemas are loaded /// Paths (URIs) to the CSDL files or resources /// A flag to indicate whether to throw if LoadItems returns errors private IList Init(IEnumerable xmlReaders, IEnumerable filePaths, bool throwOnError) { EntityUtil.CheckArgumentNull(xmlReaders, "xmlReaders"); // do the basic initialization Init(); IList errors = LoadItems(xmlReaders, filePaths, SchemaDataModelOption.EntityDataModel, MetadataItem.EdmProviderManifest, this, throwOnError); return errors; } #endregion #region Fields // Cache for primitive type maps for Edm to provider private CacheForPrimitiveTypes _primitiveTypeMaps = new CacheForPrimitiveTypes(); private Double _edmVersion = XmlConstants.UndefinedVersion; /// /// Gets canonical versions of InitializerMetadata instances. This avoids repeatedly /// compiling delegates for materialization. /// private Memoizer _getCanonicalInitializerMetadataMemoizer; /// /// Manages user defined function definitions. /// private Memoizer _getGeneratedFunctionDefinitionsMemoizer; private OcAssemblyCache _conventionalOcCache = new OcAssemblyCache(); #endregion #region Properties /// /// Version of the EDM that this ItemCollection represents. /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Edm")] public Double EdmVersion { get { return _edmVersion; } internal set { _edmVersion = value; } } /// /// conventional oc mapping cache, the locking mechanism is provided by ----semblyCache /// internal OcAssemblyCache ConventionalOcCache { get { return _conventionalOcCache; } } #endregion #region Methods /// /// Given an InitializerMetadata instance, returns the canonical version of that instance. /// This allows us to avoid compiling materialization delegates repeatedly for the same /// pattern. /// internal InitializerMetadata GetCanonicalInitializerMetadata(InitializerMetadata metadata) { if (null == _getCanonicalInitializerMetadataMemoizer) { // We memoize the identity function because the first evaluation of the function establishes // the canonical 'reference' for the initializer metadata with a particular 'value'. Interlocked.CompareExchange(ref _getCanonicalInitializerMetadataMemoizer, new Memoizer( m => m, EqualityComparer.Default), null); } // check if an equivalent has already been registered InitializerMetadata canonical = _getCanonicalInitializerMetadataMemoizer.Evaluate(metadata); return canonical; } internal static bool IsSystemNamespace(DbProviderManifest manifest, string namespaceName) { if (manifest == MetadataItem.EdmProviderManifest) { return (namespaceName == EdmConstants.TransientNamespace || namespaceName == EdmConstants.EdmNamespace || namespaceName == EdmConstants.ClrPrimitiveTypeNamespace); } else { return (namespaceName == EdmConstants.TransientNamespace || namespaceName == EdmConstants.EdmNamespace || namespaceName == EdmConstants.ClrPrimitiveTypeNamespace || (manifest != null && namespaceName == manifest.NamespaceName)); } } /// /// Load stuff from xml readers - this now includes XmlReader instances created over embedded /// resources. See the remarks section below for some useful information. /// /// A list of XmlReader instances /// whether this is a entity data model or provider data model /// provider manifest from which the primitive type definition comes from /// item collection to add the item after loading /// Indicates whether the method should bother with the file paths; see remarks below /// /// In order to accommodate XmlReaders over artifacts embedded as resources in assemblies, the /// notion of a filepath had to be abstracted into a URI. In reality, however, a res:// URI that /// points to an embedded resource does not constitute a valid URI (i.e., one that can be parsed /// by the System.Uri class in the .NET framework). In such cases, we need to supply a list of /// "filepaths" (which includes res:// URIs), instead of having this method create the collection. /// This distinction is made by setting the 'computeFilePaths' flags appropriately. /// internal static IList LoadItems(IEnumerable xmlReaders, IEnumerable sourceFilePaths, SchemaDataModelOption dataModelOption, DbProviderManifest providerManifest, ItemCollection itemCollection, bool throwOnError) { IList schemaCollection = null; // Parse and validate all the schemas - since we support using now, // we need to parse them as a group var errorCollection = SchemaManager.ParseAndValidate(xmlReaders, sourceFilePaths, dataModelOption, providerManifest, out schemaCollection); // Try to initialize the metadata if there are no errors if (MetadataHelper.CheckIfAllErrorsAreWarnings(errorCollection)) { List errors = LoadItems(providerManifest, schemaCollection, itemCollection); foreach (var error in errors) { errorCollection.Add(error); } } if (!MetadataHelper.CheckIfAllErrorsAreWarnings(errorCollection) && throwOnError) { //Future Enhancement: if there is an error, we throw exception with error and warnings. //Otherwise the user has no clue to know about warnings. throw EntityUtil.InvalidSchemaEncountered(Helper.CombineErrorMessage(errorCollection)); } return errorCollection; } internal static List LoadItems(DbProviderManifest manifest, IList somSchemas, ItemCollection itemCollection) { List errors = new List(); // Convert the schema, if model schema, then we use the EDM provider manifest, otherwise use the // store provider manifest IEnumerable newGlobalItems = LoadSomSchema(somSchemas, manifest, itemCollection); List tempCTypeFunctionIdentity = new List(); // No errors, so go ahead and add the types and make them readonly foreach (GlobalItem globalItem in newGlobalItems) { // If multiple function parameter and return types expressed in SSpace map to the same // CSpace type (e.g., SqlServer.decimal and SqlServer.numeric both map to Edm.Decimal), // we need to guard against attempts to insert duplicate functions into the collection. // if (globalItem.BuiltInTypeKind == BuiltInTypeKind.EdmFunction && globalItem.DataSpace == DataSpace.SSpace) { EdmFunction function = (EdmFunction)globalItem; StringBuilder sb = new StringBuilder(); EdmFunction.BuildIdentity( sb, function.FullName, function.Parameters, // convert function parameters to C-side types (param) => MetadataHelper.ConvertStoreTypeUsageToEdmTypeUsage(param.TypeUsage), (param) => param.Mode); string cTypeFunctionIdentity = sb.ToString(); // Validate identity if (tempCTypeFunctionIdentity.Contains(cTypeFunctionIdentity)) { errors.Add( new EdmSchemaError( Strings.DuplicatedFunctionoverloads(function.FullName, cTypeFunctionIdentity.Substring(function.FullName.Length)).Trim()/*parameters*/, (int)ErrorCode.DuplicatedFunctionoverloads, EdmSchemaErrorSeverity.Error)); continue; } tempCTypeFunctionIdentity.Add(cTypeFunctionIdentity); } globalItem.SetReadOnly(); itemCollection.AddInternal(globalItem); } return errors; } /// /// Load metadata from a SOM schema directly /// /// The SOM schema to load from /// The provider manifest used for loading the type /// item collection in which primitive types are present /// The newly created items internal static IEnumerable LoadSomSchema(IList somSchemas, DbProviderManifest providerManifest, ItemCollection itemCollection) { IEnumerable newGlobalItems = Converter.ConvertSchema(somSchemas, providerManifest, itemCollection); return newGlobalItems; } /// /// Get the list of primitive types for the given space /// /// public System.Collections.ObjectModel.ReadOnlyCollection GetPrimitiveTypes() { return _primitiveTypeMaps.GetTypes(); } /// /// Get the list of primitive types for the given version of Edm /// /// The version of edm to use /// [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "edm")] public System.Collections.ObjectModel.ReadOnlyCollection GetPrimitiveTypes(double edmVersion) { if (edmVersion == XmlConstants.EdmVersionForV1 || edmVersion == XmlConstants.EdmVersionForV1_1 || edmVersion == XmlConstants.EdmVersionForV2) { return _primitiveTypeMaps.GetTypes().Where(type => !Helper.IsSpatialType(type)).ToList().AsReadOnly(); } else if (edmVersion == XmlConstants.EdmVersionForV3) { return _primitiveTypeMaps.GetTypes(); } else { throw EntityUtil.InvalidEDMVersion(edmVersion); } } /// /// Given the canonical primitive type, get the mapping primitive type in the given dataspace /// /// canonical primitive type /// The mapped scalar type internal override PrimitiveType GetMappedPrimitiveType(PrimitiveTypeKind primitiveTypeKind) { PrimitiveType type = null; _primitiveTypeMaps.TryGetType(primitiveTypeKind, null, out type); return type; } private void LoadEdmPrimitiveTypesAndFunctions() { EdmProviderManifest providerManifest = EdmProviderManifest.Instance; System.Collections.ObjectModel.ReadOnlyCollection primitiveTypes = providerManifest.GetStoreTypes(); for (int i = 0; i < primitiveTypes.Count; i++) { this.AddInternal(primitiveTypes[i]); _primitiveTypeMaps.Add(primitiveTypes[i]); } System.Collections.ObjectModel.ReadOnlyCollection functions = providerManifest.GetStoreFunctions(); for (int i = 0; i < functions.Count; i++) { this.AddInternal(functions[i]); } } /// /// Generates function definition or returns a cached one. /// Guarantees type match of declaration and generated parameters. /// Guarantees return type match. /// Throws internal error for functions without definition. /// Passes thru exceptions occured during definition generation. /// internal DbLambda GetGeneratedFunctionDefinition(EdmFunction function) { if (null == _getGeneratedFunctionDefinitionsMemoizer) { Interlocked.CompareExchange( ref _getGeneratedFunctionDefinitionsMemoizer, new Memoizer(GenerateFunctionDefinition, null), null); } return _getGeneratedFunctionDefinitionsMemoizer.Evaluate(function); } /// /// Generates function definition or returns a cached one. /// Guarantees type match of declaration and generated parameters. /// Guarantees return type match. /// Throws internal error for functions without definition. /// Passes thru exceptions occured during definition generation. /// internal DbLambda GenerateFunctionDefinition(EdmFunction function) { Debug.Assert(function.IsModelDefinedFunction, "Function definition can be requested only for user-defined model functions."); if (!function.HasUserDefinedBody) { throw EntityUtil.FunctionHasNoDefinition(function); } DbLambda generatedDefinition; // Generate the body generatedDefinition = Mapping.ViewGeneration.Utils.ExternalCalls.CompileFunctionDefinition( function.FullName, function.CommandTextAttribute, function.Parameters, this); // Ensure the result type of the generated definition matches the result type of the edm function (the declaration) if (!TypeSemantics.IsStructurallyEqual(function.ReturnParameter.TypeUsage, generatedDefinition.Body.ResultType)) { throw EntityUtil.FunctionDefinitionResultTypeMismatch(function, generatedDefinition.Body.ResultType); } Debug.Assert(generatedDefinition != null, "generatedDefinition != null"); return generatedDefinition; } #endregion }//---- ItemCollection }//----