File: System\Data\Entity\Design\EntityStoreSchemaGenerator.cs
Project: ndp\fx\src\DataEntityDesign\Design\System.Data.Entity.Design.csproj (System.Data.Entity.Design)
//---------------------------------------------------------------------
// <copyright file="EntityStoreSchemaGenerator.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner       Microsoft
// @backupOwner Microsoft
//---------------------------------------------------------------------
using System.Collections.Generic;
using System.Diagnostics;
using System.Xml;
using SOM=System.Data.EntityModel;
using System.Globalization;
using System.Data.Metadata.Edm;
using System.Data.Entity.Design.Common;
using System.Data.Entity.Design.SsdlGenerator;
using System.Data.Common;
using System.Data.EntityClient;
using System.IO;
using System.Data.Mapping;
using System.Data.Common.Utils;
using System.Collections.ObjectModel;
using System.Data.Common.CommandTrees;
using System.Data.Common.CommandTrees.ExpressionBuilder;
using System.Text;
using System.Linq;
using Microsoft.Build.Utilities;
 
namespace System.Data.Entity.Design
{
    /// <summary>
    /// Responsible for Loading Database Schema Information
    /// </summary>
    public sealed partial class EntityStoreSchemaGenerator 
    {
        private const string CONTAINER_SUFFIX = "Container";
        private EntityStoreSchemaGeneratorDatabaseSchemaLoader _loader;
        private readonly string _provider;
        private string _providerManifestToken = string.Empty;
        private EntityContainer _entityContainer = null;
        private StoreItemCollection _storeItemCollection;
        private string _namespaceName;
        private MetadataItemSerializer.ErrorsLookup _errorsLookup;
        private List<EdmType> _invalidTypes;
        private Version _targetEntityFrameworkVersion;
 
        /// <summary>
        /// Creates a new EntityStoreGenerator
        /// </summary>
        /// <param name="providerInvariantName">The name of the provider to use to load the schema information.</param>
        /// <param name="connectionString">A connection string to the DB that should be loaded from.</param>
        /// <param name="namespaceName">The namespace name to use for the store metadata that is generated.</param>
        public EntityStoreSchemaGenerator(string providerInvariantName, string connectionString, string namespaceName)
        {
            EDesignUtil.CheckStringArgument(providerInvariantName, "providerInvariantName");
            EDesignUtil.CheckArgumentNull(connectionString, "connectionString"); // check for NULL string and support empty connection string
            EDesignUtil.CheckStringArgument(namespaceName, "namespaceName");
 
            _namespaceName = namespaceName;
 
            _provider = providerInvariantName;
            _loader = new EntityStoreSchemaGeneratorDatabaseSchemaLoader(providerInvariantName, connectionString);
        }
 
        /// <summary>
        /// Gets the EntityContainer that was created
        /// </summary>
        public EntityContainer EntityContainer
        {
            get
            {
                return _entityContainer;
            }
        }
 
        /// <summary>
        /// Gets the StoreItemCollection that was created
        /// </summary>
        [CLSCompliant(false)]
        public StoreItemCollection StoreItemCollection
        {
            get
            {
                return _storeItemCollection;
            }
        }
 
        /// <summary>
        /// Indicates whether the given storage model will be used to produce an entity model with foreign keys.
        /// </summary>
        public bool GenerateForeignKeyProperties
        {
            get;
            set;
        }
 
        /// <summary>
        /// Creates a Metadata schema from the DbSchemaLoader that was passed in
        /// </summary>
        /// <returns>The new metadata for the schema that was loaded</returns>
        public IList<EdmSchemaError> GenerateStoreMetadata()
        {
            List<EntityStoreSchemaFilterEntry> filters = new List<EntityStoreSchemaFilterEntry>();
            return DoGenerateStoreMetadata(filters, EntityFrameworkVersions.Latest);
        }
 
        /// <summary>
        /// Creates a Metadata schema from the DbSchemaLoader that was passed in
        /// </summary>
        /// <param name="filters">The filters to be applied during generation.</param>
        /// <returns>The new metadata for the schema that was loaded</returns>
        public IList<EdmSchemaError> GenerateStoreMetadata(IEnumerable<EntityStoreSchemaFilterEntry> filters)
        {
            EDesignUtil.CheckArgumentNull(filters, "filters");
            return DoGenerateStoreMetadata(filters, EntityFrameworkVersions.Latest);
        }
 
        /// <summary>
        /// Creates a Metadata schema from the DbSchemaLoader that was passed in
        /// </summary>
        /// <param name="filters">The filters to be applied during generation.</param>
        /// <param name="targetFrameworkMoniker">The filters to be applied during generation.</param>
        /// <returns>The new metadata for the schema that was loaded</returns>
        public IList<EdmSchemaError> GenerateStoreMetadata(IEnumerable<EntityStoreSchemaFilterEntry> filters, Version targetEntityFrameworkVersion)
        {
            EDesignUtil.CheckArgumentNull(filters, "filters");
            EDesignUtil.CheckTargetEntityFrameworkVersionArgument(targetEntityFrameworkVersion, "targetEntityFrameworkVersion");
 
 
            // we are not going to actually use targetFrameworkMoniker at this time, but
            // we want the option to use it in the future if we change the 
            // the ssdl schema
            return DoGenerateStoreMetadata(filters, targetEntityFrameworkVersion);
        }
 
        private IList<EdmSchemaError> DoGenerateStoreMetadata(IEnumerable<EntityStoreSchemaFilterEntry> filters, Version targetEntityFrameworkVersion)
        {
            if (_entityContainer != null)
            {
                _entityContainer = null;
                _storeItemCollection = null;
                _errorsLookup = null;
                _invalidTypes = null;
            }
 
            _targetEntityFrameworkVersion = targetEntityFrameworkVersion;
            LoadMethodSessionState session = new LoadMethodSessionState(targetEntityFrameworkVersion);
            try
            {
                _loader.Open();
 
                DbConnection connection = _loader.InnerConnection;
                DbProviderFactory providerFactory = DbProviderServices.GetProviderFactory(_loader.ProviderInvariantName);
                DbProviderServices providerServices = DbProviderServices.GetProviderServices(providerFactory);
                _providerManifestToken = providerServices.GetProviderManifestToken(connection);
                DbProviderManifest storeManifest = providerServices.GetProviderManifest(_providerManifestToken);
                
                session.Filters = filters;
                Debug.Assert(_namespaceName != null, "_namespaceName should not be null at this point, did you add a new ctor?");
 
                session.ItemCollection = new StoreItemCollection(providerFactory, providerServices.GetProviderManifest(_providerManifestToken), _providerManifestToken);
 
                CreateTableEntityTypes(session);
                CreateViewEntityTypes(session);
                string entityContainerName = this._namespaceName.Replace(".", string.Empty) + CONTAINER_SUFFIX;
 
                Debug.Assert(entityContainerName != null, "We should always have a container name");
                EntityContainer entityContainer = new EntityContainer(entityContainerName, DataSpace.SSpace);
 
                foreach (EntityType type in session.GetAllEntities())
                {
                    Debug.Assert(type.KeyMembers.Count > 0, "Why do we have Entities without keys in our valid Entities collection");
                    session.ItemCollection.AddInternal(type);
                    EntitySet entitySet = CreateEntitySet(session, type);
                    session.EntityTypeToSet.Add(type, entitySet);
                    entityContainer.AddEntitySetBase(entitySet);
                }
 
                CreateAssociationTypes(session);
                foreach (AssociationType type in session.AssociationTypes)
                {
                    session.ItemCollection.AddInternal(type);
                    AssociationSet set = CreateAssociationSet(session, type);
                    entityContainer.AddEntitySetBase(set);
                }
 
                entityContainer.SetReadOnly();
                session.ItemCollection.AddInternal(entityContainer);
                FixupKeylessEntitySets(entityContainer, session);
 
                if (_targetEntityFrameworkVersion >= EntityFrameworkVersions.Version3 &&
                    _loader.StoreSchemaModelVersion >= EntityFrameworkVersions.Version3)
                {
                    CreateTvfReturnRowTypes(session);
                }
                CreateEdmFunctions(session);
                foreach (EdmFunction function in session.Functions)
                {
                    session.ItemCollection.AddInternal(function);
                }
 
                if (!HasErrorSeverityErrors(session.Errors))
                {
                    _entityContainer = entityContainer;
                    _storeItemCollection = session.ItemCollection;
                    _errorsLookup = session.ItemToErrorsMap;
                    _invalidTypes = new List<EdmType>(session.InvalidTypes);
                }
            }
            catch (Exception e)
            {
                if (MetadataUtil.IsCatchableExceptionType(e))
                {
                    string message = EDesignUtil.GetMessagesFromEntireExceptionChain(e);
                    session.AddErrorsForType(null,
                        new EdmSchemaError(message,
                                    (int)ModelBuilderErrorCode.UnknownError,
                                    EdmSchemaErrorSeverity.Error,
                                    e));
                }
                else
                {
                    throw;
                }
            }
            finally
            {
                _loader.Close();
            }
 
            return new List<EdmSchemaError>(session.Errors);
        }
 
        /// <summary>
        /// Writes the Schema to xml
        /// </summary>
        /// <param name="outputFileName">The name of the file to write the xml to.</param>
        public void WriteStoreSchema(string outputFileName)
        {
            EDesignUtil.CheckStringArgument(outputFileName, "outputFileName");
            CheckValidItemCollection();
 
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;
            using (XmlWriter writer = XmlWriter.Create(outputFileName, settings))
            {
                WriteStoreSchema(writer);
            }
        }
 
        /// <summary>
        /// Writes the Schema to xml.
        /// </summary>
        /// <param name="writer">The XmlWriter to write the xml to.</param>
        public void WriteStoreSchema(XmlWriter writer)
        {
            EDesignUtil.CheckArgumentNull(writer, "writer");
            CheckValidItemCollection();
            
            // we are going to add this EntityStoreSchemaGenerator namespace at the top of 
            // the file so that when we mark the entitysets with where they came from 
            // we don't have to repeat the namespace on each node.  The VS tools use 
            // the source information to give better messages when refreshing the .ssdl from the db
            // e.g.
            // <Schema xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator ...>
            //   <EntityContainer ...>
            //      <!-- Views is the name of the StoreInformation EntitySet that this EntitySet was created from -->
            //      <EntitySet ... store:SchemaInformationSource="Views" /> 
            //   </EntityContainer>
            //   ...
            // </Schema>
            var xmlPrefixToNamespace = new KeyValuePair<string, string>("store", DesignXmlConstants.EntityStoreSchemaGeneratorNamespace);
            MetadataItemSerializer.WriteXml(writer, StoreItemCollection, _namespaceName, _errorsLookup, _invalidTypes, _provider, _providerManifestToken, _targetEntityFrameworkVersion, xmlPrefixToNamespace);
        }
 
        /// <summary>
        /// Creates an EntityConnection loaded with the providers metadata for the store schema.
        /// Store schema model is the one used in <see cref="EntityFrameworkVersions.Version2"/>.
        /// </summary>
        /// <param name="providerInvariantName">The provider invariant name.</param>
        /// <param name="connectionString">The connection for the providers connection.</param>
        /// <returns>An EntityConnection that can query the ConceptualSchemaDefinition for the provider.</returns>
        public static EntityConnection CreateStoreSchemaConnection(string providerInvariantName, string connectionString)
        {
            return CreateStoreSchemaConnection(providerInvariantName, connectionString, EntityFrameworkVersions.Version2);
        }
 
        /// <summary>
        /// Creates an EntityConnection loaded with the providers metadata for the store schema.
        /// Note that the targetEntityFrameworkVersion parameter uses internal EntityFramework version numbers as
        /// described in the <see cref="EntityFrameworkVersions"/> class.
        /// </summary>
        /// <param name="providerInvariantName">The provider invariant name.</param>
        /// <param name="connectionString">The connection for the providers connection.</param>
        /// <param name="targetEntityFrameworkVersion">The internal Entity Framework version that is being targeted.</param>
        /// <returns>An EntityConnection that can query the ConceptualSchemaDefinition for the provider.</returns>
        public static EntityConnection CreateStoreSchemaConnection(string providerInvariantName, string connectionString, Version targetEntityFrameworkVersion)
        {
            EDesignUtil.CheckArgumentNull(providerInvariantName, "providerInvariantName");
            EDesignUtil.CheckArgumentNull(connectionString, "connectionString");
            EDesignUtil.CheckTargetEntityFrameworkVersionArgument(targetEntityFrameworkVersion, "targetEntityFrameworkVersion");
 
            DbProviderFactory factory;
            try
            {
                factory = DbProviderFactories.GetFactory(providerInvariantName);
            }
            catch (ArgumentException e)
            {
                throw EDesignUtil.Argument(Strings.EntityClient_InvalidStoreProvider(providerInvariantName), e);
            }
 
            DbProviderServices providerServices = MetadataUtil.GetProviderServices(factory);
 
            DbConnection providerConnection = factory.CreateConnection();
            if (providerConnection == null)
            {
                throw EDesignUtil.ProviderIncompatible(Strings.ProviderFactoryReturnedNullFactory(providerInvariantName));
            }
            providerConnection.ConnectionString = connectionString;
 
            MetadataWorkspace workspace = GetProviderSchemaMetadataWorkspace(providerServices, providerConnection, targetEntityFrameworkVersion);
 
            // create the connection with the information we have
            return new EntityConnection(workspace, providerConnection);
        }
 
        private static MetadataWorkspace GetProviderSchemaMetadataWorkspace(DbProviderServices providerServices, DbConnection providerConnection, Version targetEntityFrameworkVersion)
        {
            XmlReader csdl = null;
            XmlReader ssdl = null;
            XmlReader msl = null;
 
            Debug.Assert(EntityFrameworkVersions.IsValidVersion(targetEntityFrameworkVersion), "EntityFrameworkVersions.IsValidVersion(targetEntityFrameworkVersion)");
            string csdlName;
            string ssdlName;
            string mslName;
            if (targetEntityFrameworkVersion >= EntityFrameworkVersions.Version3)
            {
                csdlName = DbProviderManifest.ConceptualSchemaDefinitionVersion3;
                ssdlName = DbProviderManifest.StoreSchemaDefinitionVersion3;
                mslName = DbProviderManifest.StoreSchemaMappingVersion3;
            }
            else
            {
                csdlName = DbProviderManifest.ConceptualSchemaDefinition;
                ssdlName = DbProviderManifest.StoreSchemaDefinition;
                mslName = DbProviderManifest.StoreSchemaMapping;
            }
 
            try
            {
                // create the metadata workspace
                MetadataWorkspace workspace = new MetadataWorkspace();
 
                string manifestToken = providerServices.GetProviderManifestToken(providerConnection);
                DbProviderManifest providerManifest = providerServices.GetProviderManifest(manifestToken);
 
                // create the EdmItemCollection
                IList<EdmSchemaError> errors;
                ssdl = providerManifest.GetInformation(ssdlName);
                string location = Strings.DbProviderServicesInformationLocationPath(providerConnection.GetType().Name, ssdlName);
                List<string> ssdlLocations = new List<string>(1);
                ssdlLocations.Add(location);
                StoreItemCollection storeItemCollection = new StoreItemCollection(new XmlReader[] { ssdl }, ssdlLocations.AsReadOnly(), out errors);
                ThrowOnError(errors);
                workspace.RegisterItemCollection(storeItemCollection);
 
                csdl = DbProviderServices.GetConceptualSchemaDefinition(csdlName);
                location = Strings.DbProviderServicesInformationLocationPath(typeof(DbProviderServices).Name, csdlName);
                List<string> csdlLocations = new List<string>(1);
                csdlLocations.Add(location);
                EdmItemCollection edmItemCollection = new EdmItemCollection(new XmlReader[] { csdl }, csdlLocations.AsReadOnly(), out errors);
                ThrowOnError(errors);
                workspace.RegisterItemCollection(edmItemCollection);
 
                msl = providerManifest.GetInformation(mslName);
                location = Strings.DbProviderServicesInformationLocationPath(providerConnection.GetType().Name, DbProviderManifest.StoreSchemaMapping);
                List<string> mslLocations = new List<string>(1);
                mslLocations.Add(location);
                StorageMappingItemCollection mappingItemCollection = new StorageMappingItemCollection(edmItemCollection,
                                                                         storeItemCollection,
                                                                         new XmlReader[] { msl },
                                                                         mslLocations,
                                                                         out errors);
                ThrowOnError(errors);
                workspace.RegisterItemCollection(mappingItemCollection);
 
                // make the views generate here so we can wrap the provider schema problems
                // in a ProviderIncompatibleException
                ForceViewGeneration(workspace);
                return workspace;
            }
            catch (ProviderIncompatibleException)
            {
                // we don't really want to catch this one, just rethrow it
                throw;
            }
            catch (Exception e)
            {
                if (MetadataUtil.IsCatchableExceptionType(e))
                {
                    throw EDesignUtil.ProviderIncompatible(Strings.ProviderSchemaErrors, e);
                }
 
                throw;
            }
            finally
            {
                if (csdl != null) ((IDisposable)csdl).Dispose();
                if (ssdl != null) ((IDisposable)ssdl).Dispose();
                if (msl != null) ((IDisposable)msl).Dispose();
            }
        }
 
        private static void ForceViewGeneration(MetadataWorkspace workspace)
        {
            ReadOnlyCollection<EntityContainer> containers = workspace.GetItems<EntityContainer>(DataSpace.SSpace);
            Debug.Assert(containers.Count != 0, "no s space containers found");
            Debug.Assert(containers[0].BaseEntitySets.Count != 0, "no entity sets in the sspace container");
            workspace.GetCqtView(containers[0].BaseEntitySets[0]);
        }
 
        private static void ThrowOnError(IList<EdmSchemaError> errors)
        {
            if (errors.Count != 0)
            {
                if (!MetadataUtil.CheckIfAllErrorsAreWarnings(errors))
                {
                    throw EDesignUtil.ProviderIncompatible(Strings.ProviderSchemaErrors, EntityUtil.InvalidSchemaEncountered(MetadataUtil.CombineErrorMessage(errors)));
                }
            }
        }
 
        private void CheckValidItemCollection()
        {
            if (_entityContainer == null)
            {
                throw EDesignUtil.EntityStoreGeneratorSchemaNotLoaded();
            }
        }
 
        internal static bool HasErrorSeverityErrors(IEnumerable<EdmSchemaError> errors)
        {
            foreach (EdmSchemaError error in errors)
            {
                if (error.Severity == EdmSchemaErrorSeverity.Error)
                {
                    return true;
                }
            }
            return false;
        }
 
 
        private AssociationSet CreateAssociationSet(LoadMethodSessionState session, 
            AssociationType type)
        {
            AssociationSet set = new AssociationSet(type.Name, type);
 
            foreach(AssociationEndMember end in type.RelationshipEndMembers)
            {
                EntitySet entitySet = session.GetEntitySet(end);
                DbObjectKey key = session.GetKey(entitySet.ElementType);
                AssociationSetEnd setEnd = new AssociationSetEnd(entitySet, set, end);
                set.AddAssociationSetEnd(setEnd);
            }
 
            set.SetReadOnly();
            return set;
        }
 
        private EntitySet CreateEntitySet( 
            LoadMethodSessionState session,
            EntityType type 
            )
        {
            DbObjectKey key = session.GetKey(type);
            string schema = key.Schema;
 
            string table = null;
            if (key.TableName != type.Name)
            {
                table = key.TableName;
            }
 
            EntitySet entitySet = new EntitySet(type.Name, 
                        schema, 
                        table,
                        null,
                        type);
 
            MetadataProperty property = System.Data.EntityModel.SchemaObjectModel.SchemaElement.CreateMetadataPropertyFromOtherNamespaceXmlArtifact(DesignXmlConstants.EntityStoreSchemaGeneratorNamespace, DesignXmlConstants.EntityStoreSchemaGeneratorTypeAttributeName, GetSourceNameFromObjectType(key.ObjectType));
            List<MetadataProperty> properties = new List<MetadataProperty>();
            properties.Add(property);
            entitySet.AddMetadataProperties(properties);
            entitySet.SetReadOnly();
            return entitySet;
        }
 
        private string GetSourceNameFromObjectType(DbObjectType dbObjectType)
        {
            switch(dbObjectType)
            {
                case DbObjectType.Table:
                    return DesignXmlConstants.TypeValueTables;
                default:
                    Debug.Assert(dbObjectType == DbObjectType.View, "did you change to a call that could have different types?");
                    return DesignXmlConstants.TypeValueViews;
            }
        }
 
        private void CreateEdmFunctions(LoadMethodSessionState session)
        {
            using(FunctionDetailsReader reader = _loader.LoadFunctionDetails(session.Filters))
            {
                DbObjectKey currentFunction = new DbObjectKey();
                List<FunctionDetailsReader.Memento> parameters = new List<FunctionDetailsReader.Memento>();
                while(reader.Read())
                {
                    DbObjectKey rowFunction = reader.CreateDbObjectKey();
                    if (rowFunction != currentFunction)
                    {
                        if (!currentFunction.IsEmpty)
                        {
                            CreateEdmFunction(session, currentFunction, parameters);
                            parameters.Clear();
                        }
                        currentFunction = rowFunction;
                    }
                    parameters.Add(reader.CreateMemento());
                }
 
                if (parameters.Count != 0)
                {
                    CreateEdmFunction(session, currentFunction, parameters);
                }
            }
        }
 
        private void CreateEdmFunction(LoadMethodSessionState session, DbObjectKey functionKey, List<FunctionDetailsReader.Memento> parameters)
        {
            Debug.Assert(parameters.Count != 0, "don't call the method with no data");
 
            FunctionDetailsReader row = parameters[0].CreateReader();
 
            FunctionParameter returnParameter = null;
            bool isValid = true;
            List<EdmSchemaError> errors = new List<EdmSchemaError>();
            if (row.ReturnType != null)
            {
                Debug.Assert(!row.IsTvf, "TVF can't have ReturnType (used only for scalars).");
                bool excludedForTarget;
                TypeUsage returnType = GetScalarFunctionTypeUsage(session, row.ReturnType, out excludedForTarget);
                if (returnType != null)
                {
                    returnParameter = new FunctionParameter(EdmConstants.ReturnType, returnType, ParameterMode.ReturnValue);
                }
                else
                {
                    isValid = false;
                    errors.Add(new EdmSchemaError(excludedForTarget ?
                                                  Strings.UnsupportedFunctionReturnDataTypeForTarget(row.ProcedureName, row.ReturnType) :
                                                  Strings.UnsupportedFunctionReturnDataType(row.ProcedureName, row.ReturnType),
                                                  (int)ModelBuilderErrorCode.UnsupportedType,
                                                  EdmSchemaErrorSeverity.Warning));
                }
            }
            else if (row.IsTvf)
            {
                if (_targetEntityFrameworkVersion < EntityFrameworkVersions.Version3)
                {
                    return;
                }
                RowType tvfReturnType;
                if (session.TryGetTvfReturnType(functionKey, out tvfReturnType) && !session.InvalidTypes.Contains(tvfReturnType))
                {
                    var collectionType = tvfReturnType.GetCollectionType();
                    collectionType.SetReadOnly();
                    returnParameter = new FunctionParameter(EdmConstants.ReturnType, TypeUsage.Create(collectionType), ParameterMode.ReturnValue);
                }
                else
                {
                    isValid = false;
 
                    // If the TVF return type exists, but it is not valid, then reassign all its errors directly to the TVF.
                    // This is needed in order to avoid the following kind of error reporting:
                    // SSDL:
                    // 
                    // <!-- Errors found while generating type:
                    //    column1 type not supported
                    //    column2 type not supported
                    //   <RowType />
                    // -->
                    // ...
                    // ...
                    // <!-- Error found while generating type:
                    //    TableReferencedByTvfWasNotFound
                    //   <Function Name="TVF" .... />
                    // -->
                    // 
                    // Instead we want something like this:
                    // 
                    // <!-- Errors found while generating type:
                    //    column1 type not supported
                    //    column2 type not supported
                    //    TableReferencedByTvfWasNotFound
                    //   <Function Name="TVF" .... />
                    // -->
                    // 
 
                    List<EdmSchemaError> tvfReturnTypeErrors;
                    if (tvfReturnType != null && session.ItemToErrorsMap.TryGetValue(tvfReturnType, out tvfReturnTypeErrors))
                    {
                        errors.AddRange(tvfReturnTypeErrors);
                        session.ItemToErrorsMap.Remove(tvfReturnType);
                        if (session.InvalidTypes.Contains(tvfReturnType))
                        {
                            session.InvalidTypes.Remove(tvfReturnType);
                        }
                    }
                    
                    errors.Add(new EdmSchemaError(
                        Strings.TableReferencedByTvfWasNotFound(functionKey),
                        (int)ModelBuilderErrorCode.MissingTvfReturnTable,
                        EdmSchemaErrorSeverity.Warning));
                }
            }
            
            bool caseSensitive = false;
            UniqueIdentifierService uniqueIdentifiers = new UniqueIdentifierService(caseSensitive);
            List<FunctionParameter> functionParameters = new List<FunctionParameter>();
            for (int i = 0; i < parameters.Count && !row.IsParameterNameNull; i++)
            {
                row.Attach(parameters[i]);
                TypeUsage parameterType = null;
                bool excludedForTarget = false;
                if (!row.IsParameterTypeNull)
                {
                    parameterType = GetScalarFunctionTypeUsage(session, row.ParameterType, out excludedForTarget);
                }
 
                if (parameterType != null)
                {
                    ParameterMode mode;
                    if (!row.TryGetParameterMode(out mode))
                    {
                        isValid = false;
                        string modeValue = "null";
                        if (!row.IsParameterModeNull)
                        {
                            modeValue = row.ProcParameterMode;
                        }
                        errors.Add(new EdmSchemaError(
                            Strings.ParameterDirectionNotValid(
                            row.ProcedureName,
                            row.ParameterName,
                            modeValue),
                            (int)ModelBuilderErrorCode.ParameterDirectionNotValid,
                            EdmSchemaErrorSeverity.Warning));
                    }
 
                    // the mode will get defaulted to something, so it is ok to keep creating after
                    // an error getting the mode value.
                    string parameterName = EntityModelSchemaGenerator.CreateValidEcmaName(row.ParameterName, 'p');
                    parameterName = uniqueIdentifiers.AdjustIdentifier(parameterName);
                    FunctionParameter parameter = new FunctionParameter(parameterName, parameterType, mode);
                    functionParameters.Add(parameter);
                }
                else
                {
                    isValid = false;
                    string typeValue = "null";
                    if (!row.IsParameterTypeNull)
                    {
                        typeValue = row.ParameterType;
                    }
                    errors.Add(new EdmSchemaError(excludedForTarget ?
                                                  Strings.UnsupportedFunctionParameterDataTypeForTarget(row.ProcedureName, row.ParameterName, i, typeValue) :
                                                  Strings.UnsupportedFunctionParameterDataType(row.ProcedureName, row.ParameterName, i, typeValue),
                                                  (int)ModelBuilderErrorCode.UnsupportedType,
                                                  EdmSchemaErrorSeverity.Warning));
                }
            }
 
            string functionName = EntityModelSchemaGenerator.CreateValidEcmaName(row.ProcedureName, 'f');
            functionName = session.UsedTypeNames.AdjustIdentifier(functionName);
            FunctionParameter[] returnParameters = 
                returnParameter == null ? new FunctionParameter[0] : new FunctionParameter[] {returnParameter};
            EdmFunction function = new EdmFunction(functionName,
                _namespaceName,
                DataSpace.SSpace,
                new EdmFunctionPayload
                {
                    Schema = row.Schema,
                    StoreFunctionName = functionName != row.ProcedureName ? row.ProcedureName : null,
                    IsAggregate = row.IsIsAggregate,
                    IsBuiltIn = row.IsBuiltIn,
                    IsNiladic = row.IsNiladic,
                    IsComposable = row.IsComposable,
                    ReturnParameters = returnParameters,
                    Parameters = functionParameters.ToArray()
                });
            function.SetReadOnly();
 
            session.AddErrorsForType(function, errors);
            if (isValid)
            {
                session.Functions.Add(function);
            }
            else
            {
                session.InvalidTypes.Add(function);
            }
        }
 
        private TypeUsage GetScalarFunctionTypeUsage(LoadMethodSessionState session, string dataType, out bool excludedForTarget)
        {
            PrimitiveType primitiveType;
            if (session.TryGetStorePrimitiveType(dataType, out primitiveType, out excludedForTarget))
            {
                TypeUsage usage = TypeUsage.Create(primitiveType, FacetValues.NullFacetValues);
                return usage;
            }
            return null;
        }
 
        private void CreateAssociationTypes(LoadMethodSessionState session)
        {
            string currentRelationshipId = string.Empty;
            List<RelationshipDetailsRow> columns = new List<RelationshipDetailsRow>();
            foreach (RelationshipDetailsRow row in _loader.LoadRelationships(session.Filters))
            {
                string rowRelationshipId = row.RelationshipId;
                if (rowRelationshipId != currentRelationshipId)
                {
                    if (!string.IsNullOrEmpty(currentRelationshipId))
                    {
                        CreateAssociationType(session, columns);
                        columns.Clear();
                    }
                    currentRelationshipId = rowRelationshipId;
                }
 
                columns.Add(row);
            }
 
            if (!string.IsNullOrEmpty(currentRelationshipId))
            {
                CreateAssociationType(session, columns);
            }
        }
 
        private void CreateAssociationType(LoadMethodSessionState session,
            List<RelationshipDetailsRow> columns)
        {
            Debug.Assert(columns.Count != 0, "should have at least one column");
 
            RelationshipDetailsRow firstRow = columns[0];
 
            // get the entity types for the ends
            EntityType pkEntityType;
            EntityType fkEntityType;
            if (!TryGetEndEntities(session, firstRow, out pkEntityType, out fkEntityType))
            {
                return;
            }
 
            if (!AreRelationshipColumnsTheTypesEntireKey(pkEntityType, columns, r => r.PKColumn))
            {
                session.AddErrorsForType(pkEntityType, new EdmSchemaError(Strings.UnsupportedDbRelationship(firstRow.RelationshipName), (int)ModelBuilderErrorCode.UnsupportedDbRelationship, EdmSchemaErrorSeverity.Warning));
                return;
                         
            }
            UniqueIdentifierService usedEndNames = new UniqueIdentifierService(false);
            // figure out the lower bound of the pk end
            bool someFkColmnsAreNullable;
            if (_targetEntityFrameworkVersion == EntityFrameworkVersions.Version1)
            {
                someFkColmnsAreNullable = AreAllFkKeyColumnsNullable(fkEntityType, columns);
            }
            else
            {
                someFkColmnsAreNullable = AreAnyFkKeyColumnsNullable(fkEntityType, columns);
            }
 
            RelationshipMultiplicity pkMultiplicity = someFkColmnsAreNullable ? RelationshipMultiplicity.ZeroOrOne : RelationshipMultiplicity.One;
            //Get the Delete Action for the end and set it.
            //The only DeleteAction we support is Cascade, ignor all others for now.
            OperationAction onDeleteAction = OperationAction.None;
            if (firstRow.RelationshipIsCascadeDelete)
            {
                onDeleteAction = OperationAction.Cascade;
            }
            
            AssociationEndMember pkEnd = CreateAssociationEnd( session,
                        pkEntityType,
                        pkMultiplicity,
                        usedEndNames, onDeleteAction);
 
            RelationshipMultiplicity fkMultiplicity = RelationshipMultiplicity.Many;
 
            if ( !someFkColmnsAreNullable &&
                 AreRelationshipColumnsTheTypesEntireKey(fkEntityType, columns, r => r.FKColumn))
            {
                // both the pk and fk side columns are the keys of their types
                // so this is a 1 to one relationship
                fkMultiplicity = RelationshipMultiplicity.ZeroOrOne;
            }
 
            AssociationEndMember fkEnd = CreateAssociationEnd(session, 
                        fkEntityType, 
                        fkMultiplicity,
                        usedEndNames, OperationAction.None);
 
 
            // create the type
            string typeName = session.UsedTypeNames.AdjustIdentifier(firstRow.RelationshipName);
            AssociationType type = new AssociationType(typeName, 
                _namespaceName, false, DataSpace.SSpace);
            type.AddMember(pkEnd);
            type.AddMember(fkEnd);
 
            List<EdmSchemaError> errors = new List<EdmSchemaError>();
            bool isValid = CreateReferentialConstraint(session,
                        type,
                        pkEnd,
                        fkEnd,
                        columns,
                        errors);
 
 
            string errorMessage;
 
            // We can skip most validation checks if the FKs are directly surfaced (since we can produce valid mappings in these cases).
            if (!this.GenerateForeignKeyProperties)
            {
                if (IsFkPartiallyContainedInPK(type, out errorMessage))
                {
                    errors.Add(new EdmSchemaError(
                        errorMessage,
                        (int)ModelBuilderErrorCode.UnsupportedForeinKeyPattern,
                        EdmSchemaErrorSeverity.Warning));
                    isValid = false;
                }
 
                if (isValid)
                {
                    //Now check if any FK (which could also be a PK) is shared among multiple Associations (ie shared via foreign key constraint).
                    // To do this we check if the Association Type being generated has any dependent property which is also a dependent in one of the association typed already added.
                    //If so, we keep one Association and throw the rest away.
 
                    foreach (var toPropertyOfAddedAssociation in session.AssociationTypes.SelectMany(t => t.ReferentialConstraints.SelectMany(refconst => refconst.ToProperties)))
                    {
                        foreach (var toProperty in type.ReferentialConstraints.SelectMany(refconst => refconst.ToProperties))
                        {
                            if (toProperty.DeclaringType.Equals(toPropertyOfAddedAssociation.DeclaringType) && toProperty.Equals(toPropertyOfAddedAssociation))
                            {
                                errors.Add(new EdmSchemaError(
                                    Strings.SharedForeignKey(type.Name, toProperty, toProperty.DeclaringType),
                                    (int)ModelBuilderErrorCode.SharedForeignKey,
                                    EdmSchemaErrorSeverity.Warning));
 
                                isValid = false;
                                break;
                            }
                        }
 
                        if (!isValid)
                        {
                            break;
                        }
                    }
                }
            }
 
            if (isValid)
            { 
                session.AssociationTypes.Add(type);
            }
            else
            {
                session.InvalidTypes.Add(type);
                session.RelationshipEndTypeLookup.Remove(pkEnd);
                session.RelationshipEndTypeLookup.Remove(fkEnd);
            }
 
 
            type.SetReadOnly();
            session.AddErrorsForType(type, errors);
        }
 
        private bool TryGetEndEntities(
            LoadMethodSessionState session,
            RelationshipDetailsRow row,
            out EntityType pkEntityType,
            out EntityType fkEntityType)
        {
            RelationshipDetailsCollection table = row.Table;
            DbObjectKey pkKey = new DbObjectKey(row[table.PKCatalogColumn],
                        row[table.PKSchemaColumn],
                        row[table.PKTableColumn], DbObjectType.Unknown);
            DbObjectKey fkKey = new DbObjectKey(row[table.FKCatalogColumn],
                        row[table.FKSchemaColumn],
                        row[table.FKTableColumn], DbObjectType.Unknown);
            
            bool worked = session.TryGetEntity(pkKey, out pkEntityType);
            worked &= session.TryGetEntity(fkKey, out fkEntityType);
 
            return worked;
        }
 
        private static bool AreRelationshipColumnsTheTypesEntireKey( 
            EntityType entity, 
            List<RelationshipDetailsRow> columns,
            Func<RelationshipDetailsRow, string> getColumnName)
        {
            if (entity.KeyMembers.Count != columns.Count)
            {
                // to be the entire key,
                // must have the same number of columns
                return false;
            }
            
            foreach (RelationshipDetailsRow row in columns)
            {
                if (!entity.KeyMembers.Contains(getColumnName(row)))
                {
                    // not a key
                    return false;
                }
            }
            return true;
        }
 
        private static bool AreAnyFkKeyColumnsNullable(
            EntityType entity,
            List<RelationshipDetailsRow> columns)
        {
            foreach (RelationshipDetailsRow row in columns)
            {
                EdmProperty property;
                if (entity.Properties.TryGetValue(row.FKColumn, false, out property))
                {
                    if (property.Nullable)
                    {
                        return true;
                    }
                }
                else
                {
                    Debug.Fail("Why didn't we find the column?");
                    return false;
                }
            }
            return false;
        }
 
        private static bool AreAllFkKeyColumnsNullable(
            EntityType entity,
            List<RelationshipDetailsRow> columns)
        {
            foreach (RelationshipDetailsRow row in columns)
            {
                EdmProperty property;
                if (entity.Properties.TryGetValue(row.FKColumn, false, out property))
                {
                    if (!property.Nullable)
                    {
                        return false;
                    }
                }
                else
                {
                    Debug.Fail("Why didn't we find the column?");
                    return false;
                }
            }
            return true;
        }
 
        private AssociationEndMember CreateAssociationEnd(LoadMethodSessionState session, 
            EntityType type, 
            RelationshipMultiplicity multiplicity,
            UniqueIdentifierService usedEndNames,
            OperationAction deleteAction
            )
        {
            string role = usedEndNames.AdjustIdentifier(type.Name);
            RefType refType = type.GetReferenceType();
            AssociationEndMember end = new AssociationEndMember(role, refType, multiplicity);
            end.DeleteBehavior = deleteAction;
            session.RelationshipEndTypeLookup.Add(end, type);
            return end;
        }
 
        private bool CreateReferentialConstraint(LoadMethodSessionState session,
            AssociationType association,
            AssociationEndMember pkEnd,
            AssociationEndMember fkEnd,
            List<RelationshipDetailsRow> columns,
            List<EdmSchemaError> errors)
        {
            EdmProperty[] fromProperties = new EdmProperty[columns.Count];
            EdmProperty[] toProperties = new EdmProperty[columns.Count];
            EntityType pkEntityType = session.RelationshipEndTypeLookup[pkEnd];
            EntityType fkEntityType = session.RelationshipEndTypeLookup[fkEnd];
            for (int index = 0; index < columns.Count; index++)
            {
                EdmProperty property;
 
                if(!pkEntityType.Properties.TryGetValue(columns[index].PKColumn, false, out property))
                {
                    errors.Add(
                        new EdmSchemaError(
                          Strings.AssociationMissingKeyColumn(
                            pkEntityType.Name,
                            fkEntityType.Name,
                            pkEntityType.Name + "." + columns[index].PKColumn),
                          (int)ModelBuilderErrorCode.AssociationMissingKeyColumn,
                          EdmSchemaErrorSeverity.Warning));
                    return false;
                }
                fromProperties[index] = property;
 
                if(!fkEntityType.Properties.TryGetValue(columns[index].FKColumn, false, out property))
                {
                    errors.Add(
                        new EdmSchemaError(
                        Strings.AssociationMissingKeyColumn(
                            pkEntityType.Name,
                            fkEntityType.Name,
                            fkEntityType.Name + "." + columns[index].FKColumn),
                            (int)ModelBuilderErrorCode.AssociationMissingKeyColumn,
                        EdmSchemaErrorSeverity.Warning));
                    return false;
                }
                toProperties[index] = property;
            }
 
            ReferentialConstraint constraint = new ReferentialConstraint(pkEnd,
                fkEnd,
                fromProperties,
                toProperties);
 
            association.AddReferentialConstraint(constraint);
            return true;
        }
 
        static internal bool IsFkPartiallyContainedInPK(AssociationType association, out string errorMessage)
        {
            ReferentialConstraint constraint = association.ReferentialConstraints[0];
            EntityType toType = (EntityType)constraint.ToProperties[0].DeclaringType;
 
            bool toPropertiesAreFullyContainedInPk = true;
            bool toPropertiesContainedAtLeastOnePK = false;
 
            foreach (EdmProperty edmProperty in constraint.ToProperties)
            {
                // check if there is at least one to property is not primary key
                toPropertiesAreFullyContainedInPk &= toType.KeyMembers.Contains(edmProperty);
                // check if there is one to property is primary key
                toPropertiesContainedAtLeastOnePK |= toType.KeyMembers.Contains(edmProperty); 
            }
            if (!toPropertiesAreFullyContainedInPk && toPropertiesContainedAtLeastOnePK)
            {
                string foreignKeys = MetadataUtil.MembersToCommaSeparatedString((System.Collections.IEnumerable)constraint.ToProperties);
                string primaryKeys = MetadataUtil.MembersToCommaSeparatedString((System.Collections.IEnumerable)toType.KeyMembers);
                errorMessage = Strings.UnsupportedForeignKeyPattern(association.Name, foreignKeys, primaryKeys, toType.Name);
                return true;
            }
            errorMessage = "";
            return false;
        }
 
        private void CreateViewEntityTypes(LoadMethodSessionState session)
        {
            CreateTableTypes(session, _loader.LoadViewDetails(session.Filters), CreateEntityType, DbObjectType.View);
        }
 
        private void CreateTableEntityTypes(LoadMethodSessionState session)
        {
            CreateTableTypes(session, _loader.LoadTableDetails(session.Filters), CreateEntityType, DbObjectType.Table);
        }
 
        private void CreateTvfReturnRowTypes(LoadMethodSessionState session)
        {
            CreateTableTypes(session, _loader.LoadFunctionReturnTableDetails(session.Filters), CreateTvfReturnRowType, DbObjectType.Function);
        }
 
        private void CreateTableTypes(
            LoadMethodSessionState session,
            IEnumerable<DataRow> tableDetailsRows,
            Action<
                LoadMethodSessionState/*session*/, 
                IList<TableDetailsRow>/*columns*/, 
                ICollection<string>/*primaryKeys*/,
                DbObjectType/*objectType*/,
                List<EdmSchemaError>/*errors*/> createType,
            DbObjectType objectType)
        {
            DbObjectKey currentKey = new DbObjectKey();
            List<TableDetailsRow> singleTableColumns = new List<TableDetailsRow>();
            List<string> primaryKeys = new List<string>();
            foreach (TableDetailsRow row in tableDetailsRows)
            {
                DbObjectKey rowKey = row.CreateDbObjectKey(objectType);
                if (rowKey != currentKey)
                {
                    if (singleTableColumns.Count != 0)
                    {
                        createType(
                            session,
                            singleTableColumns,
                            primaryKeys,
                            objectType, 
                            null);
 
                        singleTableColumns.Clear();
                        primaryKeys.Clear();
                    }
                    currentKey = rowKey;
                }
 
                singleTableColumns.Add(row);
                if (row.IsPrimaryKey)
                {
                    primaryKeys.Add(row.ColumnName);
                }
            }
 
            // pick up the last one
            if (singleTableColumns.Count != 0)
            {
                createType(
                    session,
                    singleTableColumns,
                    primaryKeys,
                    objectType,
                    null);
 
            }
        }
 
        private void CreateEntityType(
            LoadMethodSessionState session, 
            IList<TableDetailsRow> columns, 
            ICollection<string> primaryKeys,
            DbObjectType objectType,
            List<EdmSchemaError> errors)
        {
            Debug.Assert(columns.Count != 0, "Trying to create an EntityType with 0 properties");
            Debug.Assert(primaryKeys != null, "primaryKeys != null");
 
            DbObjectKey tableKey = columns[0].CreateDbObjectKey(objectType);
            if (errors == null)
            {
                errors = new List<EdmSchemaError>();
            }
 
            //
            // Handle Tables without explicit declaration of keys
            //
            EntityCreationStatus status = EntityCreationStatus.Normal;
            if (primaryKeys.Count == 0)
            {
                List<string> pKeys = new List<string>(columns.Count);
                session.AddTableWithoutKey(tableKey);
                if (InferKeyColumns(session, columns, pKeys, tableKey, ref primaryKeys))
                {
                    errors.Add(new EdmSchemaError(
                        Strings.NoPrimaryKeyDefined(tableKey),
                                    (int)ModelBuilderErrorCode.NoPrimaryKeyDefined,
                                     EdmSchemaErrorSeverity.Warning));
                    status = EntityCreationStatus.ReadOnly;
                }
                else
                {
                    errors.Add(new EdmSchemaError(
                        Strings.CannotCreateEntityWithNoPrimaryKeyDefined(tableKey),
                                        (int)ModelBuilderErrorCode.CannotCreateEntityWithoutPrimaryKey,
                                        EdmSchemaErrorSeverity.Warning));
                    status = EntityCreationStatus.Invalid;
                }
            }
 
            Debug.Assert(primaryKeys == null || primaryKeys.Count > 0,"There must be at least one key columns at this point in time");
 
            IList<string> excludedColumns;
            var properties = CreateEdmProperties(session, columns, tableKey, errors, out excludedColumns);
 
            var excludedKeyColumns = (primaryKeys != null ? primaryKeys.Intersect(excludedColumns) : new string[0]).ToArray();
 
            if (primaryKeys != null && excludedKeyColumns.Length == 0)
            {
                foreach (EdmMember pkColumn in properties.Where(p => primaryKeys.Contains(p.Name)))
                {
                    if (!MetadataUtil.IsValidKeyType(_targetEntityFrameworkVersion, pkColumn.TypeUsage.EdmType))
                    {
                        // make it a read-only table by calling this method recursively with no keys
                        errors = new List<EdmSchemaError>();
                        var tableColumn = columns.Where(c => c.ColumnName == pkColumn.Name).Single();
                        errors.Add(new EdmSchemaError(Strings.InvalidTypeForPrimaryKey(tableColumn.GetMostQualifiedTableName(),
                            tableColumn.ColumnName,
                            tableColumn.DataType),
                            (int)ModelBuilderErrorCode.InvalidKeyTypeFound,
                            EdmSchemaErrorSeverity.Warning));
 
                        string[] keyColumns = new string[0];
                        CreateEntityType(session, columns, keyColumns, objectType, errors);
                        return;
                    }
                }
            }
 
            if (excludedKeyColumns.Length > 0)
            {
                // see if we have any keys left
                if (primaryKeys != null && excludedKeyColumns.Length < primaryKeys.Count)
                {
                    primaryKeys = primaryKeys.Except(excludedKeyColumns).ToList();
                    status = EntityCreationStatus.ReadOnly;
                }
                else
                {
                    primaryKeys = null;
                    status = EntityCreationStatus.Invalid;
                }
 
                foreach (string columnName in excludedKeyColumns)
                {
                    if (status == EntityCreationStatus.ReadOnly)
                    {
                        errors.Add(new EdmSchemaError(
                            Strings.ExcludedColumnWasAKeyColumnEntityIsReadOnly(columnName, columns[0].GetMostQualifiedTableName()),
                            (int)ModelBuilderErrorCode.ExcludedColumnWasAKeyColumn,
                            EdmSchemaErrorSeverity.Warning));
                    }
                    else
                    {
                        Debug.Assert(status == EntityCreationStatus.Invalid, "Did we change some code above to make it possible to be something different?");
                        errors.Add(new EdmSchemaError(
                            Strings.ExcludedColumnWasAKeyColumnEntityIsInvalid(columnName, columns[0].GetMostQualifiedTableName()),
                            (int)ModelBuilderErrorCode.ExcludedColumnWasAKeyColumn,
                            EdmSchemaErrorSeverity.Warning));
                    }
                }
            }
 
            string typeName = session.UsedTypeNames.AdjustIdentifier(columns[0].TableName);
            var entityType = new EntityType(typeName, _namespaceName, DataSpace.SSpace, primaryKeys, properties);
            entityType.SetReadOnly();
 
            switch (status)
            {
                case EntityCreationStatus.Normal:
                    session.AddEntity(tableKey, entityType);
                    break;
                case EntityCreationStatus.ReadOnly:
                    session.AddEntity(tableKey, entityType);
                    session.ReadOnlyEntities.Add(entityType);
                    break;
                default:
                    Debug.Assert(status == EntityCreationStatus.Invalid, "did you add a new value?");
                    session.InvalidTypes.Add(entityType);
                    break;
            }
 
            session.AddErrorsForType(entityType, errors);
        }
 
        private void CreateTvfReturnRowType(
            LoadMethodSessionState session,
            IList<TableDetailsRow> columns,
            ICollection<string> primaryKeys,
            DbObjectType objectType,
            List<EdmSchemaError> errors)
        {
            Debug.Assert(columns.Count != 0, "Trying to create a RowType with 0 properties");
            Debug.Assert(primaryKeys != null, "primaryKeys != null");
 
            DbObjectKey tableKey = columns[0].CreateDbObjectKey(objectType);
            if (errors == null)
            {
                errors = new List<EdmSchemaError>();
            }
 
            IList<string> excludedColumns;
            var properties = CreateEdmProperties(session, columns, tableKey, errors, out excludedColumns);
 
            var rowType = new RowType(properties);
            rowType.SetReadOnly();
            session.AddTvfReturnType(tableKey, rowType);
            if (rowType.Properties.Count == 0)
            {
                session.InvalidTypes.Add(rowType);
            }
            session.AddErrorsForType(rowType, errors);
        }
 
        private IList<EdmProperty> CreateEdmProperties(
            LoadMethodSessionState session,
            IList<TableDetailsRow> columns,
            DbObjectKey tableKey,
            List<EdmSchemaError> errors,
            out IList<string> excludedColumns)
        {
            Debug.Assert(columns.Count != 0, "columns.Count != 0");
            Debug.Assert(errors != null, "errors != null");
 
            var members = new List<EdmProperty>();
            excludedColumns = new List<string>();
            foreach (TableDetailsRow row in columns)
            {
                PrimitiveType primitiveType;
                bool excludedForTarget = false;
                if (row.IsDataTypeNull() || !session.TryGetStorePrimitiveType(row.DataType, out primitiveType, out excludedForTarget))
                {
                    string message;
                    if (!row.IsDataTypeNull())
                    {
                        message = excludedForTarget ?
                            Strings.UnsupportedDataTypeForTarget(row.DataType, row.GetMostQualifiedTableName(), row.ColumnName) :
                            Strings.UnsupportedDataType(row.DataType, row.GetMostQualifiedTableName(), row.ColumnName);
                    }
                    else
                    {
                        message = Strings.UnsupportedDataTypeUnknownType(row.ColumnName, row.GetMostQualifiedTableName());
                    }
 
                    errors.Add(new EdmSchemaError(message, (int)ModelBuilderErrorCode.UnsupportedType, EdmSchemaErrorSeverity.Warning));
                    excludedColumns.Add(row.ColumnName);
 
                    continue;
                }
 
                Dictionary<string, Facet> facets = primitiveType.GetAssociatedFacetDescriptions().ToDictionary(fd => fd.FacetName, fd => fd.DefaultValueFacet);
                facets[DbProviderManifest.NullableFacetName] = Facet.Create(facets[DbProviderManifest.NullableFacetName].Description, row.IsNullable);
 
                if (primitiveType.PrimitiveTypeKind == PrimitiveTypeKind.Decimal)
                {
                    Facet precision;
                    if (facets.TryGetValue(DbProviderManifest.PrecisionFacetName, out precision))
                    {
                        if (!row.IsPrecisionNull() && !precision.Description.IsConstant)
                        {
                            if (row.Precision < precision.Description.MinValue || row.Precision > precision.Description.MaxValue)
                            {
                                DbObjectKey key = row.CreateDbObjectKey(tableKey.ObjectType);
                                errors.Add(new EdmSchemaError(
                                    Strings.ColumnFacetValueOutOfRange(
                                        DbProviderManifest.PrecisionFacetName,
                                        row.Precision,
                                        precision.Description.MinValue,
                                        precision.Description.MaxValue,
                                        row.ColumnName,
                                        key),
                                    (int)ModelBuilderErrorCode.FacetValueOutOfRange,
                                    EdmSchemaErrorSeverity.Warning));
                                excludedColumns.Add(row.ColumnName);
                                continue;
                            }
                            facets[precision.Name] = Facet.Create(precision.Description, (byte)row.Precision);
                        }
                    }
                    Facet scale;
                    if (facets.TryGetValue(DbProviderManifest.ScaleFacetName, out scale))
                    {
                        if (!row.IsScaleNull() && !scale.Description.IsConstant)
                        {
                            if (row.Scale < scale.Description.MinValue || row.Scale > scale.Description.MaxValue)
                            {
                                DbObjectKey key = row.CreateDbObjectKey(tableKey.ObjectType);
                                errors.Add(new EdmSchemaError(
                                    Strings.ColumnFacetValueOutOfRange(
                                        DbProviderManifest.ScaleFacetName,
                                        row.Scale,
                                        scale.Description.MinValue,
                                        scale.Description.MaxValue,
                                        row.ColumnName,
                                        key),
                                    (int)ModelBuilderErrorCode.FacetValueOutOfRange,
                                    EdmSchemaErrorSeverity.Warning));
                                excludedColumns.Add(row.ColumnName);
                                continue;
                            }
                            facets[scale.Name] = Facet.Create(scale.Description, (byte)row.Scale);
                        }
                    }
                }
                else if (primitiveType.PrimitiveTypeKind == PrimitiveTypeKind.DateTime || 
                         primitiveType.PrimitiveTypeKind == PrimitiveTypeKind.Time ||
                         primitiveType.PrimitiveTypeKind == PrimitiveTypeKind.DateTimeOffset)
                {
                    Facet datetimePrecision;
                    if (facets.TryGetValue(DbProviderManifest.PrecisionFacetName, out datetimePrecision))
                    {
                        if (!row.IsDateTimePrecisionNull() && !datetimePrecision.Description.IsConstant)
                        {
                            if (row.DateTimePrecision < datetimePrecision.Description.MinValue || row.DateTimePrecision > datetimePrecision.Description.MaxValue)
                            {
                                DbObjectKey key = row.CreateDbObjectKey(tableKey.ObjectType);
                                errors.Add(new EdmSchemaError(
                                    Strings.ColumnFacetValueOutOfRange(
                                        DbProviderManifest.PrecisionFacetName,
                                        row.DateTimePrecision,
                                        datetimePrecision.Description.MinValue,
                                        datetimePrecision.Description.MaxValue,
                                        row.ColumnName,
                                        key),
                                    (int)ModelBuilderErrorCode.FacetValueOutOfRange,
                                    EdmSchemaErrorSeverity.Warning));
                                excludedColumns.Add(row.ColumnName);
                                continue;
                            }
                            facets[datetimePrecision.Name] = Facet.Create(datetimePrecision.Description, (byte)row.DateTimePrecision);
                        }
                    }
                }
                else if (primitiveType.PrimitiveTypeKind == PrimitiveTypeKind.String ||
                         primitiveType.PrimitiveTypeKind == PrimitiveTypeKind.Binary)
                {
                    Facet maxLength;
                    if (facets.TryGetValue(DbProviderManifest.MaxLengthFacetName, out maxLength))
                    {
                        if (!row.IsMaximumLengthNull() && !maxLength.Description.IsConstant)
                        {
                            if (row.MaximumLength < maxLength.Description.MinValue || row.MaximumLength > maxLength.Description.MaxValue)
                            {
                                DbObjectKey key = row.CreateDbObjectKey(tableKey.ObjectType);
                                errors.Add(new EdmSchemaError(
                                    Strings.ColumnFacetValueOutOfRange(
                                        DbProviderManifest.MaxLengthFacetName,
                                        row.MaximumLength,
                                        maxLength.Description.MinValue,
                                        maxLength.Description.MaxValue,
                                        row.ColumnName,
                                        key),
                                    (int)ModelBuilderErrorCode.FacetValueOutOfRange,
                                    EdmSchemaErrorSeverity.Warning));
                                excludedColumns.Add(row.ColumnName);
                                continue;
                            }
                            facets[maxLength.Name] = Facet.Create(maxLength.Description, row.MaximumLength);
                        }
                    }
                }
 
                if (!row.IsIsIdentityNull() && row.IsIdentity)
                {
                    Facet facet = Facet.Create(System.Data.Metadata.Edm.Converter.StoreGeneratedPatternFacet, StoreGeneratedPattern.Identity);
                    facets.Add(facet.Name, facet);
                }
                else if (!row.IsIsServerGeneratedNull() && row.IsServerGenerated)
                {
                    Facet facet = Facet.Create(System.Data.Metadata.Edm.Converter.StoreGeneratedPatternFacet, StoreGeneratedPattern.Computed);
                    facets.Add(facet.Name, facet);
                }
 
                members.Add(new EdmProperty(row.ColumnName, TypeUsage.Create(primitiveType, facets.Values)));
            }
 
            return members;
        }
 
        private bool InferKeyColumns(LoadMethodSessionState session, IList<TableDetailsRow> columns, List<string> pKeys, DbObjectKey tableKey, ref ICollection<string> primaryKeys)
        {
            for (int i = 0; i < columns.Count; i++)
            {
                if (!columns[i].IsNullable)
                {
                    PrimitiveType primitiveType;
                    bool _;
                    if (session.TryGetStorePrimitiveType(columns[i].DataType, out primitiveType, out _) &&
                        MetadataUtil.IsValidKeyType(_targetEntityFrameworkVersion, primitiveType))
                    {
                        pKeys.Add(columns[i].ColumnName);
                    }
                }
            }
 
            // if there are valid key column candidates, make them the new key columns
            if (pKeys.Count > 0)
            {
                primaryKeys = pKeys;
            }
            else
            {
                primaryKeys = null;
            }
 
            return primaryKeys != null;
        }
 
        /// <summary>
        /// Populates DefiningQuery attribute of RO view entities
        /// </summary>
        /// <param name="viewEntitySets"></param>
        /// <param name="entityContainer"></param>
        /// <param name="session"></param>
        private void FixupKeylessEntitySets(EntityContainer entityContainer, LoadMethodSessionState session)
        {
            // if there are views to process
            if (session.ReadOnlyEntities.Count > 0)
            {
                //
                // create 'bogus' metadataworkspace
                //
                MetadataWorkspace metadataWorkspace = CreateMetadataWorkspace(entityContainer, session);
 
                if (null == metadataWorkspace)
                {
                    // failed to create bogus metadataworkspace
                    return;
                }
 
                //
                // For all tables/views that we could infer valid keys, update DefiningQuery with 
                // provider specific ReadOnly view SQL
                //
                foreach (EntityType entityType in session.ReadOnlyEntities)
                {
                    EntitySet entitySet = session.EntityTypeToSet[entityType];
                    DbObjectKey key = session.GetKey(entityType);
                    
                    // add properties that make it possible for the designer to track back these 
                    // types to their source db objects
                    List<MetadataProperty> properties = new List<MetadataProperty>();
                    if (key.Schema != null)
                    {
                        properties.Add(System.Data.EntityModel.SchemaObjectModel.SchemaElement.CreateMetadataPropertyFromOtherNamespaceXmlArtifact(DesignXmlConstants.EntityStoreSchemaGeneratorNamespace, DesignXmlConstants.EntityStoreSchemaGeneratorSchemaAttributeName, key.Schema));
                    }
                    properties.Add(System.Data.EntityModel.SchemaObjectModel.SchemaElement.CreateMetadataPropertyFromOtherNamespaceXmlArtifact(DesignXmlConstants.EntityStoreSchemaGeneratorNamespace, DesignXmlConstants.EntityStoreSchemaGeneratorNameAttributeName, key.TableName));
                    entitySet.AddMetadataProperties(properties);
 
                    FixupViewEntitySetDefiningQuery(entitySet, metadataWorkspace);
                }
            }
        }
 
        /// <summary>
        /// Creates 'transient' metadataworkspace based on store schema (EntityContainer) and trivial C-S mapping
        /// </summary>
        /// <param name="entityContainer"></param>
        /// <param name="session"></param>
        /// <returns></returns>
        private MetadataWorkspace CreateMetadataWorkspace(EntityContainer entityContainer, LoadMethodSessionState session)
        {
            MetadataWorkspace metadataWorkspace = new MetadataWorkspace();
            
            EntityModelSchemaGenerator modelGen = new EntityModelSchemaGenerator(entityContainer);
            modelGen.GenerateForeignKeyProperties = this.GenerateForeignKeyProperties;
 
            IEnumerable<EdmSchemaError> errors = modelGen.GenerateMetadata();
 
            if (EntityStoreSchemaGenerator.HasErrorSeverityErrors(errors))
            {
                // this is a 'transient' metadataworkspace 
                // no errors from this metadataworkspace should be shown to the user
                return null;
            }
 
            // register edmitemcollection
            metadataWorkspace.RegisterItemCollection(modelGen.EdmItemCollection);
            
            // register StoreItemCollection
            metadataWorkspace.RegisterItemCollection(session.ItemCollection);
 
            // register mapping
            using (MemoryStream memStream = new MemoryStream())
            {
                using (XmlWriter xmlWriter = XmlWriter.Create(memStream))
                {
                    modelGen.WriteStorageMapping(xmlWriter);
                    xmlWriter.Close();
                }
                
                memStream.Seek(0, SeekOrigin.Begin);
 
                using (XmlReader xmlReader = XmlReader.Create(memStream))
                {
                    List<XmlReader> xmlReaders = new List<XmlReader>();
                    xmlReaders.Add(xmlReader);
                    metadataWorkspace.RegisterItemCollection(new StorageMappingItemCollection(modelGen.EdmItemCollection, 
                                                                                              session.ItemCollection, 
                                                                                              xmlReaders));
                }
            }
 
            return metadataWorkspace;
        }
 
        /// <summary>
        /// Generates provider specific, read only SQL and updates entitySet DefiningQuery 
        /// </summary>
        /// <param name="entitySet"></param>
        /// <param name="metadataWorkspace"></param>
        private void FixupViewEntitySetDefiningQuery(EntitySet entitySet, MetadataWorkspace metadataWorkspace)
        {
            DbExpressionBinding inputBinding = DbExpressionBuilder.BindAs(DbExpressionBuilder.Scan(entitySet), entitySet.Name);
            List<KeyValuePair<string, DbExpression>> projectList = new List<KeyValuePair<string, DbExpression>>(entitySet.ElementType.Members.Count);
            foreach (EdmMember member in entitySet.ElementType.Members)
            {
                Debug.Assert(member.BuiltInTypeKind == BuiltInTypeKind.EdmProperty, "Every member must be a edmproperty");
                EdmProperty propertyInfo = (EdmProperty)member;
                projectList.Add(new KeyValuePair<string, DbExpression>(member.Name,
                                                                       DbExpressionBuilder.Property(inputBinding.Variable, propertyInfo)));
            }
            DbExpression query = inputBinding.Project(DbExpressionBuilder.NewRow(projectList));
            DbQueryCommandTree dbCommandTree = new DbQueryCommandTree(metadataWorkspace, DataSpace.SSpace, query);
 
            //
            // get provider SQL and set entitySet DefiningQuery
            //
            entitySet.DefiningQuery = (DbProviderServices.GetProviderServices(_loader.EntityConnection.StoreProviderFactory)
                                                            .CreateCommandDefinition(dbCommandTree))
                                                                .CreateCommand().CommandText;
 
            Debug.Assert(!String.IsNullOrEmpty(entitySet.DefiningQuery), "DefiningQuery must not be null or empty");
        }
    }
}