New file
[mono.git] / mcs / class / System.Data / System.Data.SqlClient / SqlCommand.cs
1 //
2 // System.Data.SqlClient.SqlCommand.cs
3 //
4 // Author:
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)
9 //
10 // (C) Ximian, Inc 2002 http://www.ximian.com/
11 // (C) Daniel Morgan, 2002
12 // Copyright (C) Tim Coleman, 2002
13 //
14
15 //
16 // Copyright (C) 2004 Novell, Inc (http://www.novell.com)
17 //
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:
25 // 
26 // The above copyright notice and this permission notice shall be
27 // included in all copies or substantial portions of the Software.
28 // 
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.
36 //
37
38 using Mono.Data.Tds;
39 using Mono.Data.Tds.Protocol;
40 using System;
41 using System.IO;
42 using System.Collections;
43 using System.Collections.Specialized;
44 using System.ComponentModel;
45 using System.Data;
46 using System.Data.Common;
47 #if NET_2_0
48 using System.Data.Sql;
49 #endif
50 using System.Runtime.InteropServices;
51 using System.Text;
52 using System.Xml;
53
54 namespace System.Data.SqlClient {
55         [DesignerAttribute ("Microsoft.VSDesigner.Data.VS.SqlCommandDesigner, "+ Consts.AssemblyMicrosoft_VSDesigner, "System.ComponentModel.Design.IDesigner")]
56         [ToolboxItemAttribute ("System.Drawing.Design.ToolboxItem, "+ Consts.AssemblySystem_Drawing)]
57 #if NET_2_0
58         [DefaultEventAttribute ("RecordsAffected")]
59         public sealed class SqlCommand : DbCommand, IDbCommand, ICloneable
60 #else
61         public sealed class SqlCommand : Component, IDbCommand, ICloneable
62 #endif // NET_2_0
63         {
64                 #region Fields
65
66                 const int DEFAULT_COMMAND_TIMEOUT = 30;
67
68                 bool disposed;
69                 int commandTimeout;
70                 bool designTimeVisible;
71                 string commandText;
72                 CommandType commandType;
73                 SqlConnection connection;
74                 SqlTransaction transaction;
75                 UpdateRowSource updatedRowSource;
76                 CommandBehavior behavior = CommandBehavior.Default;
77                 SqlParameterCollection parameters;
78                 string preparedStatement;
79 #if NET_2_0
80                 SqlNotificationRequest notification;
81                 bool notificationAutoEnlist;
82 #endif
83
84                 #endregion // Fields
85
86                 #region Constructors
87
88                 public SqlCommand() 
89                         : this (String.Empty, null, null)
90                 {
91                 }
92
93                 public SqlCommand (string cmdText)
94                         : this (cmdText, null, null)
95                 {
96                 }
97
98                 public SqlCommand (string cmdText, SqlConnection connection)
99                         : this (cmdText, connection, null)
100                 {
101                 }
102
103                 public SqlCommand (string cmdText, SqlConnection connection, SqlTransaction transaction) 
104                 {
105                         this.commandText = cmdText;
106                         this.connection = connection;
107                         this.transaction = transaction;
108                         this.commandType = CommandType.Text;
109                         this.updatedRowSource = UpdateRowSource.Both;
110
111                         this.commandTimeout = DEFAULT_COMMAND_TIMEOUT;
112 #if NET_2_0
113                         notificationAutoEnlist = true;
114 #endif
115                         designTimeVisible = true;
116                         parameters = new SqlParameterCollection (this);
117                 }
118
119                 private SqlCommand(string commandText, SqlConnection connection, SqlTransaction transaction, CommandType commandType, UpdateRowSource updatedRowSource, bool designTimeVisible, int commandTimeout, SqlParameterCollection parameters)
120                 {
121                         this.commandText = commandText;
122                         this.connection = connection;
123                         this.transaction = transaction;
124                         this.commandType = commandType;
125                         this.updatedRowSource = updatedRowSource;
126                         this.designTimeVisible = designTimeVisible;
127                         this.commandTimeout = commandTimeout;
128                         this.parameters = new SqlParameterCollection(this);
129                         for (int i = 0;i < parameters.Count;i++)
130                                 this.parameters.Add(((ICloneable)parameters[i]).Clone());
131                 }
132
133                 #endregion // Constructors
134
135                 #region Properties
136
137                 internal CommandBehavior CommandBehavior {
138                         get { return behavior; }
139                 }
140
141 #if !NET_2_0
142                 [DataSysDescription ("Command text to execute.")]
143 #endif
144                 [DefaultValue ("")]
145                 [EditorAttribute ("Microsoft.VSDesigner.Data.SQL.Design.SqlCommandTextEditor, "+ Consts.AssemblyMicrosoft_VSDesigner, "System.Drawing.Design.UITypeEditor, "+ Consts.AssemblySystem_Drawing )]
146                 [RefreshProperties (RefreshProperties.All)]
147                 public
148 #if NET_2_0
149                 override
150 #endif //NET_2_0
151                 string CommandText {
152                         get {
153                                 if (commandText == null)
154                                         return string.Empty;
155                                 return commandText;
156                         }
157                         set {
158                                 if (value != commandText && preparedStatement != null)
159                                         Unprepare ();
160                                 commandText = value; 
161                         }
162                 }
163
164 #if !NET_2_0
165                 [DataSysDescription ("Time to wait for command to execute.")]
166                 [DefaultValue (DEFAULT_COMMAND_TIMEOUT)]
167 #endif
168                 public
169 #if NET_2_0
170                 override
171 #endif //NET_2_0
172                 int CommandTimeout {
173                         get { return commandTimeout; }
174                         set { 
175                                 if (value < 0)
176                                         throw new ArgumentException ("The property value assigned is less than 0.");
177                                 commandTimeout = value; 
178                         }
179                 }
180
181 #if !NET_2_0
182                 [DataSysDescription ("How to interpret the CommandText.")]
183 #endif
184                 [DefaultValue (CommandType.Text)]
185                 [RefreshProperties (RefreshProperties.All)]
186                 public
187 #if NET_2_0
188                 override
189 #endif //NET_2_0
190                 CommandType CommandType {
191                         get { return commandType; }
192                         set { 
193                                 if (value == CommandType.TableDirect)
194 #if NET_2_0
195                                         throw new ArgumentOutOfRangeException ("CommandType.TableDirect is not supported " +
196                                                 "by the Mono SqlClient Data Provider.");
197 #else
198                                         throw new ArgumentException ("CommandType.TableDirect is not supported by the Mono SqlClient Data Provider.");
199 #endif
200
201                                 ExceptionHelper.CheckEnumValue (typeof (CommandType), value);
202                                 commandType = value; 
203                         }
204                 }
205
206                 [DefaultValue (null)]
207 #if !NET_2_0
208                 [DataSysDescription ("Connection used by the command.")]
209 #endif
210                 [EditorAttribute ("Microsoft.VSDesigner.Data.Design.DbConnectionEditor, "+ Consts.AssemblyMicrosoft_VSDesigner, "System.Drawing.Design.UITypeEditor, "+ Consts.AssemblySystem_Drawing )]                
211                 public
212 #if NET_2_0
213                 new
214 #endif //NET_2_0
215                 SqlConnection Connection {
216                         get { return connection; }
217                         set {
218                                 if (transaction != null && connection.Transaction != null && connection.Transaction.IsOpen)
219                                         throw new InvalidOperationException ("The Connection property was changed while a transaction was in progress.");
220                                 transaction = null;
221                                 connection = value; 
222                         }
223                 }
224
225                 [Browsable (false)]
226                 [DefaultValue (true)]
227                 [DesignOnly (true)]
228 #if NET_2_0
229                 [EditorBrowsable (EditorBrowsableState.Never)]
230 #endif
231                 public
232 #if NET_2_0
233                 override
234 #endif //NET_2_0
235                 bool DesignTimeVisible {
236                         get { return designTimeVisible; } 
237                         set { designTimeVisible = value; }
238                 }
239
240 #if !NET_2_0
241                 [DataSysDescription ("The parameters collection.")]
242 #endif
243                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Content)]
244                 public
245 #if NET_2_0
246                 new
247 #endif //NET_2_0
248                 SqlParameterCollection Parameters {
249                         get { return parameters; }
250                 }
251
252                 internal Tds Tds {
253                         get { return Connection.Tds; }
254                 }
255
256 #if !NET_2_0
257                 IDbConnection IDbCommand.Connection {
258                         get { return Connection; }
259                         set { 
260                                 if (!(value == null || value is SqlConnection))
261                                         throw new InvalidCastException ("The value was not a valid SqlConnection.");
262                                 Connection = (SqlConnection) value;
263                         }
264                 }
265
266                 IDataParameterCollection IDbCommand.Parameters {
267                         get { return Parameters; }
268                 }
269
270                 IDbTransaction IDbCommand.Transaction {
271                         get { return Transaction; }
272                         set {
273                                 if (!(value == null || value is SqlTransaction))
274                                         throw new ArgumentException ();
275                                 Transaction = (SqlTransaction) value; 
276                         }
277                 }
278 #endif
279
280                 [Browsable (false)]
281 #if !NET_2_0
282                 [DataSysDescription ("The transaction used by the command.")]
283 #endif
284                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
285                 public new SqlTransaction Transaction {
286                         get { return transaction; }
287                         set { transaction = value; }
288                 }
289
290 #if !NET_2_0
291                 [DataSysDescription ("When used by a DataAdapter.Update, how command results are applied to the current DataRow.")]
292 #endif
293                 [DefaultValue (UpdateRowSource.Both)]
294                 public
295 #if NET_2_0
296                 override
297 #endif // NET_2_0
298                 UpdateRowSource UpdatedRowSource {
299                         get { return updatedRowSource; }
300                         set {
301                                 ExceptionHelper.CheckEnumValue (typeof (UpdateRowSource), value);
302                                 updatedRowSource = value;
303                         }
304                 }
305
306 #if NET_2_0
307                 [Browsable (false)]
308                 [DesignerSerializationVisibility (DesignerSerializationVisibility.Hidden)]
309                 public SqlNotificationRequest Notification {
310                         get { return notification; }
311                         set { notification = value; }
312                 }
313
314                 [DefaultValue (true)]
315                 public bool NotificationAutoEnlist {
316                         get { return notificationAutoEnlist; }
317                         set { notificationAutoEnlist = value; }
318                 }
319 #endif
320                 #endregion // Fields
321
322                 #region Methods
323
324                 public
325 #if NET_2_0
326                 override
327 #endif // NET_2_0
328                 void Cancel () 
329                 {
330                         if (Connection == null || Connection.Tds == null)
331                                 return;
332                         Connection.Tds.Cancel ();
333                 }
334
335 #if NET_2_0
336                 public SqlCommand Clone ()
337                 {
338                         return new SqlCommand (commandText, connection, transaction, commandType, updatedRowSource, designTimeVisible, commandTimeout, parameters);
339                 }
340 #endif // NET_2_0
341
342                 internal void CloseDataReader (bool moreResults)
343                 {
344                         Connection.DataReader = null;
345
346                         if ((behavior & CommandBehavior.CloseConnection) != 0)
347                                 Connection.Close ();
348
349                         // Reset the behavior
350                         behavior = CommandBehavior.Default;
351                         if (Tds != null)
352                                 Tds.SequentialAccess = false;
353                 }
354
355                 public new SqlParameter CreateParameter () 
356                 {
357                         return new SqlParameter ();
358                 }
359
360                 internal void DeriveParameters ()
361                 {
362                         if (commandType != CommandType.StoredProcedure)
363                                 throw new InvalidOperationException (String.Format ("SqlCommand DeriveParameters only supports CommandType.StoredProcedure, not CommandType.{0}", commandType));
364                         ValidateCommand ("DeriveParameters");
365
366                         string procName = CommandText;
367                         string schemaName = String.Empty;
368                         int dotPosition = procName.IndexOf ('.');
369                         if (dotPosition >= 0) {
370                                 schemaName = procName.Substring (0, dotPosition);
371                                 procName = procName.Substring (dotPosition + 1);
372                         }
373                         
374                         SqlParameterCollection localParameters = new SqlParameterCollection (this);
375                         localParameters.Add ("@procedure_name", SqlDbType.NVarChar, procName.Length).Value = procName;
376                         if (schemaName.Length > 0)
377                                 localParameters.Add ("@procedure_schema", SqlDbType.NVarChar, schemaName.Length).Value = schemaName;
378                         
379                         string sql = "sp_procedure_params_rowset";
380
381                         try {
382                                 Connection.Tds.ExecProc (sql, localParameters.MetaParameters, 0, true);
383                         } catch (TdsTimeoutException ex) {
384                                 throw SqlException.FromTdsInternalException ((TdsInternalException) ex);
385                         } catch (TdsInternalException ex) {
386                                 Connection.Close ();
387                                 throw SqlException.FromTdsInternalException ((TdsInternalException) ex);
388                         }
389
390                         SqlDataReader reader = new SqlDataReader (this);
391                         parameters.Clear ();
392                         object[] dbValues = new object[reader.FieldCount];
393
394                         while (reader.Read ()) {
395                                 reader.GetValues (dbValues);
396                                 parameters.Add (new SqlParameter (dbValues));
397                         }
398                         reader.Close ();
399
400                         if (parameters.Count == 0)
401                                 throw new InvalidOperationException ("Stored procedure '" + procName + "' does not exist.");
402                 }
403
404                 private void Execute (CommandBehavior behavior, bool wantResults)
405                 {
406                         int index = 0;
407                         Connection.Tds.RecordsAffected = -1;
408                         TdsMetaParameterCollection parms = Parameters.MetaParameters;
409                         foreach (TdsMetaParameter param in parms) {
410                                 param.Validate (index++);
411                         }
412
413                         if (preparedStatement == null) {
414                                 bool schemaOnly = ((behavior & CommandBehavior.SchemaOnly) > 0);
415                                 bool keyInfo = ((behavior & CommandBehavior.KeyInfo) > 0);
416
417                                 StringBuilder sql1 = new StringBuilder ();
418                                 StringBuilder sql2 = new StringBuilder ();
419
420                                 if (schemaOnly || keyInfo)
421                                         sql1.Append ("SET FMTONLY OFF;");
422                                 if (keyInfo) {
423                                         sql1.Append ("SET NO_BROWSETABLE ON;");
424                                         sql2.Append ("SET NO_BROWSETABLE OFF;");
425                                 }
426                                 if (schemaOnly) {
427                                         sql1.Append ("SET FMTONLY ON;");
428                                         sql2.Append ("SET FMTONLY OFF;");
429                                 }
430                                 
431                                 switch (CommandType) {
432                                 case CommandType.StoredProcedure:
433                                         try {
434                                                 if (keyInfo || schemaOnly)
435                                                         Connection.Tds.Execute (sql1.ToString ());
436                                                 Connection.Tds.ExecProc (CommandText, parms, CommandTimeout, wantResults);
437                                                 if (keyInfo || schemaOnly)
438                                                         Connection.Tds.Execute (sql2.ToString ());
439                                         } catch (TdsTimeoutException ex) {
440                                                 // If it is a timeout exception there can be many reasons:
441                                                 // 1) Network is down/server is down/not reachable
442                                                 // 2) Somebody has an exclusive lock on Table/DB
443                                                 // In any of these cases, don't close the connection. Let the user do it
444                                                 throw SqlException.FromTdsInternalException ((TdsInternalException) ex);
445                                         } catch (TdsInternalException ex) {
446                                                 Connection.Close ();
447                                                 throw SqlException.FromTdsInternalException ((TdsInternalException) ex);
448                                         }
449                                         break;
450                                 case CommandType.Text:
451                                         string sql;
452                                         if (sql2.Length > 0) {
453                                                 sql = String.Format ("{0}{1};{2}", sql1.ToString (), CommandText, sql2.ToString ());
454                                         } else {
455                                                 sql = String.Format ("{0}{1}", sql1.ToString (), CommandText);
456                                         }
457                                         try {
458                                                 Connection.Tds.Execute (sql, parms, CommandTimeout, wantResults);
459                                         } catch (TdsTimeoutException ex) {
460                                                 throw SqlException.FromTdsInternalException ((TdsInternalException) ex);
461                                         } catch (TdsInternalException ex) {
462                                                 Connection.Close ();
463                                                 throw SqlException.FromTdsInternalException ((TdsInternalException) ex);
464                                         }
465                                         break;
466                                 }
467                         }
468                         else {
469                                 try {
470                                         Connection.Tds.ExecPrepared (preparedStatement, parms, CommandTimeout, wantResults);
471                                 } catch (TdsTimeoutException ex) {
472                                                 throw SqlException.FromTdsInternalException ((TdsInternalException) ex);
473                                 } catch (TdsInternalException ex) {
474                                         Connection.Close ();
475                                         throw SqlException.FromTdsInternalException ((TdsInternalException) ex);
476                                 }
477                         }
478                 }
479
480                 public
481 #if NET_2_0
482                 override
483 #endif // NET_2_0
484                 int ExecuteNonQuery ()
485                 {
486                         ValidateCommand ("ExecuteNonQuery");
487                         int result = 0;
488                         behavior = CommandBehavior.Default;
489
490                         try {
491                                 Execute (CommandBehavior.Default, false);
492                                 result = Connection.Tds.RecordsAffected;
493                         }
494                         catch (TdsTimeoutException e) {
495                                 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
496                         }
497
498                         GetOutputParameters ();
499                         return result;
500                 }
501
502                 public new SqlDataReader ExecuteReader ()
503                 {
504                         return ExecuteReader (CommandBehavior.Default);
505                 }
506
507                 public new SqlDataReader ExecuteReader (CommandBehavior behavior)
508                 {
509                         ValidateCommand ("ExecuteReader");
510                         this.behavior = behavior;
511                         if ((behavior & CommandBehavior.SequentialAccess) != 0)
512                                 Tds.SequentialAccess = true;
513                         Execute (behavior, true);
514                         Connection.DataReader = new SqlDataReader (this);
515                         
516                         return Connection.DataReader;
517                 }
518
519                 public
520 #if NET_2_0
521                 override
522 #endif // NET_2_0
523                 object ExecuteScalar ()
524                 {
525                         try {
526                                 object result = null;
527                                 ValidateCommand ("ExecuteScalar");
528                                 behavior = CommandBehavior.Default;
529                                 Execute (CommandBehavior.Default, true);
530
531                                 try {
532                                         if (Connection.Tds.NextResult () && Connection.Tds.NextRow ())
533                                                 result = Connection.Tds.ColumnValues[0];
534
535                                         if (commandType == CommandType.StoredProcedure) {
536                                                 Connection.Tds.SkipToEnd ();
537                                                 GetOutputParameters ();
538                                         }
539                                 } catch (TdsTimeoutException ex) {
540                                         throw SqlException.FromTdsInternalException ((TdsInternalException) ex);
541                                 } catch (TdsInternalException ex) {
542                                         Connection.Close ();
543                                         throw SqlException.FromTdsInternalException ((TdsInternalException) ex);
544                                 }
545
546                                 return result;
547                         }
548                         finally {
549                                 CloseDataReader (true);
550                         }
551                 }
552
553                 public XmlReader ExecuteXmlReader ()
554                 {
555                         ValidateCommand ("ExecuteXmlReader");
556                         behavior = CommandBehavior.Default;
557                         try {
558                                 Execute (CommandBehavior.Default, true);
559                         } catch (TdsTimeoutException e) {
560                                 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
561                         }
562
563                         SqlDataReader dataReader = new SqlDataReader (this);
564                         SqlXmlTextReader textReader = new SqlXmlTextReader (dataReader);
565                         XmlReader xmlReader = new XmlTextReader (textReader);
566                         return xmlReader;
567                 }
568
569                 internal void GetOutputParameters ()
570                 {
571                         IList list = Connection.Tds.OutputParameters;
572
573                         if (list != null && list.Count > 0) {
574
575                                 int index = 0;
576                                 foreach (SqlParameter parameter in parameters) {
577                                         if (parameter.Direction != ParameterDirection.Input) {
578                                                 parameter.Value = list [index];
579                                                 index += 1;
580                                         }
581                                         if (index >= list.Count)
582                                                 break;
583                                 }
584                         }
585                 }
586
587                 object ICloneable.Clone ()
588                 {
589                         return new SqlCommand (commandText, connection, transaction, commandType, updatedRowSource, designTimeVisible, commandTimeout, parameters);
590
591                 }
592
593 #if !NET_2_0
594                 IDbDataParameter IDbCommand.CreateParameter ()
595                 {
596                         return CreateParameter ();
597                 }
598
599                 IDataReader IDbCommand.ExecuteReader ()
600                 {
601                         return ExecuteReader ();
602                 }
603
604                 IDataReader IDbCommand.ExecuteReader (CommandBehavior behavior)
605                 {
606                         return ExecuteReader (behavior);
607                 }
608 #endif
609
610 #if NET_2_0
611                 protected override void Dispose (bool disposing)
612                 {
613                         if (disposed) return;
614                         if (disposing) {
615                                 parameters.Clear();
616                         }
617                         base.Dispose (disposing);
618                         disposed = true;
619                 }
620 #endif
621
622                 public
623 #if NET_2_0
624                 override
625 #endif // NET_2_0
626                 void Prepare ()
627                 {
628                         ValidateCommand ("Prepare");
629
630                         if (CommandType == CommandType.StoredProcedure)
631                                 return;
632
633                         try {
634                                 foreach (SqlParameter param in Parameters)
635                                         param.CheckIfInitialized ();
636                         } catch (Exception e) {
637                                 throw new InvalidOperationException ("SqlCommand.Prepare requires " + e.Message);
638                         }
639
640                         preparedStatement = Connection.Tds.Prepare (CommandText, Parameters.MetaParameters);
641                 }
642
643                 public void ResetCommandTimeout ()
644                 {
645                         commandTimeout = DEFAULT_COMMAND_TIMEOUT;
646                 }
647
648                 private void Unprepare ()
649                 {
650                         Connection.Tds.Unprepare (preparedStatement);
651                         preparedStatement = null;
652                 }
653
654                 private void ValidateCommand (string method)
655                 {
656                         if (Connection == null)
657 #if NET_2_0
658                                 throw new NullReferenceException (String.Format ("{0} requires a Connection object to continue.", method));
659 #else
660                                 throw new InvalidOperationException (String.Format ("{0} requires a Connection object to continue.", method));
661 #endif
662                         if (Connection.Transaction != null && transaction != Connection.Transaction)
663                                 throw new InvalidOperationException ("The Connection object does not have the same transaction as the command object.");
664                         if (Connection.State != ConnectionState.Open)
665 #if NET_2_0
666                                 throw new NullReferenceException (String.Format ("ExecuteNonQuery requires an open Connection object to continue. This connection is closed.", method));
667 #else
668                                 throw new InvalidOperationException (String.Format ("ExecuteNonQuery requires an open Connection object to continue. This connection is closed.", method));
669 #endif
670                         if (CommandText.Length == 0)
671                                 throw new InvalidOperationException ("The command text for this Command has not been set.");
672                         if (Connection.DataReader != null)
673                                 throw new InvalidOperationException ("There is already an open DataReader associated with this Connection which must be closed first.");
674                         if (Connection.XmlReader != null)
675                                 throw new InvalidOperationException ("There is already an open XmlReader associated with this Connection which must be closed first.");
676 #if NET_2_0
677                         if (method.StartsWith ("Begin") && !Connection.AsyncProcessing)
678                                 throw new InvalidOperationException ("This Connection object is not " + 
679                                         "in Asynchronous mode. Use 'Asynchronous" +
680                                         " Processing = true' to set it.");
681 #endif // NET_2_0
682                 }
683
684 #if NET_2_0
685                 protected override DbParameter CreateDbParameter ()
686                 {
687                         return CreateParameter ();
688                 }
689
690                 protected override DbDataReader ExecuteDbDataReader (CommandBehavior behavior)
691                 {
692                         return ExecuteReader (behavior);
693                 }
694
695                 protected override DbConnection DbConnection {
696                         get { return Connection; }
697                         set { Connection = (SqlConnection) value; }
698                 }
699
700                 protected override DbParameterCollection DbParameterCollection {
701                         get { return Parameters; }
702                 }
703
704                 protected override DbTransaction DbTransaction {
705                         get { return Transaction; }
706                         set { Transaction = (SqlTransaction) value; }
707                 }
708 #endif // NET_2_0
709
710                 #endregion // Methods
711
712 #if NET_2_0
713                 #region Asynchronous Methods
714
715                 internal IAsyncResult BeginExecuteInternal (CommandBehavior behavior, 
716                                                                                                         bool wantResults,
717                                                                                                         AsyncCallback callback, 
718                                                                                                         object state)
719                 {
720                         IAsyncResult ar = null;
721                         Connection.Tds.RecordsAffected = -1;
722                         TdsMetaParameterCollection parms = Parameters.MetaParameters;
723                         if (preparedStatement == null) {
724                                 bool schemaOnly = ((behavior & CommandBehavior.SchemaOnly) > 0);
725                                 bool keyInfo = ((behavior & CommandBehavior.KeyInfo) > 0);
726
727                                 StringBuilder sql1 = new StringBuilder ();
728                                 StringBuilder sql2 = new StringBuilder ();
729
730                                 if (schemaOnly || keyInfo)
731                                         sql1.Append ("SET FMTONLY OFF;");
732                                 if (keyInfo) {
733                                         sql1.Append ("SET NO_BROWSETABLE ON;");
734                                         sql2.Append ("SET NO_BROWSETABLE OFF;");
735                                 }
736                                 if (schemaOnly) {
737                                         sql1.Append ("SET FMTONLY ON;");
738                                         sql2.Append ("SET FMTONLY OFF;");
739                                 }
740                                 switch (CommandType) {
741                                 case CommandType.StoredProcedure:
742                                         string prolog = "";
743                                         string epilog = "";
744                                         if (keyInfo || schemaOnly)
745                                                 prolog = sql1.ToString ();
746                                         if (keyInfo || schemaOnly)
747                                                 epilog = sql2.ToString ();
748                                         try {
749                                                 Connection.Tds.BeginExecuteProcedure (prolog,
750                                                                                       epilog,
751                                                                                       CommandText,
752                                                                                       !wantResults,
753                                                                                       parms,
754                                                                                       callback,
755                                                                                       state);
756                                         } catch (TdsTimeoutException ex) {
757                                                 throw SqlException.FromTdsInternalException ((TdsInternalException) ex);
758                                         } catch (TdsInternalException ex) {
759                                                 Connection.Close ();
760                                                 throw SqlException.FromTdsInternalException ((TdsInternalException) ex);
761                                         }
762                                         break;
763                                 case CommandType.Text:
764                                         string sql = String.Format ("{0}{1};{2}", sql1.ToString (), CommandText, sql2.ToString ());
765                                         try {
766                                                 if (wantResults)
767                                                         ar = Connection.Tds.BeginExecuteQuery (sql, parms, callback, state);
768                                                 else
769                                                         ar = Connection.Tds.BeginExecuteNonQuery (sql, parms, callback, state);
770                                         } catch (TdsTimeoutException ex) {
771                                                 throw SqlException.FromTdsInternalException ((TdsInternalException) ex);
772                                         } catch (TdsInternalException ex) {
773                                                 Connection.Close ();
774                                                 throw SqlException.FromTdsInternalException ((TdsInternalException) ex);
775                                         }
776                                         break;
777                                 }
778                         }
779                         else {
780                                 try {
781                                         Connection.Tds.ExecPrepared (preparedStatement, parms, CommandTimeout, wantResults);
782                                 } catch (TdsTimeoutException ex) {
783                                         throw SqlException.FromTdsInternalException ((TdsInternalException) ex);
784                                 } catch (TdsInternalException ex) {
785                                         Connection.Close ();
786                                         throw SqlException.FromTdsInternalException ((TdsInternalException) ex);
787                                 }
788                         }
789                         return ar;
790                 }
791
792                 internal void EndExecuteInternal (IAsyncResult ar)
793                 {
794                         SqlAsyncResult sqlResult = ( (SqlAsyncResult) ar);
795                         Connection.Tds.WaitFor (sqlResult.InternalResult);
796                         Connection.Tds.CheckAndThrowException (sqlResult.InternalResult);
797                 }
798
799                 public IAsyncResult BeginExecuteNonQuery ()
800                 {
801                         return BeginExecuteNonQuery (null, null);
802                 }
803
804                 public IAsyncResult BeginExecuteNonQuery (AsyncCallback callback, object stateObject)
805                 {
806                         ValidateCommand ("BeginExecuteNonQuery");
807                         SqlAsyncResult ar = new SqlAsyncResult (callback, stateObject);
808                         ar.EndMethod = "EndExecuteNonQuery";
809                         ar.InternalResult = BeginExecuteInternal (CommandBehavior.Default, false, ar.BubbleCallback, ar);
810                         return ar;
811                 }
812
813                 public int EndExecuteNonQuery (IAsyncResult asyncResult)
814                 {
815                         ValidateAsyncResult (asyncResult, "EndExecuteNonQuery");
816                         EndExecuteInternal (asyncResult);
817
818                         int ret = Connection.Tds.RecordsAffected;
819
820                         GetOutputParameters ();
821                         ((SqlAsyncResult) asyncResult).Ended = true;
822                         return ret;
823                 }
824
825                 public IAsyncResult BeginExecuteReader ()
826                 {
827                         return BeginExecuteReader (null, null, CommandBehavior.Default);
828                 }
829
830                 public IAsyncResult BeginExecuteReader (CommandBehavior behavior)
831                 {
832                         return BeginExecuteReader (null, null, behavior);
833                 }
834
835                 public IAsyncResult BeginExecuteReader (AsyncCallback callback, object stateObject)
836                 {
837                         return BeginExecuteReader (callback, stateObject, CommandBehavior.Default);
838                 }
839
840                 public IAsyncResult BeginExecuteReader (AsyncCallback callback, object stateObject, CommandBehavior behavior)
841                 {
842                         ValidateCommand ("BeginExecuteReader");
843                         this.behavior = behavior;
844                         SqlAsyncResult ar = new SqlAsyncResult (callback, stateObject);
845                         ar.EndMethod = "EndExecuteReader";
846                         IAsyncResult tdsResult = BeginExecuteInternal (behavior, true, 
847                                 ar.BubbleCallback, stateObject);
848                         ar.InternalResult = tdsResult;
849                         return ar;
850                 }
851
852                 public SqlDataReader EndExecuteReader (IAsyncResult asyncResult)
853                 {
854                         ValidateAsyncResult (asyncResult, "EndExecuteReader");
855                         EndExecuteInternal (asyncResult);
856                         SqlDataReader reader = null;
857                         try {
858                                 reader = new SqlDataReader (this);
859                         } catch (TdsTimeoutException e) {
860                                 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
861                         } catch (TdsInternalException e) {
862                                 // if behavior is closeconnection, even if it throws exception
863                                 // the connection has to be closed.
864                                 if ((behavior & CommandBehavior.CloseConnection) != 0)
865                                         Connection.Close ();
866                                 throw SqlException.FromTdsInternalException ((TdsInternalException) e);
867                         }
868
869                         ((SqlAsyncResult) asyncResult).Ended = true;
870                         return reader;
871                 }
872
873                 public IAsyncResult BeginExecuteXmlReader (AsyncCallback callback, object stateObject)
874                 {
875                         ValidateCommand ("BeginExecuteXmlReader");
876                         SqlAsyncResult ar = new SqlAsyncResult (callback, stateObject);
877                         ar.EndMethod = "EndExecuteXmlReader";
878                         ar.InternalResult = BeginExecuteInternal (behavior, true, 
879                                 ar.BubbleCallback, stateObject);
880                         return ar;
881                 }
882
883                 public IAsyncResult BeginExecuteXmlReader ()
884                 {
885                         return BeginExecuteXmlReader (null, null);
886                 }
887                 
888
889                 public XmlReader EndExecuteXmlReader (IAsyncResult asyncResult)
890                 {
891                         ValidateAsyncResult (asyncResult, "EndExecuteXmlReader");
892                         EndExecuteInternal (asyncResult);
893                         SqlDataReader reader = new SqlDataReader (this);
894                         SqlXmlTextReader textReader = new SqlXmlTextReader (reader);
895                         XmlReader xmlReader = new XmlTextReader (textReader);
896                         ((SqlAsyncResult) asyncResult).Ended = true;
897                         return xmlReader;
898                 }
899
900                 internal void ValidateAsyncResult (IAsyncResult ar, string endMethod)
901                 {
902                         if (ar == null)
903                                 throw new ArgumentException ("result passed is null!");
904                         if (! (ar is SqlAsyncResult))
905                                 throw new ArgumentException (String.Format ("cannot test validity of types {0}",
906                                         ar.GetType ()));
907                         SqlAsyncResult result = (SqlAsyncResult) ar;
908                         if (result.EndMethod != endMethod)
909                                 throw new InvalidOperationException (String.Format ("Mismatched {0} called for AsyncResult. " + 
910                                         "Expected call to {1} but {0} is called instead.",
911                                         endMethod, result.EndMethod));
912                         if (result.Ended)
913                                 throw new InvalidOperationException (String.Format ("The method {0} cannot be called " + 
914                                         "more than once for the same AsyncResult.", endMethod));
915                 }
916
917                 #endregion // Asynchronous Methods
918
919                 public event StatementCompletedEventHandler StatementCompleted;
920 #endif // NET_2_0
921         }
922 }