//---------------------------------------------------------------------
// <copyright file="EntityCommand.cs" company="Microsoft">
//      Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//
// @owner  [....]
// @backupOwner [....]
//---------------------------------------------------------------------

namespace System.Data.EntityClient
{
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Data.Common;
    using System.Data.Common.CommandTrees;
    using System.Data.Common.CommandTrees.ExpressionBuilder;
    using System.Data.Common.EntitySql;
    using System.Data.Common.QueryCache;
    using System.Data.Common.Utils;
    using System.Data.Metadata.Edm;
    using System.Diagnostics;
    using System.Linq;

    /// <summary>
    /// Class representing a command for the conceptual layer
    /// </summary>
    public sealed class EntityCommand : DbCommand
    {
        #region Fields
        private const int InvalidCloseCount = -1;

        private bool _designTimeVisible;
        private string _esqlCommandText;
        private EntityConnection _connection;
        private DbCommandTree _preparedCommandTree;
        private EntityParameterCollection _parameters;
        private int? _commandTimeout;
        private CommandType _commandType;
        private EntityTransaction _transaction;
        private UpdateRowSource _updatedRowSource;
        private EntityCommandDefinition _commandDefinition;
        private bool _isCommandDefinitionBased;
        private DbCommandTree _commandTreeSetByUser;
        private DbDataReader _dataReader;
        private bool _enableQueryPlanCaching;
        private DbCommand _storeProviderCommand;
        #endregion

        /// <summary>
        /// Constructs the EntityCommand object not yet associated to a connection object
        /// </summary>
        public EntityCommand()
        {
            GC.SuppressFinalize(this);

            // Initalize the member field with proper default values
            this._designTimeVisible = true;
            this._commandType = CommandType.Text;
            this._updatedRowSource = UpdateRowSource.Both;
            this._parameters = new EntityParameterCollection();

            // Future Enhancement: (See SQLPT #300004256) At some point it would be  
            // really nice to read defaults from a global configuration, but we're not 
            // doing that today.  
            this._enableQueryPlanCaching = true;
        }

        /// <summary>
        /// Constructs the EntityCommand object with the given eSQL statement, but not yet associated to a connection object
        /// </summary>
        /// <param name="statement">The eSQL command text to execute</param>
        public EntityCommand(string statement)
            : this()
        {
            // Assign other member fields from the parameters
            this._esqlCommandText = statement;
        }

        /// <summary>
        /// Constructs the EntityCommand object with the given eSQL statement and the connection object to use
        /// </summary>
        /// <param name="statement">The eSQL command text to execute</param>
        /// <param name="connection">The connection object</param>
        public EntityCommand(string statement, EntityConnection connection)
            : this(statement)
        {
            // Assign other member fields from the parameters
            this._connection = connection;
        }

        /// <summary>
        /// Constructs the EntityCommand object with the given eSQL statement and the connection object to use
        /// </summary>
        /// <param name="statement">The eSQL command text to execute</param>
        /// <param name="connection">The connection object</param>
        /// <param name="transaction">The transaction object this command executes in</param>
        public EntityCommand(string statement, EntityConnection connection, EntityTransaction transaction)
            : this(statement, connection)
        {
            // Assign other member fields from the parameters
            this._transaction = transaction;
        }

        /// <summary>
        /// Internal constructor used by EntityCommandDefinition
        /// </summary>
        /// <param name="commandDefinition">The prepared command definition that can be executed using this EntityCommand</param>
        internal EntityCommand(EntityCommandDefinition commandDefinition)
            : this()
        {
            // Assign other member fields from the parameters
            this._commandDefinition = commandDefinition; 
            this._parameters = new EntityParameterCollection();

            // Make copies of the parameters
            foreach (EntityParameter parameter in commandDefinition.Parameters)
            {
                this._parameters.Add(parameter.Clone());
            }

            // Reset the dirty flag that was set to true when the parameters were added so that it won't say
            // it's dirty to start with
            this._parameters.ResetIsDirty();

            // Track the fact that this command was created from and represents an already prepared command definition
            this._isCommandDefinitionBased = true;
        }

        /// <summary>
        /// Constructs a new EntityCommand given a EntityConnection and an EntityCommandDefition. This 
        /// constructor is used by ObjectQueryExecution plan to execute an ObjectQuery.
        /// </summary>
        /// <param name="connection">The connection against which this EntityCommand should execute</param>
        /// <param name="commandDefinition">The prepared command definition that can be executed using this EntityCommand</param>
        internal EntityCommand(EntityConnection connection, EntityCommandDefinition entityCommandDefinition )
            : this(entityCommandDefinition)
        {
            this._connection = connection;
        }

        /// <summary>
        /// The connection object used for executing the command
        /// </summary>
        public new EntityConnection Connection
        {
            get
            {
                return this._connection;
            }
            set
            {
                ThrowIfDataReaderIsOpen();
                if (this._connection != value)
                {
                    if (null != this._connection)
                    {
                        Unprepare();
                    }
                    this._connection = value;

                    this._transaction = null;
                }
            }
        }

        /// <summary>
        /// The connection object used for executing the command
        /// </summary>
        protected override DbConnection DbConnection
        {
            get
            {
                return this.Connection;
            }
            set
            {
                this.Connection = (EntityConnection)value;
            }
        }

        /// <summary>
        /// The eSQL statement to execute, only one of the command tree or the command text can be set, not both
        /// </summary>
        public override string CommandText
        {
            get
            {
                // If the user set the command tree previously, then we cannot retrieve the command text
                if (this._commandTreeSetByUser != null)
                    throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_CannotGetCommandText);

                return this._esqlCommandText ?? "";
            }
            set
            {
                ThrowIfDataReaderIsOpen();

                // If the user set the command tree previously, then we cannot set the command text
                if (this._commandTreeSetByUser != null)
                    throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_CannotSetCommandText);

                if (this._esqlCommandText != value)
                {
                    this._esqlCommandText = value;

                    // Wipe out any preparation work we have done
                    Unprepare();

                    // If the user-defined command text or tree has been set (even to null or empty),
                    // then this command can no longer be considered command definition-based
                    this._isCommandDefinitionBased = false;
                }
            }
        }

        /// <summary>
        /// The command tree to execute, only one of the command tree or the command text can be set, not both.
        /// </summary>
        public DbCommandTree CommandTree
        {
            get
            {
                // If the user set the command text previously, then we cannot retrieve the command tree
                if (!string.IsNullOrEmpty(this._esqlCommandText))
                    throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_CannotGetCommandTree);

                return this._commandTreeSetByUser;
            }
            set
            {
                ThrowIfDataReaderIsOpen();

                // If the user set the command text previously, then we cannot set the command tree
                if (!string.IsNullOrEmpty(this._esqlCommandText))
                    throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_CannotSetCommandTree);

                // If the command type is not Text, CommandTree cannot be set
                if (CommandType.Text != CommandType)
                {
                    throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.CommandTreeOnStoredProcedureEntityCommand);
                }

                if (this._commandTreeSetByUser != value)
                {
                    this._commandTreeSetByUser = value;

                    // Wipe out any preparation work we have done
                    Unprepare();

                    // If the user-defined command text or tree has been set (even to null or empty),
                    // then this command can no longer be considered command definition-based
                    this._isCommandDefinitionBased = false;
                }
            }
        }

        /// <summary>
        /// Get or set the time in seconds to wait for the command to execute
        /// </summary>
        public override int CommandTimeout
        {
            get
            {
                // Returns the timeout value if it has been set
                if (this._commandTimeout != null)
                {
                    return this._commandTimeout.Value;
                }
                
                // Create a provider command object just so we can ask the default timeout
                if (this._connection != null && this._connection.StoreProviderFactory != null)
                {
                    DbCommand storeCommand = this._connection.StoreProviderFactory.CreateCommand();
                    if (storeCommand != null)
                    {
                        return storeCommand.CommandTimeout;
                    }
                }

                return 0;
            }
            set
            {
                ThrowIfDataReaderIsOpen();
                this._commandTimeout = value;
            }
        }

        /// <summary>
        /// The type of command being executed, only applicable when the command is using an eSQL statement and not the tree
        /// </summary>
        public override CommandType CommandType
        {
            get
            {
                return this._commandType;
            }
            set
            {
                ThrowIfDataReaderIsOpen();

                // For now, command type other than Text is not supported
                if (value != CommandType.Text && value != CommandType.StoredProcedure)
                {
                    throw EntityUtil.NotSupported(System.Data.Entity.Strings.EntityClient_UnsupportedCommandType);
                }

                this._commandType = value;
            }
        }

        /// <summary>
        /// The collection of parameters for this command
        /// </summary>
        public new EntityParameterCollection Parameters
        {
            get
            {
                return this._parameters;
            }
        }

        /// <summary>
        /// The collection of parameters for this command
        /// </summary>
        protected override DbParameterCollection DbParameterCollection
        {
            get
            {
                return this.Parameters;
            }
        }

        /// <summary>
        /// The transaction object used for executing the command
        /// </summary>
        public new EntityTransaction Transaction
        {
            get
            {
                return this._transaction;   // SQLBU 496829
            }
            set
            {
                ThrowIfDataReaderIsOpen();
                this._transaction = value;
            }
        }

        /// <summary>
        /// The transaction that this command executes in
        /// </summary>
        protected override DbTransaction DbTransaction
        {
            get
            {
                return this.Transaction;
            }
            set
            {
                this.Transaction = (EntityTransaction)value;
            }
        }

        /// <summary>
        /// Gets or sets how command results are applied to the DataRow when used by the Update method of a DbDataAdapter
        /// </summary>
        public override UpdateRowSource UpdatedRowSource
        {
            get
            {
                return this._updatedRowSource;
            }
            set
            {
                ThrowIfDataReaderIsOpen();
                this._updatedRowSource = value;
            }
        }

        /// <summary>
        /// Hidden property used by the designers
        /// </summary>
        public override bool DesignTimeVisible
        {
            get
            {
                return this._designTimeVisible;
            }
            set
            {
                ThrowIfDataReaderIsOpen();
                this._designTimeVisible = value;
                TypeDescriptor.Refresh(this);
            }
        }

        /// <summary>
        /// Enables/Disables query plan caching for this EntityCommand
        /// </summary>
        public bool EnablePlanCaching
        {
            get
            {
                return this._enableQueryPlanCaching;
            }

            set
            {
                ThrowIfDataReaderIsOpen();
                this._enableQueryPlanCaching = value;
            }
        }

        /// <summary>
        /// Cancel the execution of the command
        /// </summary>
        public override void Cancel()
        {
        }

        /// <summary>
        /// Create and return a new parameter object representing a parameter in the eSQL statement
        /// </summary>
        ///
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
        public new EntityParameter CreateParameter()
        {
            return new EntityParameter();
        }

        /// <summary>
        /// Create and return a new parameter object representing a parameter in the eSQL statement
        /// </summary>
        protected override DbParameter CreateDbParameter()
        {
            return CreateParameter();
        }

        /// <summary>
        /// Executes the command and returns a data reader for reading the results
        /// </summary>
        /// <returns>A data readerobject</returns>
        public new EntityDataReader ExecuteReader()
        {
            return ExecuteReader(CommandBehavior.Default);
        }

        /// <summary>
        /// Executes the command and returns a data reader for reading the results. May only
        /// be called on CommandType.CommandText (otherwise, use the standard Execute* methods)
        /// </summary>
        /// <param name="behavior">The behavior to use when executing the command</param>
        /// <returns>A data readerobject</returns>
        /// <exception cref="InvalidOperationException">For stored procedure commands, if called
        /// for anything but an entity collection result</exception>
        public new EntityDataReader ExecuteReader(CommandBehavior behavior)
        {
            Prepare(); // prepare the query first

            EntityDataReader reader = new EntityDataReader(this, _commandDefinition.Execute(this, behavior), behavior);
            _dataReader = reader;
            return reader;
        }

        /// <summary>
        /// Executes the command and returns a data reader for reading the results
        /// </summary>
        /// <param name="behavior">The behavior to use when executing the command</param>
        /// <returns>A data readerobject</returns>
        protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
        {
            return ExecuteReader(behavior);
        }

        /// <summary>
        /// Executes the command and discard any results returned from the command
        /// </summary>
        /// <returns>Number of rows affected</returns>
        public override int ExecuteNonQuery()
        {
            return ExecuteScalar<int>(reader => 
            {
                // consume reader before checking records affected
                CommandHelper.ConsumeReader(reader);
                return reader.RecordsAffected;
            });
        }

        /// <summary>
        /// Executes the command and return the first column in the first row of the result, extra results are ignored
        /// </summary>
        /// <returns>The result in the first column in the first row</returns>
        public override object ExecuteScalar()
        {
            return ExecuteScalar<object>(reader => 
            {
                object result = reader.Read() ? reader.GetValue(0) : null;
                // consume reader before retrieving parameters
                CommandHelper.ConsumeReader(reader);
                return result;
            });
        }

        /// <summary>
        /// Executes a reader and retrieves a scalar value using the given resultSelector delegate
        /// </summary>
        private T_Result ExecuteScalar<T_Result>(Func<DbDataReader, T_Result> resultSelector)
        {
            T_Result result;
            using (EntityDataReader reader = ExecuteReader(CommandBehavior.SequentialAccess))
            {
                result = resultSelector(reader);
            }
            return result;
        }

        /// <summary>
        /// Clear out any "compile" state
        /// </summary>
        internal void Unprepare()
        {
            this._commandDefinition = null;
            this._preparedCommandTree = null;

            // Clear the dirty flag on the parameters and parameter collection
            _parameters.ResetIsDirty();            
        }

        /// <summary>
        /// Creates a prepared version of this command
        /// </summary>
        public override void Prepare()
        {
            ThrowIfDataReaderIsOpen();
            CheckIfReadyToPrepare();

            InnerPrepare();
        }

        /// <summary>
        /// Creates a prepared version of this command without regard to the current connection state.
        /// Called by both <see cref="Prepare"/> and <see cref="ToTraceString"/>.
        /// </summary>
        private void InnerPrepare()
        {
            // Unprepare if the parameters have changed to force a reprepare
            if (_parameters.IsDirty)
            {
                Unprepare();
            }

            _commandDefinition = GetCommandDefinition();
            Debug.Assert(null != _commandDefinition, "_commandDefinition cannot be null");
        }

        /// <summary>
        /// Ensures we have the command tree, either the user passed us the tree, or an eSQL statement that we need to parse
        /// </summary>
        private void MakeCommandTree()
        {
            // We must have a connection before we come here
            Debug.Assert(this._connection != null);

            // Do the work only if we don't have a command tree yet
            if (this._preparedCommandTree == null)
            {
                DbCommandTree resultTree = null;
                if (this._commandTreeSetByUser != null)
                {
                    resultTree = this._commandTreeSetByUser;
                }
                else
                if (CommandType.Text == CommandType)
                {
                    if (!string.IsNullOrEmpty(this._esqlCommandText))
                    {
                        // The perspective to be used for the query compilation
                        Perspective perspective = (Perspective)new ModelPerspective(_connection.GetMetadataWorkspace());

                        // get a dictionary of names and typeusage from entity parameter collection
                        Dictionary<string, TypeUsage> queryParams = GetParameterTypeUsage();

                        resultTree = CqlQuery.Compile(
                            this._esqlCommandText, 
                            perspective, 
                            null /*parser option - use default*/, 
                            queryParams.Select(paramInfo => paramInfo.Value.Parameter(paramInfo.Key))).CommandTree;
                    }
                    else
                    {
                        // We have no command text, no command tree, so throw an exception
                        if (this._isCommandDefinitionBased)
                        {
                            // This command was based on a prepared command definition and has no command text,
                            // so reprepare is not possible. To create a new command with different parameters
                            // requires creating a new entity command definition and calling it's CreateCommand method.
                            throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_CannotReprepareCommandDefinitionBasedCommand);
                        }
                        else
                        {
                            throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_NoCommandText);
                        }
                    }
                }
                else if (CommandType.StoredProcedure == CommandType)
                {
                    // get a dictionary of names and typeusage from entity parameter collection
                    IEnumerable<KeyValuePair<string, TypeUsage>> queryParams = GetParameterTypeUsage();
                    EdmFunction function = DetermineFunctionImport();
                    resultTree = new DbFunctionCommandTree(this.Connection.GetMetadataWorkspace(), DataSpace.CSpace, function, null, queryParams);
                }

                // After everything is good and succeeded, assign the result to our field
                this._preparedCommandTree = resultTree;
            }
        }

        // requires: this must be a StoreProcedure command
        // effects: determines the EntityContainer function import referenced by this.CommandText
        private EdmFunction DetermineFunctionImport()
        {
            Debug.Assert(CommandType.StoredProcedure == this.CommandType);

            if (string.IsNullOrEmpty(this.CommandText) ||
                string.IsNullOrEmpty(this.CommandText.Trim()))
            {
                throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_FunctionImportEmptyCommandText);
            }

            MetadataWorkspace workspace = _connection.GetMetadataWorkspace();

            // parse the command text
            string containerName;
            string functionImportName;
            string defaultContainerName = null; // no default container in EntityCommand
            CommandHelper.ParseFunctionImportCommandText(this.CommandText, defaultContainerName, out containerName, out functionImportName);

            return CommandHelper.FindFunctionImport(_connection.GetMetadataWorkspace(), containerName, functionImportName);
        }

        /// <summary>
        /// Get the command definition for the command; will construct one if there is not already
        /// one constructed, which means it will prepare the command on the client.
        /// </summary>
        /// <returns>the command definition</returns>
        internal EntityCommandDefinition GetCommandDefinition()
        {
            EntityCommandDefinition entityCommandDefinition = _commandDefinition;

            // Construct the command definition using no special options;
            if (null == entityCommandDefinition)
            {
                //
                // check if the _commandDefinition is in cache
                //
                if (!TryGetEntityCommandDefinitionFromQueryCache(out entityCommandDefinition))
                {
                    //
                    // if not, construct the command definition using no special options;
                    //
                    entityCommandDefinition = CreateCommandDefinition();
                }

                _commandDefinition = entityCommandDefinition;
            }

            return entityCommandDefinition;
        }

        /// <summary>
        /// Returns the store command text.
        /// </summary>
        /// <returns></returns>
        [Browsable(false)]
        public string ToTraceString() 
        {
            CheckConnectionPresent();

            InnerPrepare();

            EntityCommandDefinition commandDefinition = _commandDefinition;
            if (null != commandDefinition) 
            {
                return commandDefinition.ToTraceString();
            }
            return string.Empty;
        }

        /// <summary>
        /// Gets an entitycommanddefinition from cache if a match is found for the given cache key.
        /// </summary>
        /// <param name="entityCommandDefinition">out param. returns the entitycommanddefinition for a given cache key</param>
        /// <returns>true if a match is found in cache, false otherwise</returns>
        private bool TryGetEntityCommandDefinitionFromQueryCache( out EntityCommandDefinition entityCommandDefinition )
        {
            Debug.Assert(null != _connection, "Connection must not be null at this point");
            entityCommandDefinition = null;

            //
            // if EnableQueryCaching is false, then just return to force the CommandDefinition to be created
            //
            if (!this._enableQueryPlanCaching || string.IsNullOrEmpty(this._esqlCommandText))
            {
                return false;
            }

            //
            // Create cache key
            //
            EntityClientCacheKey queryCacheKey = new EntityClientCacheKey(this);

            //
            // Try cache lookup
            //
            QueryCacheManager queryCacheManager = _connection.GetMetadataWorkspace().GetQueryCacheManager();
            Debug.Assert(null != queryCacheManager,"QuerycacheManager instance cannot be null");
            if (!queryCacheManager.TryCacheLookup(queryCacheKey, out entityCommandDefinition))
            {
                //
                // if not, construct the command definition using no special options;
                //
                entityCommandDefinition = CreateCommandDefinition();

                //
                // add to the cache
                //
                QueryCacheEntry outQueryCacheEntry = null;
                if (queryCacheManager.TryLookupAndAdd(new QueryCacheEntry(queryCacheKey, entityCommandDefinition), out outQueryCacheEntry))
                {
                    entityCommandDefinition = (EntityCommandDefinition)outQueryCacheEntry.GetTarget();
                }
            }
            
            Debug.Assert(null != entityCommandDefinition, "out entityCommandDefinition must not be null");

            return true;
        }

        /// <summary>
        /// Creates a commandDefinition for the command, using the options specified.  
        /// 
        /// Note: This method must not be side-effecting of the command
        /// </summary>
        /// <returns>the command definition</returns>
        private EntityCommandDefinition CreateCommandDefinition() 
        {
            MakeCommandTree();
            // Always check the CQT metadata against the connection metadata (internally, CQT already
            // validates metadata consistency)
            if (!_preparedCommandTree.MetadataWorkspace.IsMetadataWorkspaceCSCompatible(this.Connection.GetMetadataWorkspace()))
            {
                throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_CommandTreeMetadataIncompatible);
            }
            EntityCommandDefinition result = EntityProviderServices.Instance.CreateCommandDefinition(this._connection.StoreProviderFactory, this._preparedCommandTree);
            return result;
        }

        private void CheckConnectionPresent()
        {
            if (this._connection == null)
            {
                throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_NoConnectionForCommand);
            }
        }

        /// <summary>
        /// Checking the integrity of this command object to see if it's ready to be prepared or executed
        /// </summary>
        private void CheckIfReadyToPrepare()
        {
            // Check that we have a connection
            CheckConnectionPresent();

            if (this._connection.StoreProviderFactory == null || this._connection.StoreConnection == null)
            {
                throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionStringNeededBeforeOperation);
            }

            // Make sure the connection is not closed or broken
            if ((this._connection.State == ConnectionState.Closed) || (this._connection.State == ConnectionState.Broken))
            {
                string message = System.Data.Entity.Strings.EntityClient_ExecutingOnClosedConnection(
                    this._connection.State == ConnectionState.Closed ?
                    System.Data.Entity.Strings.EntityClient_ConnectionStateClosed :
                    System.Data.Entity.Strings.EntityClient_ConnectionStateBroken);
                throw EntityUtil.InvalidOperation(message);
            }
        }

        /// <summary>
        /// Checking if the command is still tied to a data reader, if so, then the reader must still be open and we throw
        /// </summary>
        private void ThrowIfDataReaderIsOpen()
        {
            if (this._dataReader != null)
            {
                throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_DataReaderIsStillOpen);
            }
        }

        /// <summary>
        /// Returns a dictionary of parameter name and parameter typeusage in s-space from the entity parameter 
        /// collection given by the user.
        /// </summary>
        /// <returns></returns>
        internal Dictionary<string, TypeUsage> GetParameterTypeUsage()
        {
            Debug.Assert(null != _parameters, "_parameters must not be null");
            // Extract type metadata objects from the parameters to be used by CqlQuery.Compile
            Dictionary<string, TypeUsage> queryParams = new Dictionary<string, TypeUsage>(_parameters.Count);
            foreach (EntityParameter parameter in this._parameters)
            {
                // Validate that the parameter name has the format: A character followed by alphanumerics or
                // underscores
                string parameterName = parameter.ParameterName;
                if (string.IsNullOrEmpty(parameterName))
                {
                    throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_EmptyParameterName);
                }

                // Check each parameter to make sure it's an input parameter, currently EntityCommand doesn't support
                // anything else
                if (this.CommandType == CommandType.Text && parameter.Direction != ParameterDirection.Input)
                {
                    throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_InvalidParameterDirection(parameter.ParameterName));
                }

                // Checking that we can deduce the type from the parameter if the type is not set
                if (parameter.EdmType == null && parameter.DbType == DbType.Object && (parameter.Value == null || parameter.Value is DBNull))
                {
                    throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_UnknownParameterType(parameterName));
                }

                // Validate that the parameter has an appropriate type and value
                // Any failures in GetTypeUsage will be surfaced as exceptions to the user
                TypeUsage typeUsage = null;
                typeUsage = parameter.GetTypeUsage();

                // Add the query parameter, add the same time detect if this parameter has the same name of a previous parameter
                try
                {
                    queryParams.Add(parameterName, typeUsage);
                }
                catch (ArgumentException e)
                {
                    throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_DuplicateParameterNames(parameter.ParameterName), e);
                }
            }
            
            return queryParams;
        }

        /// <summary>
        /// Call only when the reader associated with this command is closing. Copies parameter values where necessary.
        /// </summary>
        internal void NotifyDataReaderClosing()
        {
            // Disassociating the data reader with this command
            this._dataReader = null;

            if (null != _storeProviderCommand)
            {
                CommandHelper.SetEntityParameterValues(this, _storeProviderCommand, _connection);
                _storeProviderCommand = null;
            }
            if (null != this.OnDataReaderClosing)
            {
                this.OnDataReaderClosing(this, new EventArgs());
            }
        }

        /// <summary>
        /// Tells the EntityCommand about the underlying store provider command in case it needs to pull parameter values
        /// when the reader is closing.
        /// </summary>
        internal void SetStoreProviderCommand(DbCommand storeProviderCommand)
        {
            _storeProviderCommand = storeProviderCommand;
        }

        /// <summary>
        /// Event raised when the reader is closing.
        /// </summary>
        internal event EventHandler OnDataReaderClosing;
    }
}
