DBHelper (支持事务与数据库变更)


1   概述

更新内容:添加 "支持数据分页"

这个数据库操作类的主要特色有

1>     事务操作更加的方便

2>     变更数据库更加的容易

3>   支持数据分页

最新的所有代码:

using System;
using System.Data;
using System.Data.Common;
using Project.BaseFramework;
using System.Collections.Generic;
using System.Configuration;

namespace Project.BaseFramework.DataProvider
{
public class DBHelper
{

#region Constuctor
public DBHelper() { }

private static string ConnectionString = ConfigurationManager.AppSettings["DBConnectionString"];

private static IDBClient DBClient = DBClientFactory.GetDBClient(ConfigurationManager.AppSettings["DBClient"]);

[ThreadStatic]
private static TransConnection TransConnectionObj = null;

#endregion

#region ExecuteNonQuery
public static int ExecuteNonQuery(CommandType cmdType, string cmdText, params DbParameter[] parameterValues)
{
int result = 0;
bool mustCloseConn = true;

DbCommand cmd
= PrepareCmd(cmdType, cmdText, parameterValues, out mustCloseConn);
OpenConn(cmd.Connection);
result
= cmd.ExecuteNonQuery();

if (mustCloseConn) CloseConn(cmd.Connection);
ClearCmdParameters(cmd);
cmd.Dispose();

return result;
}

#endregion ExecuteNonQuery

#region ExecuteScalar
public static object ExecuteScalar(CommandType cmdType, string cmdText, params DbParameter[] parameterValues)
{
object result = 0;
bool mustCloseConn = true;

DbCommand cmd
= PrepareCmd(cmdType, cmdText, parameterValues, out mustCloseConn);
OpenConn(cmd.Connection);
result
= cmd.ExecuteScalar();

if (mustCloseConn) CloseConn(cmd.Connection);
ClearCmdParameters(cmd);
cmd.Dispose();

return result;
}
#endregion ExecuteScalar

#region ExecuteReader
public static DbDataReader ExecuteReader(CommandType cmdType, string cmdText, params DbParameter[] parameterValues)
{
DbDataReader result
= null;
bool mustCloseConn = true;
DbCommand cmd
= PrepareCmd(cmdType, cmdText, parameterValues, out mustCloseConn);
try
{
OpenConn(cmd.Connection);
if (mustCloseConn)
{
result
= cmd.ExecuteReader(CommandBehavior.CloseConnection);
}
else
{
result
= cmd.ExecuteReader();
}
ClearCmdParameters(cmd);
return result;
}
catch (Exception ex)
{
if (mustCloseConn) CloseConn(cmd.Connection);
ClearCmdParameters(cmd);
cmd.Dispose();
throw ;
}
}
#endregion ExecuteReader

#region ExecuteDataset
public static DataSet ExecuteDataSet(CommandType cmdType, string cmdText, params DbParameter[] parameterValues)
{
DataSet result
= null;
bool mustCloseConn = true;

DbCommand cmd
= PrepareCmd(cmdType, cmdText, parameterValues, out mustCloseConn);
using (DbDataAdapter da = DBClient.GetDbDataAdappter())
{
da.SelectCommand
= cmd;
result
= new DataSet();

da.Fill(result);
}

if (mustCloseConn) CloseConn(cmd.Connection);
ClearCmdParameters(cmd);
cmd.Dispose();

return result;
}
#endregion ExecuteDataset

#region ExecuteDataTable
public static DataTable ExecuteDataTable(CommandType cmdType, string cmdText, params DbParameter[] parameterValues)
{
DataSet ds
= ExecuteDataSet(cmdType,cmdText, parameterValues);
if (ds != null && ds.Tables.Count > 0)
return ds.Tables[0];
else
return null;
}
#endregion

#region ExecutePaging
public static DataTable ExecutePagingDataTable(CommandType cmdType, string cmdText,int pageIndex,int pageSize,string orderInfo, params DbParameter[] parameterValues)
{
cmdText
= DBClient.GetPagingSql(cmdText, pageIndex, pageSize, orderInfo);
return ExecuteDataTable(CommandType.Text, cmdText, parameterValues);
}

public static DbDataReader ExecutePagingReader(CommandType cmdType, string cmdText, int pageIndex, int pageSize, string orderInfo, params DbParameter[] parameterValues)
{
cmdText
= DBClient.GetPagingSql(cmdText, pageIndex, pageSize, orderInfo);
return ExecuteReader(CommandType.Text, cmdText, parameterValues);
}
#endregion

#region Transaction
public static void BeginTransaction()
{
if (TransConnectionObj == null)
{
DbConnection conn
= DBClient.GetDbConnection(ConnectionString);
OpenConn(conn);
DbTransaction trans
= conn.BeginTransaction();
TransConnectionObj
= new TransConnection();
TransConnectionObj.DBTransaction
= trans;
}
else
{
TransConnectionObj.Deeps
+= 1;
}
}

public static void CommitTransaction()
{
if (TransConnectionObj == null) return;
if (TransConnectionObj.Deeps > 0)
{
TransConnectionObj.Deeps
-= 1;
}
else
{
TransConnectionObj.DBTransaction.Commit();
ReleaseTransaction();
}
}

public static void RollbackTransaction()
{
if (TransConnectionObj == null) return;
if (TransConnectionObj.Deeps > 0)
{
TransConnectionObj.Deeps
-= 1;
}
else
{
TransConnectionObj.DBTransaction.Rollback();
ReleaseTransaction();
}
}

private static void ReleaseTransaction()
{
if (TransConnectionObj == null) return;
DbConnection conn
= TransConnectionObj.DBTransaction.Connection;
TransConnectionObj.DBTransaction.Dispose();
TransConnectionObj
= null;
CloseConn(conn);
}

#endregion

#region Connection
private static void OpenConn(DbConnection conn)
{
if (conn == null) conn = DBClient.GetDbConnection(ConnectionString);
if (conn.State == ConnectionState.Closed) conn.Open();
}

private static void CloseConn(DbConnection conn)
{
if (conn == null) return;
if (conn.State == ConnectionState.Open) conn.Close();
conn.Dispose();
conn
= null;
}
#endregion

#region Create DbParameter

public static DbParameter CreateInDbParameter(string paraName, DbType type, int size, object value)
{
return CreateDbParameter(paraName, type, size, value, ParameterDirection.Input);
}

public static DbParameter CreateInDbParameter(string paraName, DbType type, object value)
{
return CreateDbParameter(paraName, type, 0, value, ParameterDirection.Input);
}

public static DbParameter CreateOutDbParameter(string paraName, DbType type, int size)
{
return CreateDbParameter(paraName, type, size, null, ParameterDirection.Output);
}

public static DbParameter CreateOutDbParameter(string paraName, DbType type)
{
return CreateDbParameter(paraName, type, 0, null, ParameterDirection.Output);
}

public static DbParameter CreateReturnDbParameter(string paraName, DbType type, int size)
{
return CreateDbParameter(paraName, type, size, null, ParameterDirection.ReturnValue);
}

public static DbParameter CreateReturnDbParameter(string paraName, DbType type)
{
return CreateDbParameter(paraName, type, 0, null, ParameterDirection.ReturnValue);
}

public static DbParameter CreateDbParameter(string paraName, DbType type, int size, object value, ParameterDirection direction)
{
DbParameter para
= DBClient.GetDbParameter();

para.ParameterName
= paraName;

if (size != 0)
{
para.Size
= size;
}

para.DbType
= type;

if (value != null)
{
para.Value
= value;
}
else
{
para.Value
= DBNull.Value;
}

para.Direction
= direction;

return para;
}

#endregion

#region Command and Parameter
/// <summary>
/// 预处理用户提供的命令,数据库连接/事务/命令类型/参数
/// </summary>
/// <param>要处理的DbCommand</param>
/// <param>数据库连接</param>
/// <param>一个有效的事务或者是null值</param>
/// <param>命令类型 (存储过程,命令文本, 其它.)</param>
/// <param>存储过程名或都T-SQL命令文本</param>
/// <param>和命令相关联的DbParameter参数数组,如果没有参数为'null'</param>
/// <param><c>true</c> 如果连接是打开的,则为true,其它情况下为false.</param>
private static DbCommand PrepareCmd(CommandType cmdType,string cmdText, DbParameter[] cmdParams, out bool mustCloseConn)
{
DbCommand cmd
= DBClient.GetDbCommand(cmdText);

DbConnection conn
= null;
if (TransConnectionObj != null)
{
conn
= TransConnectionObj.DBTransaction.Connection;
cmd.Transaction
= TransConnectionObj.DBTransaction;
mustCloseConn
= false;
}
else
{
conn
= DBClient.GetDbConnection(ConnectionString);
mustCloseConn
= true;
}
cmd.Connection
= conn;

cmd.CommandType
= cmdType;

AttachParameters(cmd, cmdParams);

return cmd;
}

/// <summary>
/// 将DbParameter参数数组(参数值)分配给DbCommand命令.
/// 这个方法将给任何一个参数分配DBNull.Value;
/// 该操作将阻止默认值的使用.
/// </summary>
/// <param>命令名</param>
/// <param>SqlParameters数组</param>
private static void AttachParameters(DbCommand command, DbParameter[] commandParameters)
{
if (command == null) throw new ArgumentNullException("command");
if (commandParameters != null)
{
foreach (DbParameter p in commandParameters)
{
if (p != null)
{
// 检查未分配值的输出参数,将其分配以DBNull.Value.
if ((p.Direction == ParameterDirection.InputOutput || p.Direction == ParameterDirection.Input) &&
(p.Value
== null))
{
p.Value
= DBNull.Value;
}
command.Parameters.Add(p);
}
}
}
}

private static void ClearCmdParameters(DbCommand cmd)
{
bool canClear = true;
if (cmd.Connection != null && cmd.Connection.State != ConnectionState.Open)
{
foreach (DbParameter commandParameter in cmd.Parameters)
{
if (commandParameter.Direction != ParameterDirection.Input)
{
canClear
= false;
break;
}
}
}
if (canClear)
{
cmd.Parameters.Clear();
}
}
#endregion
}
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;

namespace Project.BaseFramework.DataProvider
{
internal class TransConnection
{
public TransConnection()
{
this.Deeps = 0;
}

public DbTransaction DBTransaction { get; set; }

public int Deeps { get; set; }
}
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
using System.Data.SqlClient;

namespace Project.BaseFramework.DataProvider
{
public interface IDBClient
{
DbConnection GetDbConnection(
string connectionString);

DbCommand GetDbCommand(
string cmdText);

DbDataAdapter GetDbDataAdappter();

DbParameter GetDbParameter();

string GetPagingSql(string cmdText, int pageIndex, int pageSize, string orderInfo);
}
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
using System.Data.SqlClient;

namespace Project.BaseFramework.DataProvider
{
public class SqlServerClient:IDBClient
{
public DbConnection GetDbConnection(string connectionString)
{
return new SqlConnection(connectionString);
}

public DbCommand GetDbCommand(string cmdText)
{
return new SqlCommand(cmdText);
}

public DbDataAdapter GetDbDataAdappter()
{
return new SqlDataAdapter();
}

public DbParameter GetDbParameter()
{
return new SqlParameter();
}

public string GetPagingSql(string cmdText, int pageIndex, int pageSize, string orderInfo)
{
int startIndex = (pageIndex - 1) * pageSize;
int endIndex = startIndex + pageSize + 1;
cmdText
= string.Format(@";WITH T1 AS({0}),T2 AS(SELECT *,ROW_NUMBER()OVER ({1}) AS _RowNum FROM T1)
SELECT *FROM T2
WHERE _RowNum>{2} AND _RowNum<{3}
",cmdText,orderInfo,startIndex,endIndex);
return cmdText;
}
}
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Common;
using MySql.Data.MySqlClient;

namespace Project.BaseFramework.DataProvider
{
public class MySqlClient:IDBClient
{
public DbConnection GetDbConnection(string connectionString)
{
return new MySqlConnection(connectionString);
}

public DbCommand GetDbCommand(string cmdText)
{
return new MySqlCommand(cmdText);
}

public DbDataAdapter GetDbDataAdappter()
{
return new MySqlDataAdapter();
}

public DbParameter GetDbParameter()
{
return new MySqlParameter();
}

public string GetPagingSql(string cmdText, int pageIndex, int pageSize, string orderInfo)
{
int startIndex = (pageIndex - 1) * pageSize;
cmdText
= string.Format(@"{0} {1} Limit {2}, {3}", cmdText, orderInfo, startIndex,pageSize);
return cmdText;
}
}
}


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Reflection;

namespace Project.BaseFramework.DataProvider
{
public class DBClientFactory
{
private static readonly string path = "Project.BaseFramework";

public static IDBClient GetDBClient(string dbClientClassName)
{
if(string.IsNullOrEmpty(dbClientClassName))
dbClientClassName
="SqlServerClient";
string className = string.Format("{0}.DataProvider.{1}", path, dbClientClassName);
return (IDBClient)Assembly.Load(path).CreateInstance(className);
}
}
}
View Code

配置文件

<appSettings>
<add key="DBConnectionString" value="Data Source=.;Initial Catalog=ProjectData;Persist Security Info=True;User ID=sa;Password=kjkj,911;"/>
<add key="DBClient" value="SqlServerClient"/>
</appSettings>

 

 

2  事务操作

2.1 单个事务操作示例

try
{
DBHelper.BeginTransaction();
// add
DBHelper.ExecuteNonQuery(CommandType.Text, "INSERT INTO TRole(ID,RoleName) VALUES('R1','MKT')");

//detele by pk
DBHelper.ExecuteNonQuery(CommandType.Text, "DELETE FROM TRole WHERE ID='R1'");

Console.WriteLine(
string.Format("Success and Commited"));
DBHelper.CommitTransaction();
}
catch (Exception ex)
{
Console.WriteLine(
string.Format("Exception and rollback"));
DBHelper.RollbackTransaction();
}

 

用法是:只需要把相关联的代码放在BeginTransaction和CommitTransaction中间,如果发生异常调用RollbackTransaction即可。

实现事务的方法是:

首先,DBHelper维护一个TransConnection类型的字段,并添加ThreadStatic. ThreadStatic可以维护在线程级别上的唯一性。

[ThreadStatic]
private static TransConnection TransConnectionObj = null;

 

其次,TransConnection的作用是保存事务,并记录嵌套事务的嵌套级别。

internal class TransConnection
{
public TransConnection()
{
this.Deeps = 0;
}

public DbTransaction DBTransaction { get; set; }

public int Deeps { get; set; }
}

 

最后,当调用 BeginTransaction时创建TransConnection对象。之后的多个DbCommand命令都从这个事务上拿连接。因为TransConnectionObj添加了ThreadStatic属性,所以它是线程唯一的,不会影响其它线程上的事务;所有方法执行完后,调用CommitTransaction 就提交事务,并关闭连接;如果发生异常,则调用RollbackTransaction,就会回滚所有命令,并关闭连接。

2.2 嵌套事务示例

static void Main(string[] args)
{

try
{
DBHelper.BeginTransaction();

// add
DBHelper.ExecuteNonQuery(CommandType.Text, "INSERT INTO TRole(ID,RoleName) VALUES('R1','MKT')");

Transaction2();

//detele by pk
DBHelper.ExecuteNonQuery(CommandType.Text, "DELETE FROM TRole WHERE ID='R1'");

Console.WriteLine(
string.Format("Success and Commited"));
DBHelper.CommitTransaction();
}
catch (Exception ex)
{
Console.WriteLine(
string.Format("Exception and rollback"));
DBHelper.RollbackTransaction();
}

Console.ReadLine();
}

private static void Transaction2()
{
try
{
DBHelper.BeginTransaction();
//update model
DBHelper.ExecuteNonQuery(CommandType.Text, "UPDATE TRole SET RoleName='Marketer' WHERE ID='R1'");
//throw new Exception("");

DbParameter param
= DBHelper.CreateInDbParameter("@ID", DbType.String, "R1");
DbDataReader reader
= DBHelper.ExecuteReader(CommandType.Text, "SELECT * FROM TRole WHERE ID=@ID",param);
while (reader.Read())
{
Console.WriteLine(reader[
"RoleName"]);
}

reader.Close();

DBHelper.CommitTransaction();
}
catch(Exception ex)
{
Console.WriteLine(
string.Format("Exception and rollback: {0}", ex.Message));
DBHelper.RollbackTransaction();
throw;
}
}

 

2.2.1

当为嵌套事务时,首次调用BeginTransaction,同样会创建新的TransConnection对象,深度默认为0,并保存在TransConnectionObj字段上;

第n(n>1)次调用时方法时,仅会累加嵌套的深度,不会开起新的事务。

public static void BeginTransaction()
{
if (TransConnectionObj == null)
{
DbConnection conn
= DBClient.GetDbConnection(ConnectionString);
OpenConn(conn);
DbTransaction trans
= conn.BeginTransaction();
TransConnectionObj
= new TransConnection();
TransConnectionObj.DBTransaction
= trans;
}
else
{
TransConnectionObj.Deeps
+= 1;
}
}

 

2.2.2

当CommitTransaction提交事务时,如果深度Deeps>0,那么表示此次提交的事务是内层事务,计数器减1即可;

当调用CommitTransaction提交事务,如果深度为0时,表示为最外层事务,刚做实际上的提交事务工作;

public static void CommitTransaction()
{
if (TransConnectionObj == null) return;
if (TransConnectionObj.Deeps > 0)
{
TransConnectionObj.Deeps
-= 1;
}
else
{
TransConnectionObj.DBTransaction.Commit();
ReleaseTransaction();
}
}

 

2.2.3

当RollbackTransaction提交事务时,如果深度Deeps>0,那么表示此次提交的事务是内层事务,计数器减1即可;

当调用RollbackTransaction提交事务,如果深度为0时,表示为最外层事务,刚做实际上的回滚操作;

public static void RollbackTransaction()
{
if (TransConnectionObj == null) return;
if (TransConnectionObj.Deeps > 0)
{
TransConnectionObj.Deeps
-= 1;
}
else
{
TransConnectionObj.DBTransaction.Rollback();
ReleaseTransaction();
}
}

 

3  变更数据库

同一个底层库,应用到不同项目时,数据库可能会不同。如果我们比较下不同数据库操作类之间的不同点,我们会发现所有的方法都是一致的,就是某些类型不同,如下表所示:

 

基类

SQL Server

MySql

DbConnection

SqlConnection

MySqlConnection

DbCommand

SqlCommand

MySqlCommand

DbDataAdapter

SqlDataAdapter

MySqlDataAdapte

DbParameter

SqlParameter

MySqlParameter

 

所以,根据子类出现的地方,可以用父类替换的原则,将SqlHeper中关于特定数据库的类,换成基类,并将创建特定数据库对象实例的代码统一到IDBClinet中

主要类有

DBHelper: 使用基类访问数据库,并聚合IDBClient来创建特定数据库对象的实例。

IDBClient: 定义创建特定数据库实例的接口,并转成基类型;

SqlServerClient:定义创建SqlServer对象的实例,并转成基类型;

MySqlCient:定义创建MySql对象的实例,并转成基类型;

DBClientFactory:根据类名动态创建IDBClient的实现类;

 

最后如果想要更换数据库时,只需要修改如下代码,并在配置文件中修改下连接字符串和具体的DBClient的类名:

<appSettings>
<add key="DBConnectionString" value="Data Source=.;Initial Catalog=ProjectData;Persist Security Info=True;User ID=sa;Password=kjkj,911;"/>
<add key="DBClient" value="SqlServerClient"/>
</appSettings>

 

4  支持数据分页

为了支持数据分页,在DBHelper添加了两个方法,分别返回DataTable和DbDataReader, DbDataReader用于在ORM中转成实体对象。

#region ExecutePaging
public static DataTable ExecutePagingDataTable(CommandType cmdType, string cmdText,int pageIndex,int pageSize,string orderInfo, params DbParameter[] parameterValues)
{
cmdText
= DBClient.GetPagingSql(cmdText, pageIndex, pageSize, orderInfo);
return ExecuteDataTable(CommandType.Text, cmdText, parameterValues);
}

public static DbDataReader ExecutePagingReader(CommandType cmdType, string cmdText, int pageIndex, int pageSize, string orderInfo, params DbParameter[] parameterValues)
{
cmdText
= DBClient.GetPagingSql(cmdText, pageIndex, pageSize, orderInfo);
return ExecuteReader(CommandType.Text, cmdText, parameterValues);
}
#endregion

 在分页时,虽然各种数据库的分页方法不一样,但它们都需要信息是:排序字段,开始索引位置,页大小,结束索引位置(可以通过开始索引位置和页大小计算出来)。至于它们最终分页的SQL不一样,可以放在实现了IDBClient的类中。

比如在SqlServerDBClient是用ROW_NUMBER做的:

public string GetPagingSql(string cmdText, int pageIndex, int pageSize, string orderInfo) 
{
int startIndex = (pageIndex - 1) * pageSize;
int endIndex = startIndex + pageSize + 1;
cmdText
= string.Format(@";WITH T1 AS({0}),T2 AS(SELECT *,ROW_NUMBER()OVER ({1}) AS _RowNum FROM T1)
SELECT *FROM T2
WHERE _RowNum>{2} AND _RowNum<{3}
",cmdText,orderInfo,startIndex,endIndex);
return cmdText;
}

在MySqlDBClient中是用Limit 实现的:

public string GetPagingSql(string cmdText, int pageIndex, int pageSize, string orderInfo)
{
int startIndex = (pageIndex - 1) * pageSize;
cmdText
= string.Format(@"{0} {1} Limit {2}, {3}", cmdText, orderInfo, startIndex,pageSize);
return cmdText;
}

 

在下一次随笔中,将会用这个数据库操作类,以及上篇文章用T4 Template生成代码 来实现一个简单的ORM框架,支持CRUD。暂时不考虑用反射来实现这个ORM,暂时还hold不住反射的性能问题。

 


注意!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。



 
© 2014-2018 ITdaan.com 粤ICP备14056181号