非同步的 SQL 資料庫封裝
引言
我一直在尋找一種簡單有效的庫,它能在簡化資料庫相關的程式設計的同時提供一種非同步的方法來預防死鎖。
我找到的大部分庫要麼太繁瑣,要麼靈活性不足,所以我決定自己寫個。
使用這個庫,你可以輕鬆地連線到任何 SQL-Server 資料庫,執行任何儲存過程或 T-SQL 查詢,並非同步地接收查詢結果。這個庫採用 C# 開發,沒有其他外部依賴。
背景
你可能需要一些事件驅動程式設計的背景知識,但這不是必需的。
使用
這個庫由兩個類組成:
- BLL (Business Logic Layer) 提供訪問MS-SQL資料庫、執行命令和查詢並將結果返回給呼叫者的方法和屬性。你不能直接呼叫這個類的物件,它只供其他類繼承.
- 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);
// 在此定義傳入儲存過程的引數,如果沒有引數可以省略 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");
// 等待執行完成...
// 等待時長為 <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 有三類:
- ReaderQueryCallbackResult userData;
適用於有返回recordset的語句,可以通過userData.resultDataReader獲得對返回的recordset的訪問。 - NonQueryCallbackResult userData
適用於像UPDATE這種沒有返回內容的語句,可以使用userData.AffectedRows檢查執行的結果。 - 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 Server 資料庫同步配置SQLServer資料庫
- 【tronic】為ASP.NET封裝的SQL資料庫訪問類ASP.NET封裝SQL資料庫
- 如何封裝資料庫操作封裝資料庫
- 一個可用與資料庫SQL封裝的指令碼語言--TCL資料庫SQL封裝指令碼
- 關於資料庫操作的封裝程式碼資料庫封裝
- Vuex結合Axios非同步請求資料的封裝VueiOS非同步封裝
- SQL資料同步SQL
- 封裝JDBC—非框架開發必備的封裝類封裝JDBC框架
- 初探資料庫通用程式碼庫的封裝(C#版)資料庫封裝C#
- Android 封裝AsyncTask操作Sqlite資料庫Android封裝SQLite資料庫
- 使用元件封裝資料庫操作(二) (轉)元件封裝資料庫
- 使用元件封裝資料庫操作(一) (轉)元件封裝資料庫
- 【SQL】Oracle資料庫通過job定期重建同步表資料SQLOracle資料庫
- struts的資料封裝部分封裝
- jsp呼叫javabean封裝資料庫的問題,急!JSJavaBean封裝資料庫
- jsp呼叫javabean,javabean封裝資料庫的問題JSJavaBean封裝資料庫
- 資料庫同步資料庫
- Android資料庫ContentProvider封裝原理Android資料庫IDE封裝
- C# SQLite資料庫 訪問封裝類C#SQLite資料庫封裝
- 非關係型資料庫(NOSQL)和關係型資料庫(SQL)區別詳解資料庫SQL
- FMDB 二次封裝工具類,讓你快速學會封裝,整合資料庫封裝資料庫
- 封裝ADO訪問資料庫的兩個類 (轉)封裝資料庫
- Struts2的資料封裝封裝
- Linux 非圖形介面安裝oracle資料庫LinuxOracle資料庫
- 資料庫同步方案資料庫
- WireShark資料包分析資料封裝封裝
- Android 原生 SQLite 資料庫的一次封裝實踐AndroidSQLite資料庫封裝
- 詳解JDBC資料庫連結及相關方法的封裝JDBC資料庫封裝
- 如何徹底的解除安裝sql server資料庫SQLServer資料庫
- SQL Server資料庫中轉儲裝置的分析SQLServer資料庫
- fetch資料請求的封裝封裝
- 不同Oracle資料庫之間的資料同步Oracle資料庫
- Oracl資料庫+PL/SQL安裝與配置資料庫SQL
- Python量化資料倉儲搭建3:資料落庫程式碼封裝Python封裝
- Arduino 封裝庫UI封裝
- SQL與NoSQL(關係型與非關係型)資料庫的區別SQL資料庫
- 關於高效捕獲資料庫非繫結變數的SQL語句資料庫變數SQL
- SQL資料庫SQL資料庫