异步的 SQL 数据库封装(1)
引言
我一直在寻找一种简单有效的库,它能在简化数据库相关的编程的同时提供一种异步的方法来预防死锁。
我找到的大部分库要么太繁琐,要么灵活性不足,所以我决定自己写个。
使用这个库,你可以轻松地连接到任何 SQL-Server 数据库,执行任何存储过程或 T-SQL 查询,并异步地接收查询结果。这个库采用 C# 开发,没有其他外部依赖。
背景
你可能需要一些事件驱动编程的背景知识,但这不是必需的。
使用
这个库由两个类组成:
首先,你需要像这样创建 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 style="line-height:1.5;font-size:9pt;">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 '%{1}%'", 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 有三类:
对于存储过程,只有一种需要传入 ExecuteStoredProcedure 的数据类型。但在声明变量时你需要指明存储过程的返回值类型:
StoredProcedureCallbackResult userData(eRequestType):除了声明不同外,其他操作与上面相同。