|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Announcements
Want a new Job?
Chapters
Services
Feature Zones
|
Note: This is an unedited contribution. If this article is inappropriate,
needs attention or copies someone else's work without reference then please
Report This Article
IntroductionSince even before .Net, when ADO was still an ActiveX API, connection strings have always been a point of confusion for a lot of developers. This mostly stems from the fact that a connection string is structured information with a specific format that developers have always been expected to be able to produce from memory or have to look up in documentation. To me it only made sense to add the structure they've always needed. At the same time, for most purposes I've found the ADO.Net API to be a little more granular than necessary. Which prompted me to look at creating a structure that would simplify most uses of the API. I've seen many people use the API rather recklessly, not taking care to properly dispose of connections and or readers. Or fail to properly encapsulate data related functionality in a central location, passing readers from data access classes to business classes and the like. I found that simplifying the use of ADO.Net with a simpler structure helps promote better encapsulation and more elegant design in data use. My answer to these issues is a data access structure that uses connection manager classes and connection string structs. This API only makes use of the classes provided by the .Net framework, using OleDb for access to non-Microsoft databases such as MySQL and Oracle. This decision was made to avoid the need for finding and supporting different versions of third party providers that users may or may not have so that it is usable by any developer that has the Framework installed. Through thorough use of this API, I've come to believe that this is the final data abstraction that Microsoft, for reasons unknown, decided not to provide. IConnectionManager
AbstractConnectionManager : IConnectionManagerThe Internals
ResetCommand()When reusing a manager instance, it's necessary to use the ParametersThe manager classes provide a couple of simple methods for attaching parameters to you queries, for stored procedures as well as parameterized queries. The TransactionsBy default, the connection managers don't use transactions. But when needed, all you have to do is call the BeginTransaction()This method tells the manager to create a transaction from its internal connection. Commit() & Rollback()As expected, these methods finalize your transaction. Abstracts
GetParameter(name, value)This abstract method is also protected and internal, as it's used internally by the GetAdapter(sql, IDbConneciton | CommandType)This method has two protected overloads, both of which take a SQL string. One also requires an FillSchema(table)This method is used to retrieve schema information in the form of a Clone()This method basically creates a new instance of the current implementation with a copy of it's internal connection string. Other MethodsThe remaining methods of this abstract class, generically perform various functions through the use of the abstract methods described above. ExecuteNonQuery(sql, [type])
ExecuteReader(sql, [type])
Fill(sql, [type])Like the Close() & Dispose()The ImplementationsThis API provides implementations for SQL Server, Microsoft Access, OleDB, ODBC and MySQL. The SQL Server, OleDB and ODBC implementations make use of their corresponding Framework providers. The Access and MySQL implementations extend the OleDB implementation. Direct Descendants of AbstractConnectionManager
InitializationAll implementations have an overloaded constructor that accepts an AccessConnectionManager : OleDbConnectionManagerThe Access implementation provides Access specific initialization and overrides MySqlConnectionManager : OleDbConnectionManagerThe MySQL implementation provides MySQL specific initialization, and overrides Connection StringsThis API provides several different implementations of the ConnectionStringUtilityThe
AccessDbStringThe Provider=Microsoft.Jet.OLEDB.4.0;Data Source=\somepath\mydb.mdb;User Id=admin;Password=; MsSqlDbStringThe Microsoft SQL Server connection string has six properties: Host, Database, UserId, Password, Trusted and Timeout. Trusted is used when a password is not supplied. Timeout is a connection timeout in seconds. It will output a connection string similar to the following: Server=Aron1;Database=pubs;User ID=sa;Password=asdasd;Trusted_Connection=False; MySqlDbStringThe Provider=MySQLProv;Data Source=mydb;User Id=UserName;Password=asdasd; OdbcDbStringThe Driver={SQL Native Client};Server=Aron1;Database=pubs;UID=sa;PWD=asdasd;
OleDbStringThe Provider=sqloledb;Data Source=Aron1;Initial Catalog=pubs;User Id=sa;Password=asdasd; OraDbStringThe Oracle connection string has three properties: Host, UserId and Password. It will output a connection string similar to the following: Provider=OraOLEDB.Oracle;Data Source=MyOracleDB;User Id=Username;Password=asdasd; Config XML SerializationThis API has evolved to include a config file structure that allows multiple connections to be defined, and easily exchanged for different environments. All classes related to configuration are in the First let's look at the class that user code will use to access config information... SqlConnectionListThe The config classes are modeled after the XML structure shown in the following section on serialization. This class model was generated using Skeleton Crew 2.0, a code generation tool partly designed to replace XSD.exe for generating XML serializable code from XML structure. DbConnectionList & DbConnectionListSerializer
The following example XML config defines two named connections called "ErrorLogging" which references the "ErrorLog" connection definition, and "StatsLogging" which references the connection information named "StatsLog". You could have two or three definitions for logging stats to different databases for development, staging and production, then just change the DbConnection's "ConnectionName" attribute to the correct ConnectionInfo name to change environments for what you want when asking for "StatsLogging". The Connections node is just a storage area for connection information. <DbConnectionList>
<!--
Code will ask for connections by "Name" from this list,
each entries "ConnectionName" determines which definition
to use from the "Connections" list below.
-->
<NamedConnections>
<DbConnection Name="ErrorLogging" ConnectionName="ErrorLog" />
<DbConnection Name="StatsLogging" ConnectionName="StatsLog" />
</NamedConnections>
<!--
You can specify as many sets of connection information as you want here.
The only ones that will be loaded are those named above.
-->
<Connections>
<ConnectionInfo Type="SQL" Name="StatsLog">
<Host>StatsSQL</Host>
<Database>Logs</Database>
<UserId Trusted="false">uid</UserId>
<Password>pwd</Password>
</ConnectionInfo>
<ConnectionInfo Type="SQL" Name="ErrorLog">
<Host>ErrorSQL</Host>
<Database>Logs</Database>
<UserId Trusted="true">uid</UserId>
</ConnectionInfo>
</Connections>
</DbConnectionList>
Using the config example above, to get the connection information for logging errors, you would do something similar to the following example: using System;
using System.Data;
using BoneSoft.Data;
using BoneSoft.Data.Config;
namespace Example {
class ConfigExample {
public void Test() {
string configPath = System.IO.Path.GetDirectoryName(
System.Reflection.Assembly.GetExecutingAssembly()
.CodeBase.Replace("file:///", "")) + @"\data.config";
// initialize a SqlConnectionList with a config path..
SqlConnectionList conns = new SqlConnectionList(configPath);
// use the SqlConnectionList to get the connection information
// with the name "ErrorLogging"
using (IConnectionManager icm = new SqlConnectionManager(
(MsSqlDbString)conns.GetConnectionString("ErrorLogging"))) {
icm.ResetCommand();
IDataReader idr = icm.ExecuteReader("select * from Errors");
while (idr.Read()) {
// process record...
}
idr.Close();
}
}
}
}
With this API, it's a simple matter to get connection information from config and query your database in just a few lines of code. Sample Application: Config EditorThe sample project for this article, is a small application for creating and editing data config files. Since NamedConnections reference Connections, you must create a NamedConnection first. Just right click in either ListView to access it's context menu. The File menu allows you to open or save config files.
ExampleOk, just one more quick example of how to use the API. The following example uses a stored procedure to produce an using System;
using System.Data;
using BoneSoft.Data;
namespace Example {
class DataExample {
public int ExecuteProc(string val1, int val2) {
int answer = 0;
// Setup connection string
MsSqlDbString dbs = new MsSqlDbString("host", "database", "user", "password");
//Console.WriteLine(dbs.ToString());
// Setup a connection manager taking advantage of the fact that it is IDisposable
using (IConnectionManager icm = new SqlConnectionManager(dbs)) {
// If it's being reused, it's important to reset the command
icm.ResetCommand();
// Add parameters for our stored procedure
icm.AddParameter("@param1", val1);
icm.AddParameter("@param2", val2);
// With a scalar stored proc, we could do this...
/*
icm.AddOutputParameter("@outparam", answer);
icm.ExecuteNonQuery("StoredProc", CommandType.StoredProcedure);
*/
// or we could use ExecuteReader...
IDataReader idr = icm.ExecuteReader("StoredProc", CommandType.StoredProcedure);
if (idr.Read()) {
answer = Convert.ToInt32(idr[0]);
}
// Always close your readers explicitly
idr.Close();
}
return answer;
}
}
}
ConclusionThis all seems like a lot of information, but it's really a pretty simple and easy to use API. Like I stated in the introduction, I believe that this API simplifies basic data access greatly, and also promotes better coding practices and aids in the separation of data access logic. I hope you find it useful, or at least find some good ideas in it for creating your own data access components.
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||