在 .NET 的学习过程中用过三种数据库:Sql Server、Access、SQLite。Sql Server 用得相对多一点点,但是,麻烦,每次用它都需要开服务,而且还费资源,更麻烦的是拷贝到一台没有装SqlServer的电脑上还不能用,所以一般练习中只要不涉及到什么存储过程这些也基本不用它。Access简单的文件型数据库,office的套件,基本的SQL语句也支持,在一般的小型应用系统中用起来还行,而且在Windows 7开始操作系统中就已经内置了Access的服务,随便拷贝。SQLite用过一次,需要安装服务,和Access一样,拷贝一下就可以用,就是管理工具的界面比较shit。
用过三种数据库后,发现其实在微软的ADO.NET中操作数据库的方式是一样的,链接对象都是继承自实现了IDbConnection接口的抽象类DbConnection,以及实现了IDbCommand的接口的抽象类DbCommand,其它的都一样。古就可以统一用接口或者抽象来来进行操作数据库了,这样就能够实现基本的跨数据库访问的功能了。唯一不同的就是实例化连接字符串的时候要用具体的子类来进行实例化,这时就可以用静态工厂来实现,从而做到不修改程序就实现夸数据库的功能,当然应用程序中的SQL语句是通用的才行,如果是某些数据库特有的,那就不行了。
静态工厂
也成为简单工厂,主要是用来因对对象的变化,根据不同的要求创建不同的实例,就可以进行一个不同的操作。在黑马的基础加强里面,有一个比较经典的例子能够说明他的作用:简单计算器。主程序中一直不变,变的是运算规则,然后把运算规则给抽出来,写到一个工厂中,然后根据不同的运算符来创建不同的计算实例,然后用一个同一的接口返回这个实例对象,就能够完成计算了。
/// <summary>
/// 根据传递过来的运算符,来创造不同的子类对象
/// </summary>
/// <param name="op">运算符</param>
/// <returns>返回的是一个实现了接口ICalculatorable.ICalculable的子类对象</returns>
public static ICalculatorable.ICalculable CreatInstance (string op)
{
//创建一个接口变量,赋值为null
ICalculatorable.ICalculable cc = null;
////根据传递过来的运算符来创建对应的实例成员
switch (op)
{
//如果传递过来的是加法运算符,就创建一个计算加法的实例成员,并且赋值给上面的接口变量
case "+": cc = new AddLib_02.Add(); break;
case "-": cc = new SubLib_02.Sub(); break;
case "*": cc = new MultipLib_02.Multip(); break;
case "/": cc = new DivLib_02.Div(); break;
case "%": cc = new ModelLib_02.Model(); break;
//若上面的都不符合,就将接口变量赋值为null
default: cc = null;
break;
}
//返回指向了一个具体实例对象的接口变量
return cc;
}
简单计算器的静态工厂
其实在访问数据库的时候也是一样的,唯一变的就是链接实例,所以就可以把创建实例的东西给抽出来,交给一个工厂来实现,工厂可以通过读取配置文件来进行创建相应的实例对象,然后返回。代码如下:
internal sealed class DbProvideFactory
{
private static string dbProvide = ConfigurationManager.AppSettings["provide"];
public static string DbProvide { get { return dbProvide; } } private static string connectionString = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString;
/// <summary>
/// 连接字符串
/// </summary>
public static string ConnectionString
{
get { return connectionString; }
private set { connectionString = value; }
} #region 返回一个连接对象
public static DbConnection GetConnection ()
{
switch (dbProvide.ToLower())
{
case "sqlserver":
case "sql":
case "mssqlserver":
case "mssql": return new SqlConnection(connectionString);
case "access": return new OleDbConnection(connectionString);
case "sqlite": return new SQLiteConnection(connectionString);
case "oracle": return new OracleConnection(connectionString);
default: return new SqlConnection(connectionString);
}
}
#endregion #region 返回一个 Adapter
/// <summary>
/// 返回一个 Adapter
/// </summary>
/// <param name="cmd">Command 命令</param>
/// <returns></returns>
public static DataAdapter GetAdapter (IDbCommand cmd)
{
switch (dbProvide.ToLower())
{
case "sqlserver":
case "sql":
case "mssqlserver":
case "mssql": return new SqlDataAdapter(cmd as SqlCommand);
case "access": return new OleDbDataAdapter(cmd as OleDbCommand);
case "sqlite": return new SQLiteDataAdapter(cmd as SQLiteCommand);
case "oracle": return new OracleDataAdapter(cmd as OracleCommand);
default: return new SqlDataAdapter(cmd as SqlCommand);
}
}
#endregion #region 生成参数化查询时的参数对象
/// <summary>
/// 生成参数化查询时的参数对象
/// </summary>
/// <param name="name">参数名,@符号可有可无</param>
///
/// <returns></returns>
public static DbParameter GetParameter (string name, object value)
{
if (!name.StartsWith("@"))
{
name = "@" + name;
}
if (value == null)
{
value = DBNull.Value;
}
switch (dbProvide.ToLower())
{
case "sqlserver":
case "sql":
case "mssqlserver":
case "mssql": return new SqlParameter(name, value);
case "access": return new OleDbParameter(name, value);
case "sqlite": return new SQLiteParameter(name, value);
case "oracle": return new OracleParameter(name, value);
default: return new SqlParameter(name, value);
}
} /// <summary>
/// 生成参数化查询时的 输出参数
/// </summary>
/// <param name="name">参数名</param>
/// <param name="type">参数类型</param>
/// <returns></returns>
public static IDbDataParameter GetParameter (string name,DbType type)
{
if (!name.StartsWith("@"))
{
name = "@" + name;
}
IDbDataParameter p;
switch (dbProvide.ToLower())
{
case "sqlserver":
case "sql":
case "mssqlserver":
case "mssql": p = new SqlParameter(name, type); break;
case "access": p = new OleDbParameter(name, type); break;
case "sqlite": p = new SQLiteParameter(name, type); break;
case "oracle": p = new OracleParameter(name, type); break;
default: p = new SqlParameter(name, type); break;
}
p.Direction = ParameterDirection.Output;
return p;
}
#endregion
}
上面代码中首先读取了配置文件,以及连接字符串,这点可能封装的是太好,按理说,应该从DbHelper中传递过来,但是考虑到后面多处用到它,就把他放到这个地方了。
根据配置文件中数据提供者的值不同然后创建不同的数据库连接对象。同时由于在参数化查询的时候,需要实例化参数对象,有封装了两个参数对象方法。在用SqlServer的存储过程中,考虑到有输出参数,古做了一个重载。
至于 DataAdapter,由于它里也是可以自己创建连接的,所以也就比较麻烦一点,手动创建了,其实可以利用微软已经提供好的一种方式,就是通过读取配置文件中的配置提供者,然后调用一些方法来进行创建,实现原理中应该也是利用的反射,由于这个值在配置文件中比较麻烦,需要写全称,也就放弃它了。
上面基本上就是利用简单工厂来实现的创建不同数据库实例和参数化对象的方式。
SQLite需要在官方下载ADO.NET的插件,然后进行引用,Oracle数据库没有尝试过,也需要应用组件,列在此处也是为了以后方便扩展
DbHelper
这里面就封装了几个常规的增删改查的方法,调用工厂的方法得到连接对象和参数,然后用接口去指向他们,由于使用的是接口去指向,故在添加参数的时候,只能够通过遍历来实现,可以改为抽象类来指向,这样就能够一次添加一个数组了。代码如下:
public partial class DbHelper
{
//#region 得到一个连接对象,由具体的子类去重写而实现
///// <summary>
///// 得到一个连接对象,由具体的子类去重写而实现
///// </summary>
///// <returns></returns>
//public abstruct DbConnection GetConnection ();
//#endregion #region 执行增加、删除、更新三个非查询语句
public static int ExecuteNonQuery (string strSql, params IDataParameter[] pars)
{
using (IDbConnection conn = DbProvideFactory.GetConnection() )
{
using (IDbCommand cmd = conn.CreateCommand())
{
cmd.CommandText = strSql; if (pars != null && pars.Length > )
{
foreach (var item in pars)
{
cmd.Parameters.Add(item);
}
}
conn.Open();
return cmd.ExecuteNonQuery();
}
}
}
#endregion #region 执行标量值查询
public static object ExecuteScalar (string strSql, params IDataParameter[] pars)
{
using (IDbConnection conn = DbProvideFactory.GetConnection())
{
using (IDbCommand cmd = conn.CreateCommand())
{
cmd.CommandText = strSql;
if (pars != null && pars.Length > )
{
foreach (var item in pars)
{
cmd.Parameters.Add(item);
}
}
conn.Open();
return cmd.ExecuteScalar();
}
}
}
#endregion #region 返回一个只读的DbDataReader
public static IDataReader ExecuteReader (string strSql,CommandType type, params IDataParameter[] pars)
{
IDbConnection conn = DbProvideFactory.GetConnection(); using (IDbCommand cmd = conn.CreateCommand())
{
cmd.CommandText = strSql;
if (pars != null && pars.Length > )
{
foreach (var item in pars)
{
cmd.Parameters.Add(item);
}
}
cmd.CommandType = type;
try
{
conn.Open();
//外部关闭当前连接
return cmd.ExecuteReader(System.Data.CommandBehavior.CloseConnection);
}
catch
{
conn.Close();
conn.Dispose();
throw;
}
}
}
#endregion #region 返回一张数据表格,如果没有查到数据就返回null
//由于用的是接口(或者抽象类)不能够直接创建Command来添加参数,所以调用了工厂的方法创建一个连接对象,然后创建Command来实现参数的添加
public static DataTable DataAdpater (string strSql, params IDataParameter[] pars)
{
using (IDbConnection conn = DbProvideFactory.GetConnection())
{
IDbCommand cmd = conn.CreateCommand();
cmd.CommandText = strSql;
if (pars != null && pars.Length > )
{
foreach (var item in pars)
{
cmd.Parameters.Add(item);
}
}
IDataAdapter adapter = DbProvideFactory.GetAdapter(cmd);
DataSet set = new DataSet();
int count = adapter.Fill(set);
return count > ? set.Tables[] : null;
}
}
#endregion #region 将一个SqlDataReader对象转换成一个实体类对象 +static TEntity MapEntity<TEntity>(SqlDataReader reader) where TEntity : class,new()
/// <summary>
/// 将一个DataReader对象转换成一个实体类对象
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <param name="reader">当前指向的reader</param>
/// <returns>实体对象</returns>
public static TEntity MapEntity<TEntity>(IDataReader reader) where TEntity : class,new()
{
try
{
var props = typeof(TEntity).GetProperties();
var entity = new TEntity();
foreach (var prop in props)
{
if (prop.CanWrite)
{
try
{
var index = reader.GetOrdinal(prop.Name);
var data = reader.GetValue(index);
if (data != DBNull.Value)
{
prop.SetValue(entity, Convert.ChangeType(data, prop.PropertyType), null);
}
}
catch (IndexOutOfRangeException)
{
continue;
}
}
}
return entity;
}
catch
{
return null;
}
}
#endregion
}
在上面代码中,如果程序需要使用具体的连接对象,可以将DBHelper改成一个抽象类,然后把创建连接实例对象改成抽象方法,就可以了。
通过上面的简单工厂和用接口来指向操作数据库的各种对象就能够做到简单的跨数据库的功能了