共计 5456 个字符,预计需要花费 14 分钟才能阅读完成。
本篇内容主要讲解“异步的 SQL 数据库的封装过程”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让丸趣 TV 小编来带大家学习“异步的 SQL 数据库的封装过程”吧!
引言
我一直在寻找一种简单有效的库,它能在简化数据库相关的编程的同时提供一种异步的方法来预防死锁。
我找到的大部分库要么太繁琐,要么灵活性不足,所以我决定自己写个。
使用这个库,你可以轻松地连接到任何 SQL-Server 数据库,执行任何存储过程或 T-SQL 查询,并异步地接收查询结果。这个库采用 C# 开发,没有其他外部依赖。
背景
你可能需要一些事件驱动编程的背景知识,但这不是必需的。
使用
这个库由两个类组成:
1、BLL (Business Logic Layer) 提供访问 MS-SQL 数据库、执行命令和查询并将结果返回给调用者的方法和属性。你不能直接调用这个类的对象,它只供其他类继承.2、DAL (Data Access Layer) 你需要自己编写执行 SQL 存储过程和查询的函数,并且对于不同的表你可能需要不同的 DAL 类。首先,你需要像这样创建 DAL 类:
namespace SQLWrapper { public class DAL : BLL { public DAL(string server, string db, string user, string pass) { base.Start(server, db, user, pass); } ~DAL() { base.Stop(eStopType.ForceStopAll); } /////////////////////////////////////////////////////////// // TODO: Here you can add your code here... } }
由于 BLL 类维护着处理异步查询的线程,你需要提供必要的数据来拼接连接字符串。千万别忘了调用 `Stop` 函数,否则析构函数会强制调用它。
NOTE:如果需要连接其他非 MS-SQL 数据库,你可以通过修改 BLL 类中的 `CreateConnectionString` 函数来生成合适的连接字符串。
为了调用存储过程,你应该在 DAL 中编写这种函数:
public int MyStoreProcedure(int param1, string param2) { // 根据存储过程的返回类型创建用户数据 StoredProcedureCallbackResult userData = new StoredProcedureCallbackResult(eRequestType.Scalar); // 在此定义传入存储过程的参数,如果没有参数可以省略 span userData.Parameters = new System.Data.SqlClient.SqlParameter[] { /span new System.Data.SqlClient.SqlParameter( @param1 , param1), new System.Data.SqlClient.SqlParameter(@param2 , param2), }; // Execute procedure... if (!ExecuteStoredProcedure( usp_MyStoreProcedure , userData)) throw new Exception(Execution failed // 等待执行完成... // 等待时长为 userdata.tswaitforresult // 执行未完成返回 timeout if (WaitSqlCompletes(userData) != eWaitForSQLResult.Success) throw new Exception(Execution failed // Get the result... return userData.ScalarValue; }
正如你所看到的,存储过程的返回值类型可以是 `Scalar`,`Reader` 和 `NonQuery`。对于 `Scalar`,`userData` 的 `ScalarValue` 参数有意义 (即返回结果);对于 `NonQuery`,`userData` 的 `AffectedRows` 参数就是受影响的行数;对于 `Reader` 类型,`ReturnValue` 就是函数的返回值,另外你可以通过 `userData` 的 `resultDataReader` 参数访问 recordset。
再看看这个示例:
public bool MySQLQuery(int param1, string param2) { // Create user data according to return type of store procedure in SQL( 这个注释没有更新, 说明《注释是魔鬼》有点道理) ReaderQueryCallbackResult userData = new ReaderQueryCallbackResult(); string sqlCommand = string.Format( SELECT TOP(1) * FROM tbl1 WHERE code = {0} AND name LIKE apos;%{1}% apos; , param1, param2); // Execute procedure... if (!ExecuteSQLStatement(sqlCommand, userData)) return false; // Wait until it finishes... // Note, it will wait (userData.tsWaitForResult) // for the command to be completed otherwise returns timeout if (WaitSqlCompletes(userData) != eWaitForSQLResult.Success) return false; // Get the result... if(userData.resultDataReader.HasRows userData.resultDataReader.Read()) { // Do whatever you want.... int field1 = GetIntValueOfDBField(userData.resultDataReader[ Field1], -1); string field2 = GetStringValueOfDBField(userData.resultDataReader[ Field2], null); Nullable datetime field3 = GetDateValueOfDBField(userData.resultDataReader[ Field3], null); float field4 = GetFloatValueOfDBField(userData.resultDataReader[ Field4], 0); long field5 = GetLongValueOfDBField(userData.resultDataReader[ Field5], -1); } userData.resultDataReader.Dispose(); return true; }
在这个例子中,我们调用 `ExecuteSQLStatement` 直接执行了一个 SQL 查询,但思想跟 `ExecuteStoredProcedure` 是一样的。
我们使用 `resultDataReader` 的 `.Read()` 方法来迭代处理返回的结果集。另外提供了一些 helper 方法来避免叠代中由于 NULL 字段、GetIntValueOfDBField 等引起的异常。
如果你要执行 SQL 命令而不是存储过程,需要传入 ExecuteSQLStatement 的 userData 有三类:
1、ReaderQueryCallbackResult userData:适用于有返回 recordset 的语句,可以通过 userData.resultDataReader 获得对返回的 recordset 的访问。2、NonQueryCallbackResult userData:适用于像 UPDATE 这种没有返回内容的语句,可以使用 userData.AffectedRows 检查执行的结果。3、ScalarQueryCallbackResult userData:用于查询语句只返回一个标量值的情况,例如 `SELECT code FROM tbl WHEN ID=10`,通过 userData.ScalarValue 取得返回的结果。对于存储过程,只有一种需要传入 ExecuteStoredProcedure 的数据类型。但在声明变量时你需要指明存储过程的返回值类型:
StoredProcedureCallbackResult userData(eRequestType):除了声明不同外,其他操作与上面相同。异步地使用代码
假使你不希望调用线程被查询阻塞,你需要周期性地调用 `WaitSqlCompletes` 来检查查询是否完成,执行是否失败。
/// summary /// 你需要周期性地调用 WaitSqlCompletes(userData, 10) /// 来查看结果是否可用! /// /summary public StoredProcedureCallbackResult MyStoreProcedureASYNC(int param1, string param2) { // Create user data according to return type of store procedure in SQL StoredProcedureCallbackResult userData = new StoredProcedureCallbackResult(eRequestType.Reader); // If your store procedure accepts some parameters, define them here, // or you can omit it incase there is no parameter definition userData.Parameters = new System.Data.SqlClient.SqlParameter[] { new System.Data.SqlClient.SqlParameter( @param1 , param1), new System.Data.SqlClient.SqlParameter(@param2 , param2), }; // Execute procedure... if (!ExecuteStoredProcedure( usp_MyStoreProcedure , userData)) throw new Exception(Execution failed return userData; }
在调用线程中你需要这样做:
... DAL.StoredProcedureCallbackResult userData = myDal.MyStoreProcedureASYNC(10, hello ... // each time we wait 10 milliseconds to see the result... switch(myDal.WaitSqlCompletes(userData, 10)) { case eWaitForSQLResult.Waiting: goto WAIT_MORE; case eWaitForSQLResult.Success: goto GET_THE_RESULT; default: goto EXECUTION_FAILED; } ...
数据库状态
在 BLL 中只有一个异步地提供数据库状态的事件。如果数据库连接被断开了 (通常是由于网络问题),OnDatabaseStatusChanged 事件就会被挂起。
另外,如果连接恢复了,这个事件会被再次挂起来通知你新的数据库状态。
有趣的地方
在我开发代码的时候,我明白了连接字符串中的连接时限(connection timeout)和 SQL 命令对象的执行时限(execution timeout)同样重要。
首先,你必须意识到最大容许时限是在连接字符串中定义的,并可以给出一些执行指令比连接字符串中的超时时间更长的时间。
其次,每一个命令都有着它们自己的执行时限,在这里的代码中默认为 30 秒。你可以很容易地修改它,使它适用于所有类型的命令,就像这样:
userData.tsWaitForResult = TimeSpan.FromSeconds(15);
到此,相信大家对“异步的 SQL 数据库的封装过程”有了更深的了解,不妨来实际操作一番吧!这里是丸趣 TV 网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!