//----------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// @owner [....], [....]
//---------------------------------------------------------------------
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Common;
using System.Data.Common.EntitySql;
using System.Data.Metadata.Edm;
using System.Diagnostics;
using System.Data.Common.QueryCache;
namespace System.Data.EntityClient
{
using System.Data.Common.CommandTrees;
using System.Data.Common.Utils;
using System.Data.Query.ResultAssembly;
using System.Data.Mapping;
using System.Data.Entity;
///
/// Class representing a command for the conceptual layer
///
public sealed class EntityCommand : DbCommand
{
#region Fields
private const int InvalidCloseCount = -1;
private bool _designTimeVisible;
private string _esqlStatement;
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 DbDataReader _dataReader;
private bool _enableQueryPlanCaching;
private DbCommand _storeProviderCommand;
#endregion
///
/// Constructs the EntityCommand object not yet associated to a connection object
///
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;
}
///
/// Constructs the EntityCommand object with the given ESQL statement, but not yet associated to a connection object
///
/// The ESQL command text to execute
public EntityCommand(string statement)
: this()
{
// Assign other member fields from the parameters
this._esqlStatement = statement;
}
///
/// Constructs the EntityCommand object with the given ESQL statement and the connection object to use
///
/// The ESQL command text to execute
/// The connection object
public EntityCommand(string statement, EntityConnection connection)
: this(statement)
{
// Assign other member fields from the parameters
this._connection = connection;
}
///
/// Constructs the EntityCommand object with the given ESQL statement and the connection object to use
///
/// The ESQL command text to execute
/// The connection object
/// The transaction object this command executes in
public EntityCommand(string statement, EntityConnection connection, EntityTransaction transaction)
: this(statement, connection)
{
// Assign other member fields from the parameters
this._transaction = transaction;
}
///
/// internal constructor used by EntityCommandDefinition
///
///
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();
}
///
/// Constructs a new EntityCommand given a EntityConnection and an EntityCommandDefition. This
/// constructor is used by the QueryCache when dealing with ObjectQueryT
///
///
///
internal EntityCommand( EntityConnection connection, EntityCommandDefinition entityCommandDefinition )
: this()
{
// Assign other member fields from the parameters
this._commandDefinition = entityCommandDefinition;
this._parameters = new EntityParameterCollection();
// Make copies of the parameters
foreach (EntityParameter parameter in entityCommandDefinition.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();
this._connection = connection;
}
///
/// The connection object used for executing the command
///
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;
}
}
}
///
/// The connection object used for executing the command
///
protected override DbConnection DbConnection
{
get
{
return this.Connection;
}
set
{
this.Connection = (EntityConnection)value;
}
}
///
/// The ESQL statement to execute, only one of the command tree or the command text can be set, not both
///
public override string CommandText
{
get
{
#if ENTITYCOMMAND_SETTABLE_COMMANDTREE
// EntityClient_CannotGetSetCommandText=Cannot retrieve or set the command text because the command tree was set instead of the command text.
// 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_CannotGetSetCommandText);
#endif
return this._esqlStatement ?? "";
}
set
{
ThrowIfDataReaderIsOpen();
#if ENTITYCOMMAND_SETTABLE_COMMANDTREE
// 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_CannotGetSetCommandText);
#endif
if (this._esqlStatement != value)
{
this._esqlStatement = value;
// Wipe out any preparation work we have done
Unprepare();
}
}
}
#if ENTITYCOMMAND_SETTABLE_COMMANDTREE
private DbCommandTree _commandTreeSetByUser;
///
/// The command tree to execute, only one of the command tree or the command text can be set, not both
///
internal DbCommandTree CommandTree
{
get
{
// EntityClient_CannotGetSetCommandTree=Cannot retrieve or set the command tree because the command text was set instead of the command tree.
// If the user set the command text previously, then we cannot retrieve the command tree
if (!string.IsNullOrEmpty(this._esqlStatement))
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_CannotGetSetCommandTree);
return this._commandTreeSetByUser;
}
set
{
ThrowIfDataReaderIsOpen();
// If the user set the command text previously, then we cannot set the command tree
if (!string.IsNullOrEmpty(this._esqlStatement))
throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_CannotGetSetCommandTree);
// 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();
}
}
}
#endif
///
/// Get or set the time in seconds to wait for the command to execute
///
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;
}
}
///
/// The type of command being executed, only applicable when the command is using an ESQL statement and not the tree
///
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;
}
}
///
/// The collection of parameters for this command
///
public new EntityParameterCollection Parameters
{
get
{
return this._parameters;
}
}
///
/// The collection of parameters for this command
///
protected override DbParameterCollection DbParameterCollection
{
get
{
return this.Parameters;
}
}
///
/// The transaction object used for executing the command
///
public new EntityTransaction Transaction
{
get
{
return this._transaction; // SQLBU 496829
}
set
{
ThrowIfDataReaderIsOpen();
this._transaction = value;
}
}
///
/// The transaction that this command executes in
///
protected override DbTransaction DbTransaction
{
get
{
return this.Transaction;
}
set
{
this.Transaction = (EntityTransaction)value;
}
}
///
/// Gets or sets how command results are applied to the DataRow when used by the Update method of a DbDataAdapter
///
public override UpdateRowSource UpdatedRowSource
{
get
{
return this._updatedRowSource;
}
set
{
ThrowIfDataReaderIsOpen();
this._updatedRowSource = value;
}
}
///
/// Hidden property used by the designers
///
public override bool DesignTimeVisible
{
get
{
return this._designTimeVisible;
}
set
{
ThrowIfDataReaderIsOpen();
this._designTimeVisible = value;
TypeDescriptor.Refresh(this);
}
}
///
/// Enables/Disables query plan caching for this EntityCommand
///
public bool EnablePlanCaching
{
get
{
return this._enableQueryPlanCaching;
}
set
{
ThrowIfDataReaderIsOpen();
this._enableQueryPlanCaching = value;
}
}
///
/// Cancel the execution of the command
///
public override void Cancel()
{
}
///
/// Create and return a new parameter object representing a parameter in the ESQL statement
///
///
[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
public new EntityParameter CreateParameter()
{
return new EntityParameter();
}
///
/// Create and return a new parameter object representing a parameter in the ESQL statement
///
protected override DbParameter CreateDbParameter()
{
return CreateParameter();
}
///
/// Executes the command and returns a data reader for reading the results
///
/// A data readerobject
public new EntityDataReader ExecuteReader()
{
return ExecuteReader(CommandBehavior.Default);
}
///
/// 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)
///
/// The behavior to use when executing the command
/// A data readerobject
/// For stored procedure commands, if called
/// for anything but an entity collection result
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;
}
///
/// Executes the command and returns a data reader for reading the results
///
/// The behavior to use when executing the command
/// A data readerobject
protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
{
return ExecuteReader(behavior);
}
///
/// Executes the command and discard any results returned from the command
///
/// Number of rows affected
public override int ExecuteNonQuery()
{
return ExecuteScalar(reader =>
{
// consume reader before checking records affected
CommandHelper.ConsumeReader(reader);
return reader.RecordsAffected;
});
}
///
/// Executes the command and return the first column in the first row of the result, extra results are ignored
///
/// The result in the first column in the first row
public override object ExecuteScalar()
{
return ExecuteScalar