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;
47 using System.Data.Sql;
49 using System.Runtime.InteropServices;
53 namespace System.Data.SqlClient {
54 [DesignerAttribute ("Microsoft.VSDesigner.Data.VS.SqlCommandDesigner, "+ Consts.AssemblyMicrosoft_VSDesigner, "System.ComponentModel.Design.IDesigner")]
55 [ToolboxItemAttribute ("System.Drawing.Design.ToolboxItem, "+ Consts.AssemblySystem_Drawing)]
56 [DefaultEventAttribute ("RecordsAffected")]
58 public sealed class SqlCommand : DbCommand, IDbCommand, ICloneable
60 public sealed class SqlCommand : Component, IDbCommand, ICloneable
65 bool disposed = false;
67 bool designTimeVisible;
69 CommandType commandType;
70 SqlConnection connection;
71 SqlTransaction transaction;
72 UpdateRowSource updatedRowSource;
73 CommandBehavior behavior = CommandBehavior.Default;
74 SqlParameterCollection parameters;
75 string preparedStatement = null;
77 SqlNotificationRequest notification;
79 bool notificationAutoEnlist;
86 : this (String.Empty, null, null)
90 public SqlCommand (string commandText)
91 : this (commandText, null, null)
95 public SqlCommand (string commandText, SqlConnection connection)
96 : this (commandText, connection, null)
100 public SqlCommand (string commandText, SqlConnection connection, SqlTransaction transaction)
102 this.commandText = commandText;
103 this.connection = connection;
104 this.transaction = transaction;
105 this.commandType = CommandType.Text;
106 this.updatedRowSource = UpdateRowSource.Both;
108 this.designTimeVisible = false;
109 this.commandTimeout = 30;
110 notificationAutoEnlist = true;
111 parameters = new SqlParameterCollection (this);
114 private SqlCommand(string commandText, SqlConnection connection, SqlTransaction transaction, CommandType commandType, UpdateRowSource updatedRowSource, bool designTimeVisible, int commandTimeout, SqlParameterCollection parameters)
116 this.commandText = commandText;
117 this.connection = connection;
118 this.transaction = transaction;
119 this.commandType = commandType;
120 this.updatedRowSource = updatedRowSource;
121 this.designTimeVisible = designTimeVisible;
122 this.commandTimeout = commandTimeout;
123 this.parameters = new SqlParameterCollection(this);
124 for (int i = 0;i < parameters.Count;i++)
125 this.parameters.Add(((ICloneable)parameters[i]).Clone());
127 #endregion // Constructors
131 internal CommandBehavior CommandBehavior {
132 get { return behavior; }
135 [DataCategory ("Data")]
137 [DataSysDescription ("Command text to execute.")]
140 [EditorAttribute ("Microsoft.VSDesigner.Data.SQL.Design.SqlCommandTextEditor, "+ Consts.AssemblyMicrosoft_VSDesigner, "System.Drawing.Design.UITypeEditor, "+ Consts.AssemblySystem_Drawing )]
141 [RefreshProperties (RefreshProperties.All)]
147 get { return commandText; }
149 if (value != commandText && preparedStatement != null)
156 [DataSysDescription ("Time to wait for command to execute.")]
164 get { return commandTimeout; }
167 throw new ArgumentException ("The property value assigned is less than 0.");
168 commandTimeout = value;
172 [DataCategory ("Data")]
174 [DataSysDescription ("How to interpret the CommandText.")]
176 [DefaultValue (CommandType.Text)]
177 [RefreshProperties (RefreshProperties.All)]
182 CommandType CommandType {
183 get { return commandType; }
185 if (value == CommandType.TableDirect)
187 throw new ArgumentOutOfRangeException ("CommandType.TableDirect is not supported " +
188 "by the Mono SqlClient Data Provider.");
190 throw new ArgumentException ("CommandType.TableDirect is not supported by the Mono SqlClient Data Provider.");
193 if (!Enum.IsDefined (typeof (CommandType), value))
195 throw new ArgumentOutOfRangeException (String.Format ("The CommandType enumeration value, {0}, is invalid",
198 throw ExceptionHelper.InvalidEnumValueException ("CommandType", value);
204 [DataCategory ("Behavior")]
205 [DefaultValue (null)]
207 [DataSysDescription ("Connection used by the command.")]
209 [EditorAttribute ("Microsoft.VSDesigner.Data.Design.DbConnectionEditor, "+ Consts.AssemblyMicrosoft_VSDesigner, "System.Drawing.Design.UITypeEditor, "+ Consts.AssemblySystem_Drawing )]
214 SqlConnection Connection {
215 get { return connection; }
217 if (transaction != null && connection.Transaction != null && connection.Transaction.IsOpen)
218 throw new InvalidOperationException ("The Connection property was changed while a transaction was in progress.");
225 [DefaultValue (true)]
227 [EditorBrowsable (EditorBrowsableState.Never)]
232 bool DesignTimeVisible {
233 get { return designTimeVisible; }
234 set { designTimeVisible = value; }
237 [DataCategory ("Data")]
239 [DataSysDescription ("The parameters collection.")]
241 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
246 SqlParameterCollection Parameters {
247 get { return parameters; }
251 get { return Connection.Tds; }
254 IDbConnection IDbCommand.Connection {
255 get { return Connection; }
257 if (!(value is SqlConnection))
258 throw new InvalidCastException ("The value was not a valid SqlConnection.");
259 Connection = (SqlConnection) value;
263 IDataParameterCollection IDbCommand.Parameters {
264 get { return Parameters; }
267 IDbTransaction IDbCommand.Transaction {
268 get { return Transaction; }
270 if (!(value is SqlTransaction))
271 throw new ArgumentException ();
272 Transaction = (SqlTransaction) value;
278 [DataSysDescription ("The transaction used by the command.")]
280 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
281 public new SqlTransaction Transaction {
282 get { return transaction; }
283 set { transaction = value; }
286 [DataCategory ("Behavior")]
288 [DataSysDescription ("When used by a DataAdapter.Update, how command results are applied to the current DataRow.")]
290 [DefaultValue (UpdateRowSource.Both)]
295 UpdateRowSource UpdatedRowSource {
296 get { return updatedRowSource; }
298 if (!Enum.IsDefined (typeof (UpdateRowSource), value))
300 throw new ArgumentOutOfRangeException (String.Format ("The UpdateRowSource enumeration value, {0}, is invalid",
303 throw ExceptionHelper.InvalidEnumValueException ("UpdateRowSource", value);
305 updatedRowSource = value;
310 public SqlNotificationRequest Notification {
311 get { return notification; }
312 set { notification = value; }
315 [DefaultValue (true)]
316 public bool NotificationAutoEnlist {
317 get { return notificationAutoEnlist; }
318 set { notificationAutoEnlist = value; }
331 if (Connection == null || Connection.Tds == null)
333 Connection.Tds.Cancel ();
337 public SqlCommand Clone ()
339 return new SqlCommand (commandText, connection, transaction, commandType, updatedRowSource, designTimeVisible, commandTimeout, parameters);
343 internal void CloseDataReader (bool moreResults)
345 Connection.DataReader = null;
347 if ((behavior & CommandBehavior.CloseConnection) != 0)
350 // Reset the behavior
351 behavior = CommandBehavior.Default;
353 Tds.SequentialAccess = false;
356 public new SqlParameter CreateParameter ()
358 return new SqlParameter ();
361 internal void DeriveParameters ()
363 if (commandType != CommandType.StoredProcedure)
364 throw new InvalidOperationException (String.Format ("SqlCommand DeriveParameters only supports CommandType.StoredProcedure, not CommandType.{0}", commandType));
365 ValidateCommand ("DeriveParameters");
367 SqlParameterCollection localParameters = new SqlParameterCollection (this);
368 localParameters.Add ("@procedure_name", SqlDbType.NVarChar, commandText.Length).Value = commandText;
370 string sql = "sp_procedure_params_rowset";
372 Connection.Tds.ExecProc (sql, localParameters.MetaParameters, 0, true);
374 SqlDataReader reader = new SqlDataReader (this);
376 object[] dbValues = new object[reader.FieldCount];
378 while (reader.Read ()) {
379 reader.GetValues (dbValues);
380 parameters.Add (new SqlParameter (dbValues));
386 private void Execute (CommandBehavior behavior, bool wantResults)
389 Connection.Tds.RecordsAffected = -1;
390 TdsMetaParameterCollection parms = Parameters.MetaParameters;
391 foreach (TdsMetaParameter param in parms) {
392 param.Validate (index++);
395 if (preparedStatement == null) {
396 bool schemaOnly = ((behavior & CommandBehavior.SchemaOnly) > 0);
397 bool keyInfo = ((behavior & CommandBehavior.KeyInfo) > 0);
399 StringBuilder sql1 = new StringBuilder ();
400 StringBuilder sql2 = new StringBuilder ();
402 if (schemaOnly || keyInfo)
403 sql1.Append ("SET FMTONLY OFF;");
405 sql1.Append ("SET NO_BROWSETABLE ON;");
406 sql2.Append ("SET NO_BROWSETABLE OFF;");
409 sql1.Append ("SET FMTONLY ON;");
410 sql2.Append ("SET FMTONLY OFF;");
413 switch (CommandType) {
414 case CommandType.StoredProcedure:
415 if (keyInfo || schemaOnly)
416 Connection.Tds.Execute (sql1.ToString ());
417 Connection.Tds.ExecProc (CommandText, parms, CommandTimeout, wantResults);
418 if (keyInfo || schemaOnly)
419 Connection.Tds.Execute (sql2.ToString ());
421 case CommandType.Text:
423 if (sql2.Length > 0) {
424 sql = String.Format ("{0}{1};{2}", sql1.ToString (), CommandText, sql2.ToString ());
426 sql = String.Format ("{0}{1}", sql1.ToString (), CommandText);
428 Connection.Tds.Execute (sql, parms, CommandTimeout, wantResults);
433 Connection.Tds.ExecPrepared (preparedStatement, parms, CommandTimeout, wantResults);
440 int ExecuteNonQuery ()
442 ValidateCommand ("ExecuteNonQuery");
444 behavior = CommandBehavior.Default;
447 Execute (CommandBehavior.Default, false);
448 result = Connection.Tds.RecordsAffected;
450 catch (TdsTimeoutException e) {
451 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
454 GetOutputParameters ();
458 public new SqlDataReader ExecuteReader ()
460 return ExecuteReader (CommandBehavior.Default);
463 public new SqlDataReader ExecuteReader (CommandBehavior behavior)
465 ValidateCommand ("ExecuteReader");
467 this.behavior = behavior;
468 if ((behavior & CommandBehavior.SequentialAccess) != 0)
469 Tds.SequentialAccess = true;
470 Execute (behavior, true);
471 Connection.DataReader = new SqlDataReader (this);
473 catch (TdsTimeoutException e) {
474 // if behavior is closeconnection, even if it throws exception
475 // the connection has to be closed.
476 if ((behavior & CommandBehavior.CloseConnection) != 0)
478 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
479 } catch (SqlException) {
480 // if behavior is closeconnection, even if it throws exception
481 // the connection has to be closed.
482 if ((behavior & CommandBehavior.CloseConnection) != 0)
488 return Connection.DataReader;
495 object ExecuteScalar ()
498 object result = null;
499 ValidateCommand ("ExecuteScalar");
500 behavior = CommandBehavior.Default;
502 Execute (CommandBehavior.Default, true);
504 catch (TdsTimeoutException e) {
505 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
508 if (Connection.Tds.NextResult () && Connection.Tds.NextRow ())
509 result = Connection.Tds.ColumnValues[0];
511 if (commandType == CommandType.StoredProcedure) {
512 Connection.Tds.SkipToEnd ();
513 GetOutputParameters ();
519 CloseDataReader (true);
523 public XmlReader ExecuteXmlReader ()
525 ValidateCommand ("ExecuteXmlReader");
526 behavior = CommandBehavior.Default;
528 Execute (CommandBehavior.Default, true);
530 catch (TdsTimeoutException e) {
531 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
534 SqlDataReader dataReader = new SqlDataReader (this);
535 SqlXmlTextReader textReader = new SqlXmlTextReader (dataReader);
536 XmlReader xmlReader = new XmlTextReader (textReader);
540 internal void GetOutputParameters ()
542 IList list = Connection.Tds.OutputParameters;
544 if (list != null && list.Count > 0) {
547 foreach (SqlParameter parameter in parameters) {
548 if (parameter.Direction != ParameterDirection.Input) {
549 parameter.Value = list [index];
552 if (index >= list.Count)
558 object ICloneable.Clone ()
560 return new SqlCommand (commandText, connection, transaction, commandType, updatedRowSource, designTimeVisible, commandTimeout, parameters);
564 IDbDataParameter IDbCommand.CreateParameter ()
566 return CreateParameter ();
569 IDataReader IDbCommand.ExecuteReader ()
571 return ExecuteReader ();
574 IDataReader IDbCommand.ExecuteReader (CommandBehavior behavior)
576 return ExecuteReader (behavior);
579 protected override void Dispose (bool disposing)
581 if (disposed) return;
596 ValidateCommand ("Prepare");
598 if (CommandType == CommandType.StoredProcedure)
602 foreach (SqlParameter param in Parameters)
603 param.CheckIfInitialized ();
604 }catch (Exception e) {
605 throw new InvalidOperationException ("SqlCommand.Prepare requires " + e.Message);
608 preparedStatement = Connection.Tds.Prepare (CommandText, Parameters.MetaParameters);
611 public void ResetCommandTimeout ()
616 private void Unprepare ()
618 Connection.Tds.Unprepare (preparedStatement);
619 preparedStatement = null;
622 private void ValidateCommand (string method)
624 if (Connection == null)
626 throw new NullReferenceException (String.Format ("{0} requires a Connection object to continue.", method));
628 throw new InvalidOperationException (String.Format ("{0} requires a Connection object to continue.", method));
630 if (Connection.Transaction != null && transaction != Connection.Transaction)
631 throw new InvalidOperationException ("The Connection object does not have the same transaction as the command object.");
632 if (Connection.State != ConnectionState.Open)
634 throw new NullReferenceException (String.Format ("ExecuteNonQuery requires an open Connection object to continue. This connection is closed.", method));
636 throw new InvalidOperationException (String.Format ("ExecuteNonQuery requires an open Connection object to continue. This connection is closed.", method));
638 if (commandText == String.Empty || commandText == null)
639 throw new InvalidOperationException ("The command text for this Command has not been set.");
640 if (Connection.DataReader != null)
641 throw new InvalidOperationException ("There is already an open DataReader associated with this Connection which must be closed first.");
642 if (Connection.XmlReader != null)
643 throw new InvalidOperationException ("There is already an open XmlReader associated with this Connection which must be closed first.");
645 if (method.StartsWith ("Begin") && !Connection.AsyncProcessing)
646 throw new InvalidOperationException ("This Connection object is not " +
647 "in Asynchronous mode. Use 'Asynchronous" +
648 " Processing = true' to set it.");
653 protected override DbParameter CreateDbParameter ()
655 return (DbParameter) CreateParameter ();
658 protected override DbDataReader ExecuteDbDataReader (CommandBehavior behavior)
660 return (DbDataReader) ExecuteReader (behavior);
663 protected override DbConnection DbConnection
665 get { return (DbConnection) Connection; }
666 set { Connection = (SqlConnection) value; }
669 protected override DbParameterCollection DbParameterCollection
671 get { return (DbParameterCollection) Parameters; }
674 protected override DbTransaction DbTransaction
676 get { return (DbTransaction) Transaction; }
677 set { Transaction = (SqlTransaction) value; }
681 #endregion // Methods
684 #region Asynchronous Methods
686 internal IAsyncResult BeginExecuteInternal (CommandBehavior behavior,
688 AsyncCallback callback,
691 IAsyncResult ar = null;
692 Connection.Tds.RecordsAffected = -1;
693 TdsMetaParameterCollection parms = Parameters.MetaParameters;
694 if (preparedStatement == null) {
695 bool schemaOnly = ((behavior & CommandBehavior.SchemaOnly) > 0);
696 bool keyInfo = ((behavior & CommandBehavior.KeyInfo) > 0);
698 StringBuilder sql1 = new StringBuilder ();
699 StringBuilder sql2 = new StringBuilder ();
701 if (schemaOnly || keyInfo)
702 sql1.Append ("SET FMTONLY OFF;");
704 sql1.Append ("SET NO_BROWSETABLE ON;");
705 sql2.Append ("SET NO_BROWSETABLE OFF;");
708 sql1.Append ("SET FMTONLY ON;");
709 sql2.Append ("SET FMTONLY OFF;");
712 switch (CommandType) {
713 case CommandType.StoredProcedure:
716 if (keyInfo || schemaOnly)
717 prolog = sql1.ToString ();
718 if (keyInfo || schemaOnly)
719 epilog = sql2.ToString ();
720 Connection.Tds.BeginExecuteProcedure (prolog,
729 case CommandType.Text:
730 string sql = String.Format ("{0}{1};{2}", sql1.ToString (), CommandText, sql2.ToString ());
732 ar = Connection.Tds.BeginExecuteQuery (sql, parms,
735 ar = Connection.Tds.BeginExecuteNonQuery (sql, parms, callback, state);
740 Connection.Tds.ExecPrepared (preparedStatement, parms, CommandTimeout, wantResults);
746 internal void EndExecuteInternal (IAsyncResult ar)
748 SqlAsyncResult sqlResult = ( (SqlAsyncResult) ar);
749 Connection.Tds.WaitFor (sqlResult.InternalResult);
750 Connection.Tds.CheckAndThrowException (sqlResult.InternalResult);
753 public IAsyncResult BeginExecuteNonQuery ()
755 return BeginExecuteNonQuery (null, null);
758 public IAsyncResult BeginExecuteNonQuery (AsyncCallback callback, object state)
760 ValidateCommand ("BeginExecuteNonQuery");
761 SqlAsyncResult ar = new SqlAsyncResult (callback, state);
762 ar.EndMethod = "EndExecuteNonQuery";
763 ar.InternalResult = BeginExecuteInternal (CommandBehavior.Default, false, ar.BubbleCallback, ar);
767 public int EndExecuteNonQuery (IAsyncResult ar)
769 ValidateAsyncResult (ar, "EndExecuteNonQuery");
770 EndExecuteInternal (ar);
772 int ret = Connection.Tds.RecordsAffected;
774 GetOutputParameters ();
775 ( (SqlAsyncResult) ar).Ended = true;
779 public IAsyncResult BeginExecuteReader ()
781 return BeginExecuteReader (null, null, CommandBehavior.Default);
784 public IAsyncResult BeginExecuteReader (CommandBehavior behavior)
786 return BeginExecuteReader (null, null, behavior);
789 public IAsyncResult BeginExecuteReader (AsyncCallback callback, object state)
791 return BeginExecuteReader (callback, state, CommandBehavior.Default);
794 public IAsyncResult BeginExecuteReader (AsyncCallback callback, object state, CommandBehavior behavior)
796 ValidateCommand ("BeginExecuteReader");
797 this.behavior = behavior;
798 SqlAsyncResult ar = new SqlAsyncResult (callback, state);
799 ar.EndMethod = "EndExecuteReader";
800 IAsyncResult tdsResult = BeginExecuteInternal (behavior, true,
801 ar.BubbleCallback, state);
802 ar.InternalResult = tdsResult;
806 public SqlDataReader EndExecuteReader (IAsyncResult ar)
808 ValidateAsyncResult (ar, "EndExecuteReader");
809 EndExecuteInternal (ar);
810 SqlDataReader reader = null;
812 reader = new SqlDataReader (this);
814 catch (TdsTimeoutException e) {
815 // if behavior is closeconnection, even if it throws exception
816 // the connection has to be closed.
817 if ((behavior & CommandBehavior.CloseConnection) != 0)
819 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
820 } catch (SqlException) {
821 // if behavior is closeconnection, even if it throws exception
822 // the connection has to be closed.
823 if ((behavior & CommandBehavior.CloseConnection) != 0)
829 ( (SqlAsyncResult) ar).Ended = true;
833 public IAsyncResult BeginExecuteXmlReader (AsyncCallback callback, object state)
835 ValidateCommand ("BeginExecuteXmlReader");
836 SqlAsyncResult ar = new SqlAsyncResult (callback, state);
837 ar.EndMethod = "EndExecuteXmlReader";
838 ar.InternalResult = BeginExecuteInternal (behavior, true,
839 ar.BubbleCallback, state);
843 public IAsyncResult BeginExecuteXmlReader ()
845 return BeginExecuteXmlReader (null, null);
849 public XmlReader EndExecuteXmlReader (IAsyncResult ar)
851 ValidateAsyncResult (ar, "EndExecuteXmlReader");
852 EndExecuteInternal (ar);
853 SqlDataReader reader = new SqlDataReader (this);
854 SqlXmlTextReader textReader = new SqlXmlTextReader (reader);
855 XmlReader xmlReader = new XmlTextReader (textReader);
856 ( (SqlAsyncResult) ar).Ended = true;
861 internal void ValidateAsyncResult (IAsyncResult ar, string endMethod)
864 throw new ArgumentException ("result passed is null!");
865 if (! (ar is SqlAsyncResult))
866 throw new ArgumentException (String.Format ("cannot test validity of types {0}",
869 SqlAsyncResult result = (SqlAsyncResult) ar;
871 if (result.EndMethod != endMethod)
872 throw new InvalidOperationException (String.Format ("Mismatched {0} called for AsyncResult. " +
873 "Expected call to {1} but {0} is called instead.",
878 throw new InvalidOperationException (String.Format ("The method {0} cannot be called " +
879 "more than once for the same AsyncResult.",
884 #endregion // Asynchronous Methods
885 public event StatementCompletedEventHandler StatementCompleted;