asp.net中使用Sqlserver事务时的体会,借鉴测试项目的脚本控制思想...
很多企业应用系统,尤其是电商系统,事务的运用几乎是不可避免的.事务的作用黄岛主在这里就不多说了,百度一下自有详细资料...
这里黄岛主主要说的是,通常适当地使用事务不但可以保证执行的一致性,而且也降低了维护的工作负担. 曾经一段时间因为没有好好的运用事务特性,不得不结合运行日志,订单状态..进行分析,之后加上诸如【订单同步】,【价格同步】等可供人工干预执行的按钮.
出于之前对Visual studio自带的测试项目的了解,特意引入了类似的控制机制,对asp.net中应用sql事务进行了一定的封装...目前使用中还挺顺利的..如果有问题,欢迎讨论和拍砖..
/// <summary> /// 功能简介:SqlDBUtility的内部类,用于辅助控制批量的sql语句按照事务执行 /// 创建人:黄岛主(taohuadaozhu007@qq.com) /// 联系地址:http://www.cnblogs.com/taohuadaozhu /// 更新日志: /// 2020-4-16(pcw):增加了参数化的支持 /// </summary> public class TransactionController { public class Command { private string commendText = ""; public string CommendText { get { return commendText; } } private SqlParameter[] _sqlParamsArray = null; /// <summary> /// 参数对象 /// </summary> public SqlParameter[] SqlParamsArray { get { return this._sqlParamsArray; } } /// <summary> /// 期望的返回值,影响行数 /// </summary> private int expectReturn = 0; /// <summary> /// 实际返回值,影响行数 /// </summary> private int actualReturn = 0; /// <summary> /// true:严格要求实际返回值==期望返回值,false:要求实际返回值>期望返回值 /// </summary> private bool isRequestEqual = true; private string tipsOnError = ""; private CommandType _sqlType = CommandType.Text; public string TipsOnError { get { return tipsOnError; } set { tipsOnError = value; } } /// <summary> /// 执行当前的Sql命令之后,实际返回的值,也就是影响行数 /// </summary> public int ActualReturn { get { return actualReturn; } set { actualReturn = value; } } /// <summary> /// 一个Sql命令处理对象 /// </summary> /// <param name="sCommendText">执行的sql语句</param> /// <param name="iExpectReturn">期望返回值</param> /// <param name="bRequestEqual">true:严格要求实际返回值==期望返回值,false:要求实际返回值>期望返回值</param> public Command(string sCommendText, int iExpectReturn, bool bRequestEqual, string sTipsWhenError, CommandType sqlType) { this.commendText = sCommendText; this.expectReturn = iExpectReturn; this.isRequestEqual = bRequestEqual; this.tipsOnError = sTipsWhenError; this._sqlType = sqlType; } /// <summary> /// 一个Sql命令处理对象 /// </summary> /// <param name="sCommendText">执行的sql语句</param> /// <param name="iExpectReturn">期望返回值</param> /// <param name="bRequestEqual">true:严格要求实际返回值==期望返回值,false:要求实际返回值>期望返回值</param> public Command(string sCommendText, int iExpectReturn, bool bRequestEqual, string sTipsWhenError, SqlParameter[] paramsArray, CommandType sqlType) { this.commendText = sCommendText; this.expectReturn = iExpectReturn; this.isRequestEqual = bRequestEqual; this.tipsOnError = sTipsWhenError; this._sqlParamsArray = paramsArray; this._sqlType = sqlType; } /// <summary> /// 检查是否执行成功 /// </summary> /// <returns></returns> public bool IsSuccess() { if (this.isRequestEqual) return (actualReturn == expectReturn); return (actualReturn > expectReturn); } public CommandType SqlType { get { return this._sqlType; } } } private List<Command> listCommand = null; public List<Command> ListCommand { get { if (listCommand == null) listCommand = new List<Command>(); return listCommand; } } public void Clear() { if (this.listCommand != null) this.listCommand.Clear(); } public int GetSumActualReturn() { int iSumActualReturn = 0; if (listCommand == null) return 0; for (int i = 0; i < listCommand.Count; i++) { iSumActualReturn += listCommand[i].ActualReturn; } return iSumActualReturn; } public bool IsSuccess() { if (listCommand == null) return false; for (int i = 0; i < listCommand.Count; i++) { if (listCommand[i].IsSuccess() == false) return false; } return true; } /// <summary> /// 获得事务执行失败处的信息 /// </summary> /// <returns></returns> public string GetErrorPointMssage() { if (listCommand == null) return ""; for (int i = 0; i < listCommand.Count; i++) { if (listCommand[i].IsSuccess() == false) return string.Format("{0}(code:{1})", listCommand[i].TipsOnError, listCommand[i].ActualReturn); } return ""; } public string GetErrorPointSql() { if (listCommand == null) return ""; for (int i = 0; i < listCommand.Count; i++) { if (listCommand[i].IsSuccess() == false) { #region//打印 调试语句 string sSqlDebug = ""; try { if (listCommand[i].SqlParamsArray != null && listCommand[i].SqlParamsArray.Length > 0) { sSqlDebug = listCommand[i].CommendText; foreach (SqlParameter pam in listCommand[i].SqlParamsArray) { sSqlDebug = sSqlDebug.Replace($"?{pam.ParameterName}", $"'{pam.Value}'"); } } else { return listCommand[i].CommendText; } } catch (Exception ex) { } return sSqlDebug; #endregion//打印调试语句 } } return ""; } }
上面的代码,主要用于配置要一起执行(这些语句关联同一个业务操作,可能涉及多个表,但需要保证是在同一个数据库中)的sql命令对象Command,配置的过程中,注意指定每条sql语句预期的返回值(影响的行数). 以及是否需要将预期的返回值和实际的返回值进行对比(也就是那个bool类型的bRequestEqual参数了). Command最后一个参数sTipsWhenError旨在指定如果失败了,终止并回滚sql命令后提示给用户的错误提示内容..
具体结合ado.net进行检测,回滚,提交的代码如下,黄岛主在这里提示各位,这个可能需要根据你自己的SqlDBHelp类进行略微调整...
/// <summary> /// 功能备注:用事务的方式执行多条SQL语句 /// 更新日志: /// 2020-4-16(ww):修复了多条语句中出现相同变量导致重复声明,导致异常的问题。 /// 修复了存储过程类型的sql中没有设置CommondType /// </summary> /// <param name="trans_IsolationLevel">事务隔离级别设置,可以传入:IsolationLevel.ReadCommitted</param> /// <returns></returns> public static void RunSQLWithTransaction(TransactionController theTransactionTool, IsolationLevel trans_IsolationLevel, string sSQLConnectstr) { if (theTransactionTool == null || theTransactionTool.ListCommand.Count == 0) return; int iExcuteCount = 0; using (SqlConnection conn = new SqlConnection(sSQLConnectstr)) { if (conn.State == ConnectionState.Broken || conn.State == ConnectionState.Closed) conn.Open(); using (SqlTransaction tran = conn.BeginTransaction(trans_IsolationLevel)) { using (SqlCommand sqlcm = new SqlCommand()) { if (conn.State == ConnectionState.Closed || conn.State == ConnectionState.Broken) conn.Open(); sqlcm.Connection = conn; sqlcm.Transaction = tran; try { for (int i = 0; i < theTransactionTool.ListCommand.Count; i++) { sqlcm.CommandText = theTransactionTool.ListCommand[i].CommendText; sqlcm.CommandType = theTransactionTool.ListCommand[i].SqlType;//addbyww@2020-4-16 if(theTransactionTool.ListCommand[i].SqlParamsArray!=null&& theTransactionTool.ListCommand[i].SqlParamsArray.Length > 0) { sqlcm.Parameters.AddRange(theTransactionTool.ListCommand[i].SqlParamsArray); } theTransactionTool.ListCommand[i].ActualReturn = sqlcm.ExecuteNonQuery(); if (theTransactionTool.ListCommand[i].IsSuccess() == false) { tran.Rollback(); sqlcm.Dispose(); if (conn.State == ConnectionState.Open) conn.Close(); break; } else { if (sqlcm.Parameters != null)//addbypww@2020-5-17 { sqlcm.Parameters.Clear();//否则容易造成多个语句的参数重名就错误.. addbyww@2020-4-16 } } iExcuteCount++; } if (theTransactionTool.IsSuccess()) { tran.Commit(); } } catch (Exception ex) { tran.Rollback(); ConfigLab.Utils.SaveErrorLog(string.Format("执行脚本【{0}】失败,并执行事务回滚:{1}",theTransactionTool.ListCommand[iExcuteCount].CommendText,ex.Message)); } finally { sqlcm.Dispose(); tran.Dispose(); if(conn.State==ConnectionState.Open) conn.Close(); } } } } }
好了,具体的代码其实不重要,关键是向大家分享一下这个借鉴测试项目常用脚本的事务辅助控制机制...
附件地址:https://files.cnblogs.com/files/taohuadaozhu/SQLServerHelperV2.rar