2 // System.Data.SqlClient.SqlCommand.cs
5 // Rodrigo Moya (rodrigo@ximian.com)
6 // Daniel Morgan (danmorg@sc.rr.com)
7 // Tim Coleman (tim@timcoleman.com)
8 // Diego Caravana (diego@toth.it)
10 // (C) Ximian, Inc 2002 http://www.ximian.com/
11 // (C) Daniel Morgan, 2002
12 // Copyright (C) Tim Coleman, 2002
16 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
18 // Permission is hereby granted, free of charge, to any person obtaining
19 // a copy of this software and associated documentation files (the
20 // "Software"), to deal in the Software without restriction, including
21 // without limitation the rights to use, copy, modify, merge, publish,
22 // distribute, sublicense, and/or sell copies of the Software, and to
23 // permit persons to whom the Software is furnished to do so, subject to
24 // the following conditions:
26 // The above copyright notice and this permission notice shall be
27 // included in all copies or substantial portions of the Software.
29 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
30 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
31 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
32 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
33 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
34 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
35 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
39 using Mono.Data.Tds.Protocol;
41 using System.Collections;
42 using System.Collections.Specialized;
43 using System.ComponentModel;
45 using System.Data.Common;
46 using System.Runtime.InteropServices;
50 namespace System.Data.SqlClient {
51 [DesignerAttribute ("Microsoft.VSDesigner.Data.VS.SqlCommandDesigner, "+ Consts.AssemblyMicrosoft_VSDesigner, "System.ComponentModel.Design.IDesigner")]
52 [ToolboxItemAttribute ("System.Drawing.Design.ToolboxItem, "+ Consts.AssemblySystem_Drawing)]
53 [DefaultEventAttribute ("RecordsAffected")]
55 public sealed class SqlCommand : DbCommand, IDbCommand, ICloneable
57 public sealed class SqlCommand : Component, IDbCommand, ICloneable
62 //bool disposed = false;
64 bool designTimeVisible;
66 CommandType commandType;
67 SqlConnection connection;
68 SqlTransaction transaction;
69 UpdateRowSource updatedRowSource;
70 CommandBehavior behavior = CommandBehavior.Default;
71 SqlParameterCollection parameters;
72 string preparedStatement = null;
79 : this (String.Empty, null, null)
83 public SqlCommand (string commandText)
84 : this (commandText, null, null)
88 public SqlCommand (string commandText, SqlConnection connection)
89 : this (commandText, connection, null)
93 public SqlCommand (string commandText, SqlConnection connection, SqlTransaction transaction)
95 this.commandText = commandText;
96 this.connection = connection;
97 this.transaction = transaction;
98 this.commandType = CommandType.Text;
99 this.updatedRowSource = UpdateRowSource.Both;
101 this.designTimeVisible = false;
102 this.commandTimeout = 30;
103 parameters = new SqlParameterCollection (this);
106 private SqlCommand(string commandText, SqlConnection connection, SqlTransaction transaction, CommandType commandType, UpdateRowSource updatedRowSource, bool designTimeVisible, int commandTimeout, SqlParameterCollection parameters)
108 this.commandText = commandText;
109 this.connection = connection;
110 this.transaction = transaction;
111 this.commandType = commandType;
112 this.updatedRowSource = updatedRowSource;
113 this.designTimeVisible = designTimeVisible;
114 this.commandTimeout = commandTimeout;
115 this.parameters = new SqlParameterCollection(this);
116 for (int i = 0;i < parameters.Count;i++)
117 this.parameters.Add(((ICloneable)parameters[i]).Clone());
119 #endregion // Constructors
123 internal CommandBehavior CommandBehavior {
124 get { return behavior; }
127 [DataCategory ("Data")]
129 [DataSysDescription ("Command text to execute.")]
132 [EditorAttribute ("Microsoft.VSDesigner.Data.SQL.Design.SqlCommandTextEditor, "+ Consts.AssemblyMicrosoft_VSDesigner, "System.Drawing.Design.UITypeEditor, "+ Consts.AssemblySystem_Drawing )]
133 [RefreshProperties (RefreshProperties.All)]
139 get { return commandText; }
141 if (value != commandText && preparedStatement != null)
148 [DataSysDescription ("Time to wait for command to execute.")]
156 get { return commandTimeout; }
159 throw new ArgumentException ("The property value assigned is less than 0.");
160 commandTimeout = value;
164 [DataCategory ("Data")]
166 [DataSysDescription ("How to interpret the CommandText.")]
168 [DefaultValue (CommandType.Text)]
169 [RefreshProperties (RefreshProperties.All)]
174 CommandType CommandType {
175 get { return commandType; }
177 if (value == CommandType.TableDirect)
179 throw new ArgumentOutOfRangeException ("CommandType.TableDirect is not supported " +
180 "by the Mono SqlClient Data Provider.");
182 throw new ArgumentException ("CommandType.TableDirect is not supported by the Mono SqlClient Data Provider.");
185 if (!Enum.IsDefined (typeof (CommandType), value))
187 throw new ArgumentOutOfRangeException (String.Format ("The CommandType enumeration value, {0}, is invalid",
190 throw ExceptionHelper.InvalidEnumValueException ("CommandType", value);
196 [DataCategory ("Behavior")]
197 [DefaultValue (null)]
199 [DataSysDescription ("Connection used by the command.")]
201 [EditorAttribute ("Microsoft.VSDesigner.Data.Design.DbConnectionEditor, "+ Consts.AssemblyMicrosoft_VSDesigner, "System.Drawing.Design.UITypeEditor, "+ Consts.AssemblySystem_Drawing )]
206 SqlConnection Connection {
207 get { return connection; }
209 if (transaction != null && connection.Transaction != null && connection.Transaction.IsOpen)
210 throw new InvalidOperationException ("The Connection property was changed while a transaction was in progress.");
217 [DefaultValue (true)]
219 [EditorBrowsable (EditorBrowsableState.Never)]
224 bool DesignTimeVisible {
225 get { return designTimeVisible; }
226 set { designTimeVisible = value; }
229 [DataCategory ("Data")]
231 [DataSysDescription ("The parameters collection.")]
233 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
238 SqlParameterCollection Parameters {
239 get { return parameters; }
243 get { return Connection.Tds; }
246 IDbConnection IDbCommand.Connection {
247 get { return Connection; }
249 if (!(value is SqlConnection))
250 throw new InvalidCastException ("The value was not a valid SqlConnection.");
251 Connection = (SqlConnection) value;
255 IDataParameterCollection IDbCommand.Parameters {
256 get { return Parameters; }
259 IDbTransaction IDbCommand.Transaction {
260 get { return Transaction; }
262 if (!(value is SqlTransaction))
263 throw new ArgumentException ();
264 Transaction = (SqlTransaction) value;
270 [DataSysDescription ("The transaction used by the command.")]
272 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
273 public new SqlTransaction Transaction {
274 get { return transaction; }
275 set { transaction = value; }
278 [DataCategory ("Behavior")]
280 [DataSysDescription ("When used by a DataAdapter.Update, how command results are applied to the current DataRow.")]
282 [DefaultValue (UpdateRowSource.Both)]
287 UpdateRowSource UpdatedRowSource {
288 get { return updatedRowSource; }
290 if (!Enum.IsDefined (typeof (UpdateRowSource), value))
292 throw new ArgumentOutOfRangeException (String.Format ("The UpdateRowSource enumeration value, {0}, is invalid",
295 throw ExceptionHelper.InvalidEnumValueException ("UpdateRowSource", value);
297 updatedRowSource = value;
311 if (Connection == null || Connection.Tds == null)
313 Connection.Tds.Cancel ();
316 internal void CloseDataReader (bool moreResults)
318 Connection.DataReader = null;
320 if ((behavior & CommandBehavior.CloseConnection) != 0)
323 // Reset the behavior
324 behavior = CommandBehavior.Default;
326 Tds.SequentialAccess = false;
329 public new SqlParameter CreateParameter ()
331 return new SqlParameter ();
334 internal void DeriveParameters ()
336 if (commandType != CommandType.StoredProcedure)
337 throw new InvalidOperationException (String.Format ("SqlCommand DeriveParameters only supports CommandType.StoredProcedure, not CommandType.{0}", commandType));
338 ValidateCommand ("DeriveParameters");
340 SqlParameterCollection localParameters = new SqlParameterCollection (this);
341 localParameters.Add ("@procedure_name", SqlDbType.NVarChar, commandText.Length).Value = commandText;
343 string sql = "sp_procedure_params_rowset";
345 Connection.Tds.ExecProc (sql, localParameters.MetaParameters, 0, true);
347 SqlDataReader reader = new SqlDataReader (this);
349 object[] dbValues = new object[reader.FieldCount];
351 while (reader.Read ()) {
352 reader.GetValues (dbValues);
353 parameters.Add (new SqlParameter (dbValues));
359 private void Execute (CommandBehavior behavior, bool wantResults)
362 Connection.Tds.RecordsAffected = -1;
363 TdsMetaParameterCollection parms = Parameters.MetaParameters;
364 foreach (TdsMetaParameter param in parms) {
367 if (param.Size == 0) {
368 switch (param.TypeName) {
380 throw new InvalidOperationException (String.Format ("{0}[{1}]: the Size property should " +
386 if (preparedStatement == null) {
387 bool schemaOnly = ((behavior & CommandBehavior.SchemaOnly) > 0);
388 bool keyInfo = ((behavior & CommandBehavior.KeyInfo) > 0);
390 StringBuilder sql1 = new StringBuilder ();
391 StringBuilder sql2 = new StringBuilder ();
393 if (schemaOnly || keyInfo)
394 sql1.Append ("SET FMTONLY OFF;");
396 sql1.Append ("SET NO_BROWSETABLE ON;");
397 sql2.Append ("SET NO_BROWSETABLE OFF;");
400 sql1.Append ("SET FMTONLY ON;");
401 sql2.Append ("SET FMTONLY OFF;");
404 switch (CommandType) {
405 case CommandType.StoredProcedure:
406 if (keyInfo || schemaOnly)
407 Connection.Tds.Execute (sql1.ToString ());
408 Connection.Tds.ExecProc (CommandText, parms, CommandTimeout, wantResults);
409 if (keyInfo || schemaOnly)
410 Connection.Tds.Execute (sql2.ToString ());
412 case CommandType.Text:
414 if (sql2.Length > 0) {
415 sql = String.Format ("{0}{1};{2}", sql1.ToString (), CommandText, sql2.ToString ());
417 sql = String.Format ("{0}{1}", sql1.ToString (), CommandText);
419 Connection.Tds.Execute (sql, parms, CommandTimeout, wantResults);
424 Connection.Tds.ExecPrepared (preparedStatement, parms, CommandTimeout, wantResults);
431 int ExecuteNonQuery ()
433 ValidateCommand ("ExecuteNonQuery");
435 behavior = CommandBehavior.Default;
438 Execute (CommandBehavior.Default, false);
439 result = Connection.Tds.RecordsAffected;
441 catch (TdsTimeoutException e) {
442 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
445 GetOutputParameters ();
449 public new SqlDataReader ExecuteReader ()
451 return ExecuteReader (CommandBehavior.Default);
454 public new SqlDataReader ExecuteReader (CommandBehavior behavior)
456 ValidateCommand ("ExecuteReader");
458 this.behavior = behavior;
459 if ((behavior & CommandBehavior.SequentialAccess) != 0)
460 Tds.SequentialAccess = true;
461 Execute (behavior, true);
462 Connection.DataReader = new SqlDataReader (this);
464 catch (TdsTimeoutException e) {
465 // if behavior is closeconnection, even if it throws exception
466 // the connection has to be closed.
467 if ((behavior & CommandBehavior.CloseConnection) != 0)
469 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
470 } catch (SqlException) {
471 // if behavior is closeconnection, even if it throws exception
472 // the connection has to be closed.
473 if ((behavior & CommandBehavior.CloseConnection) != 0)
479 return Connection.DataReader;
486 object ExecuteScalar ()
489 object result = null;
490 ValidateCommand ("ExecuteScalar");
491 behavior = CommandBehavior.Default;
493 Execute (CommandBehavior.Default, true);
495 catch (TdsTimeoutException e) {
496 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
499 if (Connection.Tds.NextResult () && Connection.Tds.NextRow ())
500 result = Connection.Tds.ColumnValues[0];
502 if (commandType == CommandType.StoredProcedure) {
503 Connection.Tds.SkipToEnd ();
504 GetOutputParameters ();
510 CloseDataReader (true);
514 public XmlReader ExecuteXmlReader ()
516 ValidateCommand ("ExecuteXmlReader");
517 behavior = CommandBehavior.Default;
519 Execute (CommandBehavior.Default, true);
521 catch (TdsTimeoutException e) {
522 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
525 SqlDataReader dataReader = new SqlDataReader (this);
526 SqlXmlTextReader textReader = new SqlXmlTextReader (dataReader);
527 XmlReader xmlReader = new XmlTextReader (textReader);
531 internal void GetOutputParameters ()
533 IList list = Connection.Tds.OutputParameters;
535 if (list != null && list.Count > 0) {
538 foreach (SqlParameter parameter in parameters) {
539 if (parameter.Direction != ParameterDirection.Input) {
540 parameter.Value = list [index];
543 if (index >= list.Count)
549 object ICloneable.Clone ()
551 return new SqlCommand (commandText, connection, transaction, commandType, updatedRowSource, designTimeVisible, commandTimeout, parameters);
555 IDbDataParameter IDbCommand.CreateParameter ()
557 return CreateParameter ();
560 IDataReader IDbCommand.ExecuteReader ()
562 return ExecuteReader ();
565 IDataReader IDbCommand.ExecuteReader (CommandBehavior behavior)
567 return ExecuteReader (behavior);
576 ValidateCommand ("Prepare");
578 if (CommandType == CommandType.StoredProcedure)
582 foreach (SqlParameter param in Parameters)
583 param.CheckIfInitialized ();
584 }catch (Exception e) {
585 throw new InvalidOperationException ("SqlCommand.Prepare requires " + e.Message);
588 preparedStatement = Connection.Tds.Prepare (CommandText, Parameters.MetaParameters);
591 public void ResetCommandTimeout ()
596 private void Unprepare ()
598 Connection.Tds.Unprepare (preparedStatement);
599 preparedStatement = null;
602 private void ValidateCommand (string method)
604 if (Connection == null)
606 throw new NullReferenceException (String.Format ("{0} requires a Connection object to continue.", method));
608 throw new InvalidOperationException (String.Format ("{0} requires a Connection object to continue.", method));
610 if (Connection.Transaction != null && transaction != Connection.Transaction)
611 throw new InvalidOperationException ("The Connection object does not have the same transaction as the command object.");
612 if (Connection.State != ConnectionState.Open)
614 throw new NullReferenceException (String.Format ("ExecuteNonQuery requires an open Connection object to continue. This connection is closed.", method));
616 throw new InvalidOperationException (String.Format ("ExecuteNonQuery requires an open Connection object to continue. This connection is closed.", method));
618 if (commandText == String.Empty || commandText == null)
619 throw new InvalidOperationException ("The command text for this Command has not been set.");
620 if (Connection.DataReader != null)
621 throw new InvalidOperationException ("There is already an open DataReader associated with this Connection which must be closed first.");
622 if (Connection.XmlReader != null)
623 throw new InvalidOperationException ("There is already an open XmlReader associated with this Connection which must be closed first.");
625 if (method.StartsWith ("Begin") && !Connection.AsyncProcessing)
626 throw new InvalidOperationException ("This Connection object is not " +
627 "in Asynchronous mode. Use 'Asynchronous" +
628 " Processing = true' to set it.");
633 protected override DbParameter CreateDbParameter ()
635 return (DbParameter) CreateParameter ();
638 protected override DbDataReader ExecuteDbDataReader (CommandBehavior behavior)
640 return (DbDataReader) ExecuteReader (behavior);
643 protected override DbConnection DbConnection
645 get { return (DbConnection) Connection; }
646 set { Connection = (SqlConnection) value; }
649 protected override DbParameterCollection DbParameterCollection
651 get { return (DbParameterCollection) Parameters; }
654 protected override DbTransaction DbTransaction
656 get { return (DbTransaction) Transaction; }
657 set { Transaction = (SqlTransaction) value; }
661 #endregion // Methods
664 #region Asynchronous Methods
666 internal IAsyncResult BeginExecuteInternal (CommandBehavior behavior,
668 AsyncCallback callback,
671 IAsyncResult ar = null;
672 Connection.Tds.RecordsAffected = -1;
673 TdsMetaParameterCollection parms = Parameters.MetaParameters;
674 if (preparedStatement == null) {
675 bool schemaOnly = ((behavior & CommandBehavior.SchemaOnly) > 0);
676 bool keyInfo = ((behavior & CommandBehavior.KeyInfo) > 0);
678 StringBuilder sql1 = new StringBuilder ();
679 StringBuilder sql2 = new StringBuilder ();
681 if (schemaOnly || keyInfo)
682 sql1.Append ("SET FMTONLY OFF;");
684 sql1.Append ("SET NO_BROWSETABLE ON;");
685 sql2.Append ("SET NO_BROWSETABLE OFF;");
688 sql1.Append ("SET FMTONLY ON;");
689 sql2.Append ("SET FMTONLY OFF;");
692 switch (CommandType) {
693 case CommandType.StoredProcedure:
696 if (keyInfo || schemaOnly)
697 prolog = sql1.ToString ();
698 if (keyInfo || schemaOnly)
699 epilog = sql2.ToString ();
700 Connection.Tds.BeginExecuteProcedure (prolog,
709 case CommandType.Text:
710 string sql = String.Format ("{0}{1};{2}", sql1.ToString (), CommandText, sql2.ToString ());
712 ar = Connection.Tds.BeginExecuteQuery (sql, parms,
715 ar = Connection.Tds.BeginExecuteNonQuery (sql, parms, callback, state);
720 Connection.Tds.ExecPrepared (preparedStatement, parms, CommandTimeout, wantResults);
726 internal void EndExecuteInternal (IAsyncResult ar)
728 SqlAsyncResult sqlResult = ( (SqlAsyncResult) ar);
729 Connection.Tds.WaitFor (sqlResult.InternalResult);
730 Connection.Tds.CheckAndThrowException (sqlResult.InternalResult);
733 public IAsyncResult BeginExecuteNonQuery ()
735 return BeginExecuteNonQuery (null, null);
738 public IAsyncResult BeginExecuteNonQuery (AsyncCallback callback, object state)
740 ValidateCommand ("BeginExecuteNonQuery");
741 SqlAsyncResult ar = new SqlAsyncResult (callback, state);
742 ar.EndMethod = "EndExecuteNonQuery";
743 ar.InternalResult = BeginExecuteInternal (CommandBehavior.Default, false, ar.BubbleCallback, ar);
747 public int EndExecuteNonQuery (IAsyncResult ar)
749 ValidateAsyncResult (ar, "EndExecuteNonQuery");
750 EndExecuteInternal (ar);
752 int ret = Connection.Tds.RecordsAffected;
754 GetOutputParameters ();
755 ( (SqlAsyncResult) ar).Ended = true;
759 public IAsyncResult BeginExecuteReader ()
761 return BeginExecuteReader (null, null, CommandBehavior.Default);
764 public IAsyncResult BeginExecuteReader (CommandBehavior behavior)
766 return BeginExecuteReader (null, null, behavior);
769 public IAsyncResult BeginExecuteReader (AsyncCallback callback, object state)
771 return BeginExecuteReader (callback, state, CommandBehavior.Default);
774 public IAsyncResult BeginExecuteReader (AsyncCallback callback, object state, CommandBehavior behavior)
776 ValidateCommand ("BeginExecuteReader");
777 this.behavior = behavior;
778 SqlAsyncResult ar = new SqlAsyncResult (callback, state);
779 ar.EndMethod = "EndExecuteReader";
780 IAsyncResult tdsResult = BeginExecuteInternal (behavior, true,
781 ar.BubbleCallback, state);
782 ar.InternalResult = tdsResult;
786 public SqlDataReader EndExecuteReader (IAsyncResult ar)
788 ValidateAsyncResult (ar, "EndExecuteReader");
789 EndExecuteInternal (ar);
790 SqlDataReader reader = null;
792 reader = new SqlDataReader (this);
794 catch (TdsTimeoutException e) {
795 // if behavior is closeconnection, even if it throws exception
796 // the connection has to be closed.
797 if ((behavior & CommandBehavior.CloseConnection) != 0)
799 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
800 } catch (SqlException) {
801 // if behavior is closeconnection, even if it throws exception
802 // the connection has to be closed.
803 if ((behavior & CommandBehavior.CloseConnection) != 0)
809 ( (SqlAsyncResult) ar).Ended = true;
813 public IAsyncResult BeginExecuteXmlReader (AsyncCallback callback, object state)
815 ValidateCommand ("BeginExecuteXmlReader");
816 SqlAsyncResult ar = new SqlAsyncResult (callback, state);
817 ar.EndMethod = "EndExecuteXmlReader";
818 ar.InternalResult = BeginExecuteInternal (behavior, true,
819 ar.BubbleCallback, state);
823 public XmlReader EndExecuteXmlReader (IAsyncResult ar)
825 ValidateAsyncResult (ar, "EndExecuteXmlReader");
826 EndExecuteInternal (ar);
827 SqlDataReader reader = new SqlDataReader (this);
828 SqlXmlTextReader textReader = new SqlXmlTextReader (reader);
829 XmlReader xmlReader = new XmlTextReader (textReader);
830 ( (SqlAsyncResult) ar).Ended = true;
835 internal void ValidateAsyncResult (IAsyncResult ar, string endMethod)
838 throw new ArgumentException ("result passed is null!");
839 if (! (ar is SqlAsyncResult))
840 throw new ArgumentException (String.Format ("cannot test validity of types {0}",
843 SqlAsyncResult result = (SqlAsyncResult) ar;
845 if (result.EndMethod != endMethod)
846 throw new InvalidOperationException (String.Format ("Mismatched {0} called for AsyncResult. " +
847 "Expected call to {1} but {0} is called instead.",
852 throw new InvalidOperationException (String.Format ("The method {0} cannot be called " +
853 "more than once for the same AsyncResult.",
858 #endregion // Asynchronous Methods
859 public event StatementCompletedEventHandler StatementCompleted;