1 //---------------------------------------------------------------------
2 // <copyright file="EntityCommand.cs" company="Microsoft">
3 // Copyright (c) Microsoft Corporation. All rights reserved.
7 // @backupOwner Microsoft
8 //---------------------------------------------------------------------
10 namespace System.Data.EntityClient
12 using System.Collections.Generic;
13 using System.ComponentModel;
15 using System.Data.Common;
16 using System.Data.Common.CommandTrees;
17 using System.Data.Common.CommandTrees.ExpressionBuilder;
18 using System.Data.Common.EntitySql;
19 using System.Data.Common.QueryCache;
20 using System.Data.Common.Utils;
21 using System.Data.Metadata.Edm;
22 using System.Diagnostics;
26 /// Class representing a command for the conceptual layer
28 public sealed class EntityCommand : DbCommand
31 private const int InvalidCloseCount = -1;
33 private bool _designTimeVisible;
34 private string _esqlCommandText;
35 private EntityConnection _connection;
36 private DbCommandTree _preparedCommandTree;
37 private EntityParameterCollection _parameters;
38 private int? _commandTimeout;
39 private CommandType _commandType;
40 private EntityTransaction _transaction;
41 private UpdateRowSource _updatedRowSource;
42 private EntityCommandDefinition _commandDefinition;
43 private bool _isCommandDefinitionBased;
44 private DbCommandTree _commandTreeSetByUser;
45 private DbDataReader _dataReader;
46 private bool _enableQueryPlanCaching;
47 private DbCommand _storeProviderCommand;
51 /// Constructs the EntityCommand object not yet associated to a connection object
53 public EntityCommand()
55 GC.SuppressFinalize(this);
57 // Initalize the member field with proper default values
58 this._designTimeVisible = true;
59 this._commandType = CommandType.Text;
60 this._updatedRowSource = UpdateRowSource.Both;
61 this._parameters = new EntityParameterCollection();
63 // Future Enhancement: (See SQLPT #300004256) At some point it would be
64 // really nice to read defaults from a global configuration, but we're not
66 this._enableQueryPlanCaching = true;
70 /// Constructs the EntityCommand object with the given eSQL statement, but not yet associated to a connection object
72 /// <param name="statement">The eSQL command text to execute</param>
73 public EntityCommand(string statement)
76 // Assign other member fields from the parameters
77 this._esqlCommandText = statement;
81 /// Constructs the EntityCommand object with the given eSQL statement and the connection object to use
83 /// <param name="statement">The eSQL command text to execute</param>
84 /// <param name="connection">The connection object</param>
85 public EntityCommand(string statement, EntityConnection connection)
88 // Assign other member fields from the parameters
89 this._connection = connection;
93 /// Constructs the EntityCommand object with the given eSQL statement and the connection object to use
95 /// <param name="statement">The eSQL command text to execute</param>
96 /// <param name="connection">The connection object</param>
97 /// <param name="transaction">The transaction object this command executes in</param>
98 public EntityCommand(string statement, EntityConnection connection, EntityTransaction transaction)
99 : this(statement, connection)
101 // Assign other member fields from the parameters
102 this._transaction = transaction;
106 /// Internal constructor used by EntityCommandDefinition
108 /// <param name="commandDefinition">The prepared command definition that can be executed using this EntityCommand</param>
109 internal EntityCommand(EntityCommandDefinition commandDefinition)
112 // Assign other member fields from the parameters
113 this._commandDefinition = commandDefinition;
114 this._parameters = new EntityParameterCollection();
116 // Make copies of the parameters
117 foreach (EntityParameter parameter in commandDefinition.Parameters)
119 this._parameters.Add(parameter.Clone());
122 // Reset the dirty flag that was set to true when the parameters were added so that it won't say
123 // it's dirty to start with
124 this._parameters.ResetIsDirty();
126 // Track the fact that this command was created from and represents an already prepared command definition
127 this._isCommandDefinitionBased = true;
131 /// Constructs a new EntityCommand given a EntityConnection and an EntityCommandDefition. This
132 /// constructor is used by ObjectQueryExecution plan to execute an ObjectQuery.
134 /// <param name="connection">The connection against which this EntityCommand should execute</param>
135 /// <param name="commandDefinition">The prepared command definition that can be executed using this EntityCommand</param>
136 internal EntityCommand(EntityConnection connection, EntityCommandDefinition entityCommandDefinition )
137 : this(entityCommandDefinition)
139 this._connection = connection;
143 /// The connection object used for executing the command
145 public new EntityConnection Connection
149 return this._connection;
153 ThrowIfDataReaderIsOpen();
154 if (this._connection != value)
156 if (null != this._connection)
160 this._connection = value;
162 this._transaction = null;
168 /// The connection object used for executing the command
170 protected override DbConnection DbConnection
174 return this.Connection;
178 this.Connection = (EntityConnection)value;
183 /// The eSQL statement to execute, only one of the command tree or the command text can be set, not both
185 public override string CommandText
189 // If the user set the command tree previously, then we cannot retrieve the command text
190 if (this._commandTreeSetByUser != null)
191 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_CannotGetCommandText);
193 return this._esqlCommandText ?? "";
197 ThrowIfDataReaderIsOpen();
199 // If the user set the command tree previously, then we cannot set the command text
200 if (this._commandTreeSetByUser != null)
201 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_CannotSetCommandText);
203 if (this._esqlCommandText != value)
205 this._esqlCommandText = value;
207 // Wipe out any preparation work we have done
210 // If the user-defined command text or tree has been set (even to null or empty),
211 // then this command can no longer be considered command definition-based
212 this._isCommandDefinitionBased = false;
218 /// The command tree to execute, only one of the command tree or the command text can be set, not both.
220 public DbCommandTree CommandTree
224 // If the user set the command text previously, then we cannot retrieve the command tree
225 if (!string.IsNullOrEmpty(this._esqlCommandText))
226 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_CannotGetCommandTree);
228 return this._commandTreeSetByUser;
232 ThrowIfDataReaderIsOpen();
234 // If the user set the command text previously, then we cannot set the command tree
235 if (!string.IsNullOrEmpty(this._esqlCommandText))
236 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_CannotSetCommandTree);
238 // If the command type is not Text, CommandTree cannot be set
239 if (CommandType.Text != CommandType)
241 throw EntityUtil.InternalError(EntityUtil.InternalErrorCode.CommandTreeOnStoredProcedureEntityCommand);
244 if (this._commandTreeSetByUser != value)
246 this._commandTreeSetByUser = value;
248 // Wipe out any preparation work we have done
251 // If the user-defined command text or tree has been set (even to null or empty),
252 // then this command can no longer be considered command definition-based
253 this._isCommandDefinitionBased = false;
259 /// Get or set the time in seconds to wait for the command to execute
261 public override int CommandTimeout
265 // Returns the timeout value if it has been set
266 if (this._commandTimeout != null)
268 return this._commandTimeout.Value;
271 // Create a provider command object just so we can ask the default timeout
272 if (this._connection != null && this._connection.StoreProviderFactory != null)
274 DbCommand storeCommand = this._connection.StoreProviderFactory.CreateCommand();
275 if (storeCommand != null)
277 return storeCommand.CommandTimeout;
285 ThrowIfDataReaderIsOpen();
286 this._commandTimeout = value;
291 /// The type of command being executed, only applicable when the command is using an eSQL statement and not the tree
293 public override CommandType CommandType
297 return this._commandType;
301 ThrowIfDataReaderIsOpen();
303 // For now, command type other than Text is not supported
304 if (value != CommandType.Text && value != CommandType.StoredProcedure)
306 throw EntityUtil.NotSupported(System.Data.Entity.Strings.EntityClient_UnsupportedCommandType);
309 this._commandType = value;
314 /// The collection of parameters for this command
316 public new EntityParameterCollection Parameters
320 return this._parameters;
325 /// The collection of parameters for this command
327 protected override DbParameterCollection DbParameterCollection
331 return this.Parameters;
336 /// The transaction object used for executing the command
338 public new EntityTransaction Transaction
342 return this._transaction; // SQLBU 496829
346 ThrowIfDataReaderIsOpen();
347 this._transaction = value;
352 /// The transaction that this command executes in
354 protected override DbTransaction DbTransaction
358 return this.Transaction;
362 this.Transaction = (EntityTransaction)value;
367 /// Gets or sets how command results are applied to the DataRow when used by the Update method of a DbDataAdapter
369 public override UpdateRowSource UpdatedRowSource
373 return this._updatedRowSource;
377 ThrowIfDataReaderIsOpen();
378 this._updatedRowSource = value;
383 /// Hidden property used by the designers
385 public override bool DesignTimeVisible
389 return this._designTimeVisible;
393 ThrowIfDataReaderIsOpen();
394 this._designTimeVisible = value;
395 TypeDescriptor.Refresh(this);
400 /// Enables/Disables query plan caching for this EntityCommand
402 public bool EnablePlanCaching
406 return this._enableQueryPlanCaching;
411 ThrowIfDataReaderIsOpen();
412 this._enableQueryPlanCaching = value;
417 /// Cancel the execution of the command
419 public override void Cancel()
424 /// Create and return a new parameter object representing a parameter in the eSQL statement
427 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic")]
428 public new EntityParameter CreateParameter()
430 return new EntityParameter();
434 /// Create and return a new parameter object representing a parameter in the eSQL statement
436 protected override DbParameter CreateDbParameter()
438 return CreateParameter();
442 /// Executes the command and returns a data reader for reading the results
444 /// <returns>A data readerobject</returns>
445 public new EntityDataReader ExecuteReader()
447 return ExecuteReader(CommandBehavior.Default);
451 /// Executes the command and returns a data reader for reading the results. May only
452 /// be called on CommandType.CommandText (otherwise, use the standard Execute* methods)
454 /// <param name="behavior">The behavior to use when executing the command</param>
455 /// <returns>A data readerobject</returns>
456 /// <exception cref="InvalidOperationException">For stored procedure commands, if called
457 /// for anything but an entity collection result</exception>
458 public new EntityDataReader ExecuteReader(CommandBehavior behavior)
460 Prepare(); // prepare the query first
462 EntityDataReader reader = new EntityDataReader(this, _commandDefinition.Execute(this, behavior), behavior);
463 _dataReader = reader;
468 /// Executes the command and returns a data reader for reading the results
470 /// <param name="behavior">The behavior to use when executing the command</param>
471 /// <returns>A data readerobject</returns>
472 protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior)
474 return ExecuteReader(behavior);
478 /// Executes the command and discard any results returned from the command
480 /// <returns>Number of rows affected</returns>
481 public override int ExecuteNonQuery()
483 return ExecuteScalar<int>(reader =>
485 // consume reader before checking records affected
486 CommandHelper.ConsumeReader(reader);
487 return reader.RecordsAffected;
492 /// Executes the command and return the first column in the first row of the result, extra results are ignored
494 /// <returns>The result in the first column in the first row</returns>
495 public override object ExecuteScalar()
497 return ExecuteScalar<object>(reader =>
499 object result = reader.Read() ? reader.GetValue(0) : null;
500 // consume reader before retrieving parameters
501 CommandHelper.ConsumeReader(reader);
507 /// Executes a reader and retrieves a scalar value using the given resultSelector delegate
509 private T_Result ExecuteScalar<T_Result>(Func<DbDataReader, T_Result> resultSelector)
512 using (EntityDataReader reader = ExecuteReader(CommandBehavior.SequentialAccess))
514 result = resultSelector(reader);
520 /// Clear out any "compile" state
522 internal void Unprepare()
524 this._commandDefinition = null;
525 this._preparedCommandTree = null;
527 // Clear the dirty flag on the parameters and parameter collection
528 _parameters.ResetIsDirty();
532 /// Creates a prepared version of this command
534 public override void Prepare()
536 ThrowIfDataReaderIsOpen();
537 CheckIfReadyToPrepare();
543 /// Creates a prepared version of this command without regard to the current connection state.
544 /// Called by both <see cref="Prepare"/> and <see cref="ToTraceString"/>.
546 private void InnerPrepare()
548 // Unprepare if the parameters have changed to force a reprepare
549 if (_parameters.IsDirty)
554 _commandDefinition = GetCommandDefinition();
555 Debug.Assert(null != _commandDefinition, "_commandDefinition cannot be null");
559 /// Ensures we have the command tree, either the user passed us the tree, or an eSQL statement that we need to parse
561 private void MakeCommandTree()
563 // We must have a connection before we come here
564 Debug.Assert(this._connection != null);
566 // Do the work only if we don't have a command tree yet
567 if (this._preparedCommandTree == null)
569 DbCommandTree resultTree = null;
570 if (this._commandTreeSetByUser != null)
572 resultTree = this._commandTreeSetByUser;
575 if (CommandType.Text == CommandType)
577 if (!string.IsNullOrEmpty(this._esqlCommandText))
579 // The perspective to be used for the query compilation
580 Perspective perspective = (Perspective)new ModelPerspective(_connection.GetMetadataWorkspace());
582 // get a dictionary of names and typeusage from entity parameter collection
583 Dictionary<string, TypeUsage> queryParams = GetParameterTypeUsage();
585 resultTree = CqlQuery.Compile(
586 this._esqlCommandText,
588 null /*parser option - use default*/,
589 queryParams.Select(paramInfo => paramInfo.Value.Parameter(paramInfo.Key))).CommandTree;
593 // We have no command text, no command tree, so throw an exception
594 if (this._isCommandDefinitionBased)
596 // This command was based on a prepared command definition and has no command text,
597 // so reprepare is not possible. To create a new command with different parameters
598 // requires creating a new entity command definition and calling it's CreateCommand method.
599 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_CannotReprepareCommandDefinitionBasedCommand);
603 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_NoCommandText);
607 else if (CommandType.StoredProcedure == CommandType)
609 // get a dictionary of names and typeusage from entity parameter collection
610 IEnumerable<KeyValuePair<string, TypeUsage>> queryParams = GetParameterTypeUsage();
611 EdmFunction function = DetermineFunctionImport();
612 resultTree = new DbFunctionCommandTree(this.Connection.GetMetadataWorkspace(), DataSpace.CSpace, function, null, queryParams);
615 // After everything is good and succeeded, assign the result to our field
616 this._preparedCommandTree = resultTree;
620 // requires: this must be a StoreProcedure command
621 // effects: determines the EntityContainer function import referenced by this.CommandText
622 private EdmFunction DetermineFunctionImport()
624 Debug.Assert(CommandType.StoredProcedure == this.CommandType);
626 if (string.IsNullOrEmpty(this.CommandText) ||
627 string.IsNullOrEmpty(this.CommandText.Trim()))
629 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_FunctionImportEmptyCommandText);
632 MetadataWorkspace workspace = _connection.GetMetadataWorkspace();
634 // parse the command text
635 string containerName;
636 string functionImportName;
637 string defaultContainerName = null; // no default container in EntityCommand
638 CommandHelper.ParseFunctionImportCommandText(this.CommandText, defaultContainerName, out containerName, out functionImportName);
640 return CommandHelper.FindFunctionImport(_connection.GetMetadataWorkspace(), containerName, functionImportName);
644 /// Get the command definition for the command; will construct one if there is not already
645 /// one constructed, which means it will prepare the command on the client.
647 /// <returns>the command definition</returns>
648 internal EntityCommandDefinition GetCommandDefinition()
650 EntityCommandDefinition entityCommandDefinition = _commandDefinition;
652 // Construct the command definition using no special options;
653 if (null == entityCommandDefinition)
656 // check if the _commandDefinition is in cache
658 if (!TryGetEntityCommandDefinitionFromQueryCache(out entityCommandDefinition))
661 // if not, construct the command definition using no special options;
663 entityCommandDefinition = CreateCommandDefinition();
666 _commandDefinition = entityCommandDefinition;
669 return entityCommandDefinition;
673 /// Returns the store command text.
675 /// <returns></returns>
677 public string ToTraceString()
679 CheckConnectionPresent();
683 EntityCommandDefinition commandDefinition = _commandDefinition;
684 if (null != commandDefinition)
686 return commandDefinition.ToTraceString();
692 /// Gets an entitycommanddefinition from cache if a match is found for the given cache key.
694 /// <param name="entityCommandDefinition">out param. returns the entitycommanddefinition for a given cache key</param>
695 /// <returns>true if a match is found in cache, false otherwise</returns>
696 private bool TryGetEntityCommandDefinitionFromQueryCache( out EntityCommandDefinition entityCommandDefinition )
698 Debug.Assert(null != _connection, "Connection must not be null at this point");
699 entityCommandDefinition = null;
702 // if EnableQueryCaching is false, then just return to force the CommandDefinition to be created
704 if (!this._enableQueryPlanCaching || string.IsNullOrEmpty(this._esqlCommandText))
712 EntityClientCacheKey queryCacheKey = new EntityClientCacheKey(this);
717 QueryCacheManager queryCacheManager = _connection.GetMetadataWorkspace().GetQueryCacheManager();
718 Debug.Assert(null != queryCacheManager,"QuerycacheManager instance cannot be null");
719 if (!queryCacheManager.TryCacheLookup(queryCacheKey, out entityCommandDefinition))
722 // if not, construct the command definition using no special options;
724 entityCommandDefinition = CreateCommandDefinition();
729 QueryCacheEntry outQueryCacheEntry = null;
730 if (queryCacheManager.TryLookupAndAdd(new QueryCacheEntry(queryCacheKey, entityCommandDefinition), out outQueryCacheEntry))
732 entityCommandDefinition = (EntityCommandDefinition)outQueryCacheEntry.GetTarget();
736 Debug.Assert(null != entityCommandDefinition, "out entityCommandDefinition must not be null");
742 /// Creates a commandDefinition for the command, using the options specified.
744 /// Note: This method must not be side-effecting of the command
746 /// <returns>the command definition</returns>
747 private EntityCommandDefinition CreateCommandDefinition()
750 // Always check the CQT metadata against the connection metadata (internally, CQT already
751 // validates metadata consistency)
752 if (!_preparedCommandTree.MetadataWorkspace.IsMetadataWorkspaceCSCompatible(this.Connection.GetMetadataWorkspace()))
754 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_CommandTreeMetadataIncompatible);
756 EntityCommandDefinition result = EntityProviderServices.Instance.CreateCommandDefinition(this._connection.StoreProviderFactory, this._preparedCommandTree);
760 private void CheckConnectionPresent()
762 if (this._connection == null)
764 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_NoConnectionForCommand);
769 /// Checking the integrity of this command object to see if it's ready to be prepared or executed
771 private void CheckIfReadyToPrepare()
773 // Check that we have a connection
774 CheckConnectionPresent();
776 if (this._connection.StoreProviderFactory == null || this._connection.StoreConnection == null)
778 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_ConnectionStringNeededBeforeOperation);
781 // Make sure the connection is not closed or broken
782 if ((this._connection.State == ConnectionState.Closed) || (this._connection.State == ConnectionState.Broken))
784 string message = System.Data.Entity.Strings.EntityClient_ExecutingOnClosedConnection(
785 this._connection.State == ConnectionState.Closed ?
786 System.Data.Entity.Strings.EntityClient_ConnectionStateClosed :
787 System.Data.Entity.Strings.EntityClient_ConnectionStateBroken);
788 throw EntityUtil.InvalidOperation(message);
793 /// Checking if the command is still tied to a data reader, if so, then the reader must still be open and we throw
795 private void ThrowIfDataReaderIsOpen()
797 if (this._dataReader != null)
799 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_DataReaderIsStillOpen);
804 /// Returns a dictionary of parameter name and parameter typeusage in s-space from the entity parameter
805 /// collection given by the user.
807 /// <returns></returns>
808 internal Dictionary<string, TypeUsage> GetParameterTypeUsage()
810 Debug.Assert(null != _parameters, "_parameters must not be null");
811 // Extract type metadata objects from the parameters to be used by CqlQuery.Compile
812 Dictionary<string, TypeUsage> queryParams = new Dictionary<string, TypeUsage>(_parameters.Count);
813 foreach (EntityParameter parameter in this._parameters)
815 // Validate that the parameter name has the format: A character followed by alphanumerics or
817 string parameterName = parameter.ParameterName;
818 if (string.IsNullOrEmpty(parameterName))
820 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_EmptyParameterName);
823 // Check each parameter to make sure it's an input parameter, currently EntityCommand doesn't support
825 if (this.CommandType == CommandType.Text && parameter.Direction != ParameterDirection.Input)
827 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_InvalidParameterDirection(parameter.ParameterName));
830 // Checking that we can deduce the type from the parameter if the type is not set
831 if (parameter.EdmType == null && parameter.DbType == DbType.Object && (parameter.Value == null || parameter.Value is DBNull))
833 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_UnknownParameterType(parameterName));
836 // Validate that the parameter has an appropriate type and value
837 // Any failures in GetTypeUsage will be surfaced as exceptions to the user
838 TypeUsage typeUsage = null;
839 typeUsage = parameter.GetTypeUsage();
841 // Add the query parameter, add the same time detect if this parameter has the same name of a previous parameter
844 queryParams.Add(parameterName, typeUsage);
846 catch (ArgumentException e)
848 throw EntityUtil.InvalidOperation(System.Data.Entity.Strings.EntityClient_DuplicateParameterNames(parameter.ParameterName), e);
856 /// Call only when the reader associated with this command is closing. Copies parameter values where necessary.
858 internal void NotifyDataReaderClosing()
860 // Disassociating the data reader with this command
861 this._dataReader = null;
863 if (null != _storeProviderCommand)
865 CommandHelper.SetEntityParameterValues(this, _storeProviderCommand, _connection);
866 _storeProviderCommand = null;
868 if (null != this.OnDataReaderClosing)
870 this.OnDataReaderClosing(this, new EventArgs());
875 /// Tells the EntityCommand about the underlying store provider command in case it needs to pull parameter values
876 /// when the reader is closing.
878 internal void SetStoreProviderCommand(DbCommand storeProviderCommand)
880 _storeProviderCommand = storeProviderCommand;
884 /// Event raised when the reader is closing.
886 internal event EventHandler OnDataReaderClosing;