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)]
54 public sealed class SqlCommand : DbCommand, IDbCommand, ICloneable
56 public sealed class SqlCommand : Component, IDbCommand, ICloneable
61 //bool disposed = false;
63 bool designTimeVisible;
65 CommandType commandType;
66 SqlConnection connection;
67 SqlTransaction transaction;
68 UpdateRowSource updatedRowSource;
69 CommandBehavior behavior = CommandBehavior.Default;
70 SqlParameterCollection parameters;
71 string preparedStatement = null;
78 : this (String.Empty, null, null)
82 public SqlCommand (string commandText)
83 : this (commandText, null, null)
87 public SqlCommand (string commandText, SqlConnection connection)
88 : this (commandText, connection, null)
92 public SqlCommand (string commandText, SqlConnection connection, SqlTransaction transaction)
94 this.commandText = commandText;
95 this.connection = connection;
96 this.transaction = transaction;
97 this.commandType = CommandType.Text;
98 this.updatedRowSource = UpdateRowSource.Both;
100 this.designTimeVisible = false;
101 this.commandTimeout = 30;
102 parameters = new SqlParameterCollection (this);
105 private SqlCommand(string commandText, SqlConnection connection, SqlTransaction transaction, CommandType commandType, UpdateRowSource updatedRowSource, bool designTimeVisible, int commandTimeout, SqlParameterCollection parameters)
107 this.commandText = commandText;
108 this.connection = connection;
109 this.transaction = transaction;
110 this.commandType = commandType;
111 this.updatedRowSource = updatedRowSource;
112 this.designTimeVisible = designTimeVisible;
113 this.commandTimeout = commandTimeout;
114 this.parameters = new SqlParameterCollection(this);
115 for (int i = 0;i < parameters.Count;i++)
116 this.parameters.Add(((ICloneable)parameters[i]).Clone());
118 #endregion // Constructors
122 internal CommandBehavior CommandBehavior {
123 get { return behavior; }
126 [DataCategory ("Data")]
128 [DataSysDescription ("Command text to execute.")]
131 [EditorAttribute ("Microsoft.VSDesigner.Data.SQL.Design.SqlCommandTextEditor, "+ Consts.AssemblyMicrosoft_VSDesigner, "System.Drawing.Design.UITypeEditor, "+ Consts.AssemblySystem_Drawing )]
132 [RefreshProperties (RefreshProperties.All)]
138 get { return commandText; }
140 if (value != commandText && preparedStatement != null)
147 [DataSysDescription ("Time to wait for command to execute.")]
155 get { return commandTimeout; }
158 throw new ArgumentException ("The property value assigned is less than 0.");
159 commandTimeout = value;
163 [DataCategory ("Data")]
165 [DataSysDescription ("How to interpret the CommandText.")]
167 [DefaultValue (CommandType.Text)]
168 [RefreshProperties (RefreshProperties.All)]
173 CommandType CommandType {
174 get { return commandType; }
176 if (value == CommandType.TableDirect)
177 throw new ArgumentException ("CommandType.TableDirect is not supported by the Mono SqlClient Data Provider.");
179 if (!Enum.IsDefined (typeof (CommandType), value))
180 throw ExceptionHelper.InvalidEnumValueException ("CommandType", value);
185 [DataCategory ("Behavior")]
186 [DefaultValue (null)]
188 [DataSysDescription ("Connection used by the command.")]
190 [EditorAttribute ("Microsoft.VSDesigner.Data.Design.DbConnectionEditor, "+ Consts.AssemblyMicrosoft_VSDesigner, "System.Drawing.Design.UITypeEditor, "+ Consts.AssemblySystem_Drawing )]
195 SqlConnection Connection {
196 get { return connection; }
198 if (transaction != null && connection.Transaction != null && connection.Transaction.IsOpen)
199 throw new InvalidOperationException ("The Connection property was changed while a transaction was in progress.");
206 [DefaultValue (true)]
212 bool DesignTimeVisible {
213 get { return designTimeVisible; }
214 set { designTimeVisible = value; }
217 [DataCategory ("Data")]
219 [DataSysDescription ("The parameters collection.")]
221 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
226 SqlParameterCollection Parameters {
227 get { return parameters; }
231 get { return Connection.Tds; }
234 IDbConnection IDbCommand.Connection {
235 get { return Connection; }
237 if (!(value is SqlConnection))
238 throw new InvalidCastException ("The value was not a valid SqlConnection.");
239 Connection = (SqlConnection) value;
243 IDataParameterCollection IDbCommand.Parameters {
244 get { return Parameters; }
247 IDbTransaction IDbCommand.Transaction {
248 get { return Transaction; }
250 if (!(value is SqlTransaction))
251 throw new ArgumentException ();
252 Transaction = (SqlTransaction) value;
258 [DataSysDescription ("The transaction used by the command.")]
260 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
261 public new SqlTransaction Transaction {
262 get { return transaction; }
263 set { transaction = value; }
266 [DataCategory ("Behavior")]
268 [DataSysDescription ("When used by a DataAdapter.Update, how command results are applied to the current DataRow.")]
270 [DefaultValue (UpdateRowSource.Both)]
275 UpdateRowSource UpdatedRowSource {
276 get { return updatedRowSource; }
278 if (!Enum.IsDefined (typeof (UpdateRowSource), value))
279 throw ExceptionHelper.InvalidEnumValueException ("UpdateRowSource", value);
280 updatedRowSource = value;
294 if (Connection == null || Connection.Tds == null)
296 Connection.Tds.Cancel ();
299 internal void CloseDataReader (bool moreResults)
301 Connection.DataReader = null;
303 if ((behavior & CommandBehavior.CloseConnection) != 0)
306 // Reset the behavior
307 behavior = CommandBehavior.Default;
308 Tds.SequentialAccess = false;
311 public new SqlParameter CreateParameter ()
313 return new SqlParameter ();
316 internal void DeriveParameters ()
318 if (commandType != CommandType.StoredProcedure)
319 throw new InvalidOperationException (String.Format ("SqlCommand DeriveParameters only supports CommandType.StoredProcedure, not CommandType.{0}", commandType));
320 ValidateCommand ("DeriveParameters");
322 SqlParameterCollection localParameters = new SqlParameterCollection (this);
323 localParameters.Add ("@procedure_name", SqlDbType.NVarChar, commandText.Length).Value = commandText;
325 string sql = "sp_procedure_params_rowset";
327 Connection.Tds.ExecProc (sql, localParameters.MetaParameters, 0, true);
329 SqlDataReader reader = new SqlDataReader (this);
331 object[] dbValues = new object[reader.FieldCount];
333 while (reader.Read ()) {
334 reader.GetValues (dbValues);
335 parameters.Add (new SqlParameter (dbValues));
341 private void Execute (CommandBehavior behavior, bool wantResults)
343 Connection.Tds.RecordsAffected = -1;
344 TdsMetaParameterCollection parms = Parameters.MetaParameters;
345 if (preparedStatement == null) {
346 bool schemaOnly = ((behavior & CommandBehavior.SchemaOnly) > 0);
347 bool keyInfo = ((behavior & CommandBehavior.KeyInfo) > 0);
349 StringBuilder sql1 = new StringBuilder ();
350 StringBuilder sql2 = new StringBuilder ();
352 if (schemaOnly || keyInfo)
353 sql1.Append ("SET FMTONLY OFF;");
355 sql1.Append ("SET NO_BROWSETABLE ON;");
356 sql2.Append ("SET NO_BROWSETABLE OFF;");
359 sql1.Append ("SET FMTONLY ON;");
360 sql2.Append ("SET FMTONLY OFF;");
363 switch (CommandType) {
364 case CommandType.StoredProcedure:
365 if (keyInfo || schemaOnly)
366 Connection.Tds.Execute (sql1.ToString ());
367 Connection.Tds.ExecProc (CommandText, parms, CommandTimeout, wantResults);
368 if (keyInfo || schemaOnly)
369 Connection.Tds.Execute (sql2.ToString ());
371 case CommandType.Text:
372 //string sql = String.Format ("{0}{1};{2}", sql1.ToString (), CommandText, sql2.ToString ());
374 if (sql2.Length > 0) {
375 sql = String.Format ("{0}{1};{2}", sql1.ToString (), CommandText, sql2.ToString ());
377 sql = String.Format ("{0}{1}", sql1.ToString (), CommandText);
379 Connection.Tds.Execute (sql, parms, CommandTimeout, wantResults);
384 Connection.Tds.ExecPrepared (preparedStatement, parms, CommandTimeout, wantResults);
391 int ExecuteNonQuery ()
393 ValidateCommand ("ExecuteNonQuery");
395 behavior = CommandBehavior.Default;
398 Execute (CommandBehavior.Default, false);
399 result = Connection.Tds.RecordsAffected;
401 catch (TdsTimeoutException e) {
402 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
405 GetOutputParameters ();
409 public new SqlDataReader ExecuteReader ()
411 return ExecuteReader (CommandBehavior.Default);
414 public new SqlDataReader ExecuteReader (CommandBehavior behavior)
416 ValidateCommand ("ExecuteReader");
418 this.behavior = behavior;
419 if ((behavior & CommandBehavior.SequentialAccess) != 0)
420 Tds.SequentialAccess = true;
421 Execute (behavior, true);
422 Connection.DataReader = new SqlDataReader (this);
424 catch (TdsTimeoutException e) {
425 // if behavior is closeconnection, even if it throws exception
426 // the connection has to be closed.
427 if ((behavior & CommandBehavior.CloseConnection) != 0)
429 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
430 } catch (SqlException) {
431 // if behavior is closeconnection, even if it throws exception
432 // the connection has to be closed.
433 if ((behavior & CommandBehavior.CloseConnection) != 0)
439 return Connection.DataReader;
446 object ExecuteScalar ()
448 ValidateCommand ("ExecuteScalar");
449 behavior = CommandBehavior.Default;
451 Execute (CommandBehavior.Default, true);
453 catch (TdsTimeoutException e) {
454 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
457 if (!Connection.Tds.NextResult () || !Connection.Tds.NextRow ())
460 object result = Connection.Tds.ColumnValues [0];
461 CloseDataReader (true);
465 public XmlReader ExecuteXmlReader ()
467 ValidateCommand ("ExecuteXmlReader");
468 behavior = CommandBehavior.Default;
470 Execute (CommandBehavior.Default, true);
472 catch (TdsTimeoutException e) {
473 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
476 SqlDataReader dataReader = new SqlDataReader (this);
477 SqlXmlTextReader textReader = new SqlXmlTextReader (dataReader);
478 XmlReader xmlReader = new XmlTextReader (textReader);
482 internal void GetOutputParameters ()
484 IList list = Connection.Tds.OutputParameters;
486 if (list != null && list.Count > 0) {
489 foreach (SqlParameter parameter in parameters) {
490 if (parameter.Direction != ParameterDirection.Input) {
491 parameter.Value = list [index];
494 if (index >= list.Count)
500 object ICloneable.Clone ()
502 return new SqlCommand (commandText, connection, transaction, commandType, updatedRowSource, designTimeVisible, commandTimeout, parameters);
506 IDbDataParameter IDbCommand.CreateParameter ()
508 return CreateParameter ();
511 IDataReader IDbCommand.ExecuteReader ()
513 return ExecuteReader ();
516 IDataReader IDbCommand.ExecuteReader (CommandBehavior behavior)
518 return ExecuteReader (behavior);
527 ValidateCommand ("Prepare");
529 if (CommandType == CommandType.StoredProcedure)
533 foreach (SqlParameter param in Parameters)
534 param.CheckIfInitialized ();
535 }catch (Exception e) {
536 throw new InvalidOperationException ("SqlCommand.Prepare requires " + e.Message);
539 preparedStatement = Connection.Tds.Prepare (CommandText, Parameters.MetaParameters);
542 public void ResetCommandTimeout ()
547 private void Unprepare ()
549 Connection.Tds.Unprepare (preparedStatement);
550 preparedStatement = null;
553 private void ValidateCommand (string method)
555 if (Connection == null)
556 throw new InvalidOperationException (String.Format ("{0} requires a Connection object to continue.", method));
557 if (Connection.Transaction != null && transaction != Connection.Transaction)
558 throw new InvalidOperationException ("The Connection object does not have the same transaction as the command object.");
559 if (Connection.State != ConnectionState.Open)
560 throw new InvalidOperationException (String.Format ("ExecuteNonQuery requires an open Connection object to continue. This connection is closed.", method));
561 if (commandText == String.Empty || commandText == null)
562 throw new InvalidOperationException ("The command text for this Command has not been set.");
563 if (Connection.DataReader != null)
564 throw new InvalidOperationException ("There is already an open DataReader associated with this Connection which must be closed first.");
565 if (Connection.XmlReader != null)
566 throw new InvalidOperationException ("There is already an open XmlReader associated with this Connection which must be closed first.");
568 if (method.StartsWith ("Begin") && !Connection.AsyncProcessing)
569 throw new InvalidOperationException ("This Connection object is not " +
570 "in Asynchronous mode. Use 'Asynchronous" +
571 " Processing = true' to set it.");
576 protected override DbParameter CreateDbParameter ()
578 return (DbParameter) CreateParameter ();
581 protected override DbDataReader ExecuteDbDataReader (CommandBehavior behavior)
583 return (DbDataReader) ExecuteReader (behavior);
586 protected override DbConnection DbConnection
588 get { return (DbConnection) Connection; }
589 set { Connection = (SqlConnection) value; }
592 protected override DbParameterCollection DbParameterCollection
594 get { return (DbParameterCollection) Parameters; }
597 protected override DbTransaction DbTransaction
599 get { return (DbTransaction) Transaction; }
600 set { Transaction = (SqlTransaction) value; }
604 #endregion // Methods
607 #region Asynchronous Methods
609 internal IAsyncResult BeginExecuteInternal (CommandBehavior behavior,
611 AsyncCallback callback,
614 IAsyncResult ar = null;
615 Connection.Tds.RecordsAffected = -1;
616 TdsMetaParameterCollection parms = Parameters.MetaParameters;
617 if (preparedStatement == null) {
618 bool schemaOnly = ((behavior & CommandBehavior.SchemaOnly) > 0);
619 bool keyInfo = ((behavior & CommandBehavior.KeyInfo) > 0);
621 StringBuilder sql1 = new StringBuilder ();
622 StringBuilder sql2 = new StringBuilder ();
624 if (schemaOnly || keyInfo)
625 sql1.Append ("SET FMTONLY OFF;");
627 sql1.Append ("SET NO_BROWSETABLE ON;");
628 sql2.Append ("SET NO_BROWSETABLE OFF;");
631 sql1.Append ("SET FMTONLY ON;");
632 sql2.Append ("SET FMTONLY OFF;");
635 switch (CommandType) {
636 case CommandType.StoredProcedure:
639 if (keyInfo || schemaOnly)
640 prolog = sql1.ToString ();
641 if (keyInfo || schemaOnly)
642 epilog = sql2.ToString ();
643 Connection.Tds.BeginExecuteProcedure (prolog,
652 case CommandType.Text:
653 string sql = String.Format ("{0}{1};{2}", sql1.ToString (), CommandText, sql2.ToString ());
655 ar = Connection.Tds.BeginExecuteQuery (sql, parms,
658 ar = Connection.Tds.BeginExecuteNonQuery (sql, parms, callback, state);
663 Connection.Tds.ExecPrepared (preparedStatement, parms, CommandTimeout, wantResults);
669 internal void EndExecuteInternal (IAsyncResult ar)
671 SqlAsyncResult sqlResult = ( (SqlAsyncResult) ar);
672 Connection.Tds.WaitFor (sqlResult.InternalResult);
673 Connection.Tds.CheckAndThrowException (sqlResult.InternalResult);
676 public IAsyncResult BeginExecuteNonQuery ()
678 return BeginExecuteNonQuery (null, null);
681 public IAsyncResult BeginExecuteNonQuery (AsyncCallback callback, object state)
683 ValidateCommand ("BeginExecuteNonQuery");
684 SqlAsyncResult ar = new SqlAsyncResult (callback, state);
685 ar.EndMethod = "EndExecuteNonQuery";
686 ar.InternalResult = BeginExecuteInternal (CommandBehavior.Default, false, ar.BubbleCallback, ar);
690 public int EndExecuteNonQuery (IAsyncResult ar)
692 ValidateAsyncResult (ar, "EndExecuteNonQuery");
693 EndExecuteInternal (ar);
695 int ret = Connection.Tds.RecordsAffected;
697 GetOutputParameters ();
698 ( (SqlAsyncResult) ar).Ended = true;
702 public IAsyncResult BeginExecuteReader ()
704 return BeginExecuteReader (null, null, CommandBehavior.Default);
707 public IAsyncResult BeginExecuteReader (CommandBehavior behavior)
709 return BeginExecuteReader (null, null, behavior);
712 public IAsyncResult BeginExecuteReader (AsyncCallback callback, object state)
714 return BeginExecuteReader (callback, state, CommandBehavior.Default);
717 public IAsyncResult BeginExecuteReader (AsyncCallback callback, object state, CommandBehavior behavior)
719 ValidateCommand ("BeginExecuteReader");
720 this.behavior = behavior;
721 SqlAsyncResult ar = new SqlAsyncResult (callback, state);
722 ar.EndMethod = "EndExecuteReader";
723 IAsyncResult tdsResult = BeginExecuteInternal (behavior, true,
724 ar.BubbleCallback, state);
725 ar.InternalResult = tdsResult;
729 public SqlDataReader EndExecuteReader (IAsyncResult ar)
731 ValidateAsyncResult (ar, "EndExecuteReader");
732 EndExecuteInternal (ar);
733 SqlDataReader reader = null;
735 reader = new SqlDataReader (this);
737 catch (TdsTimeoutException e) {
738 // if behavior is closeconnection, even if it throws exception
739 // the connection has to be closed.
740 if ((behavior & CommandBehavior.CloseConnection) != 0)
742 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
743 } catch (SqlException) {
744 // if behavior is closeconnection, even if it throws exception
745 // the connection has to be closed.
746 if ((behavior & CommandBehavior.CloseConnection) != 0)
752 ( (SqlAsyncResult) ar).Ended = true;
756 public IAsyncResult BeginExecuteXmlReader (AsyncCallback callback, object state)
758 ValidateCommand ("BeginExecuteXmlReader");
759 SqlAsyncResult ar = new SqlAsyncResult (callback, state);
760 ar.EndMethod = "EndExecuteXmlReader";
761 ar.InternalResult = BeginExecuteInternal (behavior, true,
762 ar.BubbleCallback, state);
766 public XmlReader EndExecuteXmlReader (IAsyncResult ar)
768 ValidateAsyncResult (ar, "EndExecuteXmlReader");
769 EndExecuteInternal (ar);
770 SqlDataReader reader = new SqlDataReader (this);
771 SqlXmlTextReader textReader = new SqlXmlTextReader (reader);
772 XmlReader xmlReader = new XmlTextReader (textReader);
773 ( (SqlAsyncResult) ar).Ended = true;
778 internal void ValidateAsyncResult (IAsyncResult ar, string endMethod)
781 throw new ArgumentException ("result passed is null!");
782 if (! (ar is SqlAsyncResult))
783 throw new ArgumentException (String.Format ("cannot test validity of types {0}",
786 SqlAsyncResult result = (SqlAsyncResult) ar;
788 if (result.EndMethod != endMethod)
789 throw new InvalidOperationException (String.Format ("Mismatched {0} called for AsyncResult. " +
790 "Expected call to {1} but {0} is called instead.",
795 throw new InvalidOperationException (String.Format ("The method {0} cannot be called " +
796 "more than once for the same AsyncResult.",
801 #endregion // Asynchronous Methods
802 public event StatementCompletedEventHandler StatementCompleted;