I was trying to explain to someone why database connections implement IDisposable, when I realized I don't really know what "opening a connection" actually mean.
So my question is - What does c# practically do when it opens a connection?
Thank you.
There are actually two classes involved in implementing a connection (actually more, but I'm simplifying).
One of these is the
IDbConnectionimplementation (SQLConnection,NpgsqlConnection,OracleConnection, etc.) that you use in your code. The other is a "real" connection object that is internal to the assembly, and not visible to your code. We'll call this "RealConnection" for now, though its actual name differs with different implementations (e.g. in Npgsql, which is the case where I'm most familiar with the implementation, the class is calledNpgsqlConnector).When you create your
IDbConnection, it does not have aRealConnection. Any attempt to do something with the database will fail. When youOpen()it then the following happens:RealConnectionin the pool, deque it and make it theRealConnectionfor theIDbConnection.RealConnectionobjects in existence is larger than the maximum size, throw an exception.RealConnection. Initialise it, which will involve opening some sort of network connection (e.g. TCP/IP) or file handle (for something like Access), go through the database's protocol for hand-shaking (varies with database type) and authorise the connection. This then becomes theRealConnectionfor theIDbConnection.Operations carried out on the
IDbConnectionare turned into operations theRealConnectiondoes on its network connection (or whatever). The results are turned into objects implementingIDataReaderand so on so as to give a consistent interface for your programming.If a
IDataReaderwas created withCommandBehavior.CloseConnection, then that datareader obtains "ownership" of theRealConnection.When you call
Close()then one of the following happens:RealConnectionwill carry out any protocol-defined procedures for ending the connection (signalling to the database that the connection is going to shut down) and closes the network connection etc. The object can then fall out of scope and become available for garbage collection.The exception would be if the
CommandBehavior.CloseConnectioncase happened, in which case it'sClose()orDispose()being called on theIDataReaderthat triggers this.If you call
Dispose()then the same thing happens as perClose(). The difference is thatDispose()is considered as "clean-up" and can work withusing, whileClose()might be used in the middle of lifetime, and followed by a laterOpen().Because of the use of the
RealConnectionobject and the fact that they are pooled, opening and closing connections changes from being something relatively heavy to relatively light. Hence rather than it being important to keep connections open for a long time to avoid the overhead of opening them, it becomes important to keep them open for as short a time as possible, since theRealConnectiondeals with the overhead for you, and the more rapidly you use them, the more efficiently the pooled connections get shared between uses.Note also, that it's okay to
Dispose()anIDbConnectionthat you have already calledClose()on (it's a rule that it should always be safe to callDispose(), whatever the state, indeed even if it was already called). Hence if you were manually callingClose()it would still be good to have the connection in ausingblock, to catch cases where exceptions happen before the call toClose(). The only exception is where you actually want the connection to stay open; say you were returning anIDataReadercreated withCommandBehavior.CloseConnection, in which case you don't dispose theIDbConnection, but do dispose the reader.Should you fail to dispose the connection, then the
RealConnectionwill not be returned to the pool for reuse, or go through its shut-down procedure. Either the pool will reach its limit, or the number of underlying connections will increase to the point of damaging performance and blocking more from being created. Eventually the finaliser onRealConnectionmay be called and lead to this being fixed, but finalisation only reduces the damage and can't be depended upon. (TheIDbConnectiondoesn't need a finaliser, as it's theRealConnectionthat holds the unmanaged resource and/or needs to do the shut-down).It's also reasonable to assume that there is some other requirement for disposal unique to the implementation of the
IDbConnectionbeyond this, and it should still be disposed of even if analysing the above leads you to believe its not necessary (the exception is whenCommandBehavior.CloseConnectionpasses all disposal burden to theIDataReader, but then it is just as important to dispose that reader).