2 // Mono.Data.TdsClient.TdsConnection.cs
5 // Tim Coleman (tim@timcoleman.com)
6 // Daniel Morgan (danmorg@sc.rr.com)
8 // Copyright (C) Tim Coleman, 2002, 2003
9 // Copyright (C) Daniel Morgan, 2003
13 // Permission is hereby granted, free of charge, to any person obtaining
14 // a copy of this software and associated documentation files (the
15 // "Software"), to deal in the Software without restriction, including
16 // without limitation the rights to use, copy, modify, merge, publish,
17 // distribute, sublicense, and/or sell copies of the Software, and to
18 // permit persons to whom the Software is furnished to do so, subject to
19 // the following conditions:
21 // The above copyright notice and this permission notice shall be
22 // included in all copies or substantial portions of the Software.
24 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
25 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
26 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
27 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
28 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
29 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
30 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 using Mono.Data.Tds.Protocol;
34 using MDTP = Mono.Data.Tds.Protocol;
36 using System.Collections;
37 using System.Collections.Specialized;
38 using System.ComponentModel;
40 using System.Data.Common;
41 using System.EnterpriseServices;
43 using System.Net.Sockets;
46 namespace Mono.Data.TdsClient {
47 public sealed class TdsConnection : Component, IDbConnection, ICloneable
50 bool disposed = false;
52 // The set of SQL connection pools
53 static TdsConnectionPoolManager tdsConnectionPools = new TdsConnectionPoolManager (TdsVersion.tds42);
55 // The current connection pool
56 TdsConnectionPool pool;
58 // The connection string that identifies this connection
59 string connectionString = null;
61 // The transaction object for the current transaction
62 TdsTransaction transaction = null;
64 // Connection parameters
65 TdsConnectionParameters parms = new TdsConnectionParameters ();
69 int connectionTimeout;
76 ConnectionState state = ConnectionState.Closed;
78 TdsDataReader dataReader = null;
87 public TdsConnection ()
92 public TdsConnection (string connectionString)
94 ConnectionString = connectionString;
97 #endregion // Constructors
101 public string ConnectionString {
102 get { return connectionString; }
103 set { SetConnectionString (value); }
106 public int ConnectionTimeout {
107 get { return connectionTimeout; }
110 public string Database {
111 get { return tds.Database; }
114 internal TdsDataReader DataReader {
115 get { return dataReader; }
116 set { dataReader = value; }
119 public string DataSource {
120 get { return dataSource; }
123 public int PacketSize {
124 get { return packetSize; }
127 public string ServerVersion {
128 get { return tds.ServerVersion; }
131 public ConnectionState State {
132 get { return state; }
135 internal MDTP.Tds Tds {
139 internal TdsTransaction Transaction {
140 get { return transaction; }
141 set { transaction = value; }
144 public string WorkstationId {
145 get { return parms.Hostname; }
148 #endregion // Properties
150 #region Events and Delegates
152 public event TdsInfoMessageEventHandler InfoMessage;
153 public event StateChangeEventHandler StateChange;
155 private void ErrorHandler (object sender, TdsInternalErrorMessageEventArgs e)
157 throw new TdsException (e.Class, e.LineNumber, e.Message, e.Number, e.Procedure, e.Server, "Mono TdsClient Data Provider", e.State);
160 private void MessageHandler (object sender, TdsInternalInfoMessageEventArgs e)
162 OnTdsInfoMessage (CreateTdsInfoMessageEvent (e.Errors));
165 #endregion // Events and Delegates
169 public TdsTransaction BeginTransaction ()
171 return BeginTransaction (IsolationLevel.ReadCommitted, String.Empty);
174 public TdsTransaction BeginTransaction (IsolationLevel iso)
176 return BeginTransaction (iso, String.Empty);
179 public TdsTransaction BeginTransaction (string transactionName)
181 return BeginTransaction (IsolationLevel.ReadCommitted, transactionName);
184 public TdsTransaction BeginTransaction (IsolationLevel iso, string transactionName)
186 if (State == ConnectionState.Closed)
187 throw new InvalidOperationException ("The connection is not open.");
188 if (Transaction != null)
189 throw new InvalidOperationException ("TdsConnection does not support parallel transactions.");
191 string isolevel = String.Empty;
193 case IsolationLevel.Chaos:
196 case IsolationLevel.ReadCommitted:
197 isolevel = "READ COMMITTED";
199 case IsolationLevel.ReadUncommitted:
200 isolevel = "READ UNCOMMITTED";
202 case IsolationLevel.RepeatableRead:
203 isolevel = "REPEATABLE READ";
205 case IsolationLevel.Serializable:
206 isolevel = "SERIALIZABLE";
210 tds.Execute (String.Format ("SET TRANSACTION ISOLATION LEVEL {0}\nBEGIN TRANSACTION {1}", isolevel, transactionName));
211 transaction = new TdsTransaction (this, iso);
215 public void ChangeDatabase (string database)
217 if (!IsValidDatabaseName (database))
218 throw new ArgumentException (String.Format ("The database name {0} is not valid."));
219 if (State != ConnectionState.Open)
220 throw new InvalidOperationException ("The connection is not open");
221 tds.Execute (String.Format ("use {0}", database));
224 private void ChangeState (ConnectionState currentState)
226 ConnectionState originalState = state;
227 state = currentState;
228 OnStateChange (CreateStateChangeEvent (originalState, currentState));
233 if (Transaction != null && Transaction.IsOpen)
234 Transaction.Rollback ();
236 pool.ReleaseConnection (tds);
239 tds.TdsErrorMessage -= new TdsInternalErrorMessageEventHandler (ErrorHandler);
240 tds.TdsInfoMessage -= new TdsInternalInfoMessageEventHandler (MessageHandler);
241 ChangeState (ConnectionState.Closed);
244 public TdsCommand CreateCommand ()
246 TdsCommand command = new TdsCommand ();
247 command.Connection = this;
251 private StateChangeEventArgs CreateStateChangeEvent (ConnectionState originalState, ConnectionState currentState)
253 return new StateChangeEventArgs (originalState, currentState);
256 private TdsInfoMessageEventArgs CreateTdsInfoMessageEvent (TdsInternalErrorCollection errors)
258 return new TdsInfoMessageEventArgs (errors);
261 protected override void Dispose (bool disposing)
265 if (State == ConnectionState.Open)
270 base.Dispose (disposing);
276 public void EnlistDistributedTransaction (ITransaction transaction)
278 throw new NotImplementedException ();
281 object ICloneable.Clone ()
283 return new TdsConnection (ConnectionString);
286 IDbTransaction IDbConnection.BeginTransaction ()
288 return BeginTransaction ();
291 IDbTransaction IDbConnection.BeginTransaction (IsolationLevel iso)
293 return BeginTransaction (iso);
296 IDbCommand IDbConnection.CreateCommand ()
298 return CreateCommand ();
301 void IDisposable.Dispose ()
304 GC.SuppressFinalize (this);
307 [MonoTODO ("Figure out the Tds way to reset the connection.")]
310 string serverName = "";
311 if (connectionString == null)
312 throw new InvalidOperationException ("Connection string has not been initialized.");
316 ParseDataSource (dataSource, out port, out serverName);
317 tds = new Tds42 (serverName, port, PacketSize, ConnectionTimeout);
320 ParseDataSource (dataSource, out port, out serverName);
321 TdsConnectionInfo info = new TdsConnectionInfo (serverName, port, packetSize, ConnectionTimeout, minPoolSize, maxPoolSize);
322 pool = tdsConnectionPools.GetConnectionPool (connectionString, info);
323 tds = pool.GetConnection ();
326 catch (TdsTimeoutException e) {
327 throw TdsException.FromTdsInternalException ((TdsInternalException) e);
330 tds.TdsErrorMessage += new TdsInternalErrorMessageEventHandler (ErrorHandler);
331 tds.TdsInfoMessage += new TdsInternalInfoMessageEventHandler (MessageHandler);
333 if (!tds.IsConnected) {
336 ChangeState (ConnectionState.Open);
337 ChangeDatabase (parms.Database);
341 pool.ReleaseConnection (tds);
345 else if (connectionReset) {
346 // tds.ExecuteNonQuery ("EXEC sp_reset_connection"); FIXME
347 ChangeState (ConnectionState.Open);
351 private void ParseDataSource (string theDataSource, out int thePort, out string theServerName)
354 thePort = 1433; // default TCP port for SQL Server
357 if ((idx = theDataSource.IndexOf (",")) > -1) {
358 theServerName = theDataSource.Substring (0, idx);
359 string p = theDataSource.Substring (idx + 1);
360 thePort = Int32.Parse (p);
363 theServerName = theDataSource;
367 void SetConnectionString (string connectionString)
369 connectionString += ";";
370 NameValueCollection parameters = new NameValueCollection ();
372 if (connectionString == String.Empty)
375 bool inQuote = false;
376 bool inDQuote = false;
378 string name = String.Empty;
379 string value = String.Empty;
380 StringBuilder sb = new StringBuilder ();
382 foreach (char c in connectionString)
389 inDQuote = !inDQuote;
392 if (!inDQuote && !inQuote) {
393 if (name != String.Empty && name != null) {
394 value = sb.ToString ();
395 parameters [name.ToUpper ().Trim ()] = value.Trim ();
398 value = String.Empty;
399 sb = new StringBuilder ();
405 if (!inDQuote && !inQuote) {
406 name = sb.ToString ();
407 sb = new StringBuilder ();
418 if (this.ConnectionString == null)
420 SetDefaultConnectionParameters (parameters);
423 SetProperties (parameters);
425 this.connectionString = connectionString;
428 void SetDefaultConnectionParameters (NameValueCollection parameters)
430 if (null == parameters.Get ("APPLICATION NAME"))
431 parameters["APPLICATION NAME"] = ".Net TdsClient Data Provider";
432 if (null == parameters.Get ("CONNECT TIMEOUT") && null == parameters.Get ("CONNECTION TIMEOUT"))
433 parameters["CONNECT TIMEOUT"] = "15";
434 if (null == parameters.Get ("CONNECTION LIFETIME"))
435 parameters["CONNECTION LIFETIME"] = "0";
436 if (null == parameters.Get ("CONNECTION RESET"))
437 parameters["CONNECTION RESET"] = "true";
438 if (null == parameters.Get ("ENLIST"))
439 parameters["ENLIST"] = "true";
440 if (null == parameters.Get ("INTEGRATED SECURITY") && null == parameters.Get ("TRUSTED_CONNECTION"))
441 parameters["INTEGRATED SECURITY"] = "false";
442 if (null == parameters.Get ("MAX POOL SIZE"))
443 parameters["MAX POOL SIZE"] = "100";
444 if (null == parameters.Get ("MIN POOL SIZE"))
445 parameters["MIN POOL SIZE"] = "0";
446 if (null == parameters.Get ("NETWORK LIBRARY") && null == parameters.Get ("NET"))
447 parameters["NETWORK LIBRARY"] = "dbmssocn";
448 if (null == parameters.Get ("PACKET SIZE"))
449 parameters["PACKET SIZE"] = "512";
450 if (null == parameters.Get ("PERSIST SECURITY INFO"))
451 parameters["PERSIST SECURITY INFO"] = "false";
452 if (null == parameters.Get ("POOLING"))
453 parameters["POOLING"] = "true";
454 if (null == parameters.Get ("WORKSTATION ID"))
455 parameters["WORKSTATION ID"] = Dns.GetHostByName ("localhost").HostName;
458 private void SetProperties (NameValueCollection parameters)
461 foreach (string name in parameters) {
462 value = parameters[name];
465 case "APPLICATION NAME" :
466 parms.ApplicationName = value;
468 case "ATTACHDBFILENAME" :
469 case "EXTENDED PROPERTIES" :
470 case "INITIAL FILE NAME" :
472 case "CONNECT TIMEOUT" :
473 case "CONNECTION TIMEOUT" :
474 connectionTimeout = Int32.Parse (value);
476 case "CONNECTION LIFETIME" :
478 case "CONNECTION RESET" :
479 connectionReset = !(value.ToUpper ().Equals ("FALSE") || value.ToUpper ().Equals ("NO"));
481 case "CURRENT LANGUAGE" :
482 parms.Language = value;
488 case "NETWORK ADDRESS" :
493 case "INITIAL CATALOG" :
495 parms.Database = value;
497 case "INTEGRATED SECURITY" :
498 case "TRUSTED_CONNECTION" :
500 case "MAX POOL SIZE" :
501 maxPoolSize = Int32.Parse (value);
503 case "MIN POOL SIZE" :
504 minPoolSize = Int32.Parse (value);
507 case "NETWORK LIBRARY" :
508 if (!value.ToUpper ().Equals ("DBMSSOCN"))
509 throw new ArgumentException ("Unsupported network library.");
512 packetSize = Int32.Parse (value);
516 parms.Password = value;
518 case "PERSIST SECURITY INFO" :
521 pooling = !(value.ToUpper ().Equals ("FALSE") || value.ToUpper ().Equals ("NO"));
526 case "WORKSTATION ID" :
527 parms.Hostname = value;
534 static bool IsValidDatabaseName (string database)
536 if (database.Length > 32 || database.Length < 1)
539 if (database[0] == '"' && database[database.Length] == '"')
540 database = database.Substring (1, database.Length - 2);
541 else if (Char.IsDigit (database[0]))
544 if (database[0] == '_')
547 foreach (char c in database.Substring (1, database.Length - 1))
548 if (!Char.IsLetterOrDigit (c) && c != '_')
553 private void OnTdsInfoMessage (TdsInfoMessageEventArgs value)
555 if (InfoMessage != null)
556 InfoMessage (this, value);
559 private void OnStateChange (StateChangeEventArgs value)
561 if (StateChange != null)
562 StateChange (this, value);
565 #endregion // Methods