網頁

2012年10月1日 星期一

以SqlTransaction搭配具名參數實作轉帳流程

一般而言,,簡略的轉帳流程大致可分為幾個部分:
1. 查詢帳戶餘額是否大於要轉帳的金額,如餘額足夠付款則進行下一個動作。
2. 轉出付款者所輸入的金額至收款者帳戶,付款者帳戶餘額 = 原始餘額扣除轉出金額。
3. 轉入付款者所輸入的金額至收款者帳戶,收款者帳戶餘額 = 原始餘額加上轉入金額。
4. 如上述動作皆完成,則通知付款者轉帳成功;
如未成功則不更動收(付)款者帳戶餘額,並通知付款者轉帳失敗。

簡而言之,即是將付款者帳戶餘額扣掉輸入之金額後再將收款者帳戶餘額加上輸入之金額。

而要如何利用SqlTransaction來做出轉帳功能呢?
其實很簡單,照著上述流程走就行了!
只是筆者建議作新增、修改、刪除皆使用具名參數輸入資料,以確保安全。

轉帳流程範例程式碼如下:


using.System.Data.SqlClient;//引用SqlClient以進行轉帳
using System.Data;//引用Data以進行餘額查詢
.
. 
.
.
//----------------------Button1_Click
 //宣告使用的物件
        SqlConnection connect = new SqlConnection
            ("server = localhost; uid = xxxx; pwd = xxxx; database = transaction;");
        SqlDataReader dr;
        //inquirecmd為查詢餘額,transoutcmd為轉帳出戶金額, transincmd為轉帳入戶金額;
        SqlCommand inquirecmd,transoutcmd, transincmd;
         //開啟連線

        connect.Open();
        //宣告交易物件並開始資料庫交易
        SqlTransaction tran = connect.BeginTransaction();
        try
        {
            
            //執行餘額查詢,並限制付款方帳戶餘額須大於轉出金額才可進行轉帳
            inquirecmd = new SqlCommand
            ("SELECT @payuser FROM account WHERE user = @payuser AND balance >= @money",
 connect);
            //定義餘額查詢中具名參數及給值
            inquirecmd.Parameters.Add("@payuser", SqlDbType.NVarChar).Value = payuserBox.Text;
            inquirecmd.Parameters.Add("@money", SqlDbType.NVarChar).Value = int.Parse(moneyBox.Text);
            inquirecmd.ExecuteNonQuery();
            //將查詢結果帶入dr
            dr = inquirecmd.ExecuteReader();
            //假如餘額大於轉出金額則進行轉帳,小於轉出金額則中斷轉帳流程並提醒使用者餘額不足
            if (dr.Read())
            {
                 dr.Close();
                //扣除付款方餘額,請注意connect後方加入了tran,表示此指令列為轉帳流程之一
                transoutcmd = new SqlCommand
                    ("UPDATE account SET balance = balance - @money WHERE user = @payuser", connect,
 tran);
                //增加付款方餘額,請注意connect後方加入了tran,表示此指令列為轉帳流程之一
                transincmd = new SqlCommand
                    ("UPDATE account SET balance = balance + @money WHERE user = @getuser", connect, 
tran);
                //定義付款方扣款指令中具名參數及給值
                transoutcmd.Parameters.Add("@money", SqlDbType.Int).Value = int.Parse(moneyBox.Text);
                transoutcmd.Parameters.Add("@payuser", SqlDbType.NVarChar).Value = payuserBox.Text;
                //定義收款方指令中具名參數及給值
                transincmd.Parameters.Add("@money", SqlDbType.Int).Value = int.Parse(moneyBox.Text);
                transincmd.Parameters.Add("@getuser", SqlDbType.NVarChar).Value = getuserBox.Text;
                //執行轉帳流程,如轉出與轉入流程皆完成則交易成功
                transoutcmd.ExecuteNonQuery();
                transincmd.ExecuteNonQuery();
                //交易成功,顯式交易成功訊息及交易金額
                tran.Commit();
                Response.Write("交易成功,交易金額為" + moneyBox.Text);
            }
            else
            {
                //查詢結果餘額不足,提醒消費者並且不進行轉帳流程
                throw new Exception("餘額不足");
            }
        }
        catch (Exception ex)
        {
            //交易失敗,將交易資料復原至交易開始時,並通知使用者交易失敗
            tran.Rollback();
            Response.Write("交易失敗,請確認輸入之帳戶是否正確");
        }

            connect.Close();


2012年9月28日 星期五

以預存程序做資料庫查詢

筆者曾在以具名參數實做資料庫新增修改刪除 文章中介紹了具名參數,及其應用的方法......

固然使用具名參數可以減低SQL Injection(SQL 隱碼)的風險,但要加強存取資料庫時的安全性,可以改用預存程序存取資料。
雖然微軟官方網站建立預存程序已經說明了如何使用SQL SERVER的預存程序,但SQL SERVER和MySQL的預存程序語法有些微差異,容易造成混淆,因此筆者將兩種資料庫預存程序列出語法作比較。

SQL SERVER版本:
//指定使用bookstore資料庫
USE bookstore;
GO

//建立select_books預存程序
CREATE PROCEDURE select_books

//定義傳入的具名參數名稱與資料型別,SQL SERVER以@辨別具名參數
@booksname varchar(50), 
 @booksprice int(10) 

//SQL SERVER以AS開始下SQL指令,以GO做結尾不需加分號(;) 
AS

//下SQL指令
SELECT booksname, booksprice 
 FROM books 
 WHERE booksname = @booksname AND booksprice = @booksprice 
GO


MySQL版本:
//MySQL預存程序建立在各資料庫內,因此無須宣告將使用的資料庫

//由root登入localhost建立select_books預存程序(MySQl為TCP/IP登入驗證) 
CREATE DEFINER=`root`@`localhost` PROCEDURE `select_books`

//定義傳入具名參數名稱的具名參數資料型別並用()區隔,
//MySQL具名參數中不可使用@,?等特殊符號,筆者使用相近的字詞作為具名參數名稱
(
p_booksname varchar(50),
 p_booksprice int(10) 
)

//MySQL以BEGIN開始下SQL指令,以END作結尾需加分號(;)
BEGIN 

//下SQL指令
SELECT booksname, booksprice
FROM books
WHERE booksname = p_booksname AND booksprice = p_booksprice;
END
從上面兩個範例看得出來,語法其實大同小異,但是差異頗多,在使用上要多加注意,不要用錯

2012年9月26日 星期三

以ADO.NET實體模型搭配EntityDataSource存取資料庫


今天保哥教了我一招 我才知道-原來ADO.NET實體資料模型也可以搭配EntityDataSource在WEBFORM上使用
步驟很簡單:
1. 建立一個ADO.NET實體模型
2.建立一個EntityDataSource
3. 設定EntityDataSource

接下來就能透過EntityDataSource來存取資料庫了!

EntityDataSource使用方式與SqlDataSource大同小異,都是透過設定精靈完成資料存取。
但兩者差異在於,後端資料庫不是搭配SQL SERVER時,透過SqlDataSource來存取資料庫將會產生語法的問題......
原因在於,SQL SERVER執行SQL指令時可以加入中括號而不影響結果,但如果資料庫使用MySQL卻會造成SERVER讀取指令錯誤無法執行。

筆者的資料庫系統就是使用MySQL,使用SqlDataSource存取資料時都需要自行指定SELECT,INSERT,UPDATE,DELETE指令,設定上也較複雜......
所以,筆者都是習慣寫程式碼 : 建立連線字串->建立連線->存取資料->將資料放到相對應表格 。但是這樣在作網站時會浪費很多時間在建立資料連接與存取上......

如果使用EntityDataSources呢?
很簡單,設定精靈設定完之後就能直接存取資料庫了,不用因為資料庫語法上的差異再做其他設定,甚至寫程式碼存取資料。

針對非SQL SERVER的開發環境EntityDataSource的確比較方便易用,但如果開發環境都是微軟自家產品時差別就不囉!(或許有,但筆者不確定)。

注:使用EntityDataSource時須注意,其所使用的SQL指令並非T-SQL,而是Entity SQL,使用上會有些微差異,範例如下:

account表中有四個欄位{id,pwd,mail,address},假設筆者要查詢account中所有欄位的資料-大家應該會用 * 來做查詢,但在Entity SQL中,例1指令將會發生錯誤,例2才可SELECT到資料
1. (X)SELECT * FROM account WHERE id = 'xyz';
2. (O)SELECT id,pwd,mail,address FROM account WHERE id  = 'xyz';
Entity SQL不可使用 "*"

微軟產品的電子書指南

今天在找學習資源時,發現MICROSOFT GULF COMMUNITY BLOG提供微軟相關產品的電子書指南,因此放上來與大家分享。
Large collection of Free Microsoft eBooks
雖然指南都是英文,但是並沒有很艱深的單字,因此請放心閱讀。

當然其中也有MVC4的電子書指南,想要好好了解MVC的話可以閱讀電子書,同時也可以從範例中做練習,對學習MVC會有很大幫助。

MVC4電子檔載點如下:Intro to ASP.NET MVC 4 with Visual Studio - Beta

另外,也提供一個MVC自學的網站ASP.NET MVC 自學日誌

以具名參數實做資料庫新增修改刪除


在C#中常會使用文字方塊來輸入資料到資料庫,但是這樣將會有SQL Injection(SQL隱碼)的危險。

當然,可以使用字元替換(TextBox1..Text.Replace)的方法,避免無聊人士胡亂輸入字元,
而筆者則是建議使用具名參數來達成相同的效果,如此一來可以避免掉許多的風險。

使用具名參數的流程可以分為五個部分:

1. 新增使用資料庫以及具名參數所需之Namespace(命名空間)
2. 宣告需使用的物件-SqlConnection及SqlCommand並給予初值
3. 開啟資料庫連線,並建立具名參數所使用的參數名稱,資料型別,並指定輸入的TextBox
4. 執行SQL指令
5. 關閉資料連線

以下為範例程式碼:
//1. 新增使用資料庫以及具名參數所需之Namespace(命名空間)
using System.Data.SqlClient;//引用資料庫所需的資料
using System.Data;//引用具名參數所需的資料
.
.
.
.
//-----------------------------------------------------------------

//2. 宣告連線並指定使用的資料庫名稱及登入帳號密碼
SqlConnection connect = new SqlConnection("server = localhost; uid = xxxx; pwd = yyyy; database = test;");

        //3.1 開啟資料連線
        connect.Open();

//----------------------------------------新增

//此SQL指令為新增table2的資料,資料表內id欄位所輸入的字串從TextBox而來
SqlCommand cmdInsert = new SqlCommand("INSERT INTO table2(id,name,content,date) VALUES (@id,@name,@content,@date)", connect);

//3.2 建立具名參數的參數名稱,並指定參數所要帶入的資料來自TextBox
cmdInsert.Parameters.Add("@id",SqlDbType.NVarChar).Value = TextBox1.Text;
cmdInsert.Parameters.Add("@name",SqlDbType.NVarChar).Value = TextBox2.Text;
cmdInsert.Parameters.Add("@content",SqlDbType.NVarChar).Value = TextBox3.Text;
cmdInsert.Parameters.Add("@date",SqlDbType.NVarChar).Value = TextBox4.Text;


        //4. 開啟資料庫執行SQL語法
        cmdInsert.ExecuteNonQuery();

//----------------------------------------修改

//此SQL指令為更新table2的資料,資料表內id欄位所輸入的字串從TextBox而來
SqlCommand cmdUpdate = new SqlCommand("UPDATE table2 SET name = @name, content = @content, date = @date WHERE id = @id",connect);

//3.2 建立具名參數的參數名稱,並指定參數所要帶入的資料來自TextBox
cmdUpdate.Parameters.Add("@id",SqlDbType.NVarChar).Value = TextBox1.Text;
cmdUpdate.Parameters.Add("@name",SqlDbType.NVarChar).Value = TextBox2.Text;
cmdUpdate.Parameters.Add("@content",SqlDbType.NVarChar).Value = TextBox3.Text;
cmdUpdate.Parameters.Add("@date",SqlDbType.NVarChar).Value = TextBox4.Text;


        //4. 開啟資料庫執行SQL語法
        cmdUpdate.ExecuteNonQuery();

//---------------------------------------刪除

//此SQL指令為刪除table2的某筆資料,TextBox1.Text輸入的字串為刪除依據
SqlCommand cmdDelete = new SqlCommand("DELETE FROM table2 WHERE id = @id",connect);

//3.2 建立具名參數的參數名稱,並指定參數所要帶入的資料來自TextBox
        cmdDelete.Parameters.Add("@id",SqlDbType.NVarChar).Value = TextBox1.Text;

        //4. 開啟資料庫執行SQL語法
        cmdDelete.ExecuteNonQuery();

        //5. 關閉資料連線
        connect.Close();