1. 程式人生 > >ADO.NET操作資料庫(四)

ADO.NET操作資料庫(四)

主要內容: 連線查詢、左表、右表、內連線、外連線、笛卡爾積、on子句資料篩選、自連線、儲存過程、連線查詢、模糊查詢、檢視、T-SQL程式設計、全域性變數、區域性變數、事務、系統儲存過程、使用者自定義儲存過程、ado.net呼叫儲存過程、觸發器。

快捷鍵:格式化快捷鍵 ctrl+K,Y

詳細內容:

1、連線查詢

連線查詢:關鍵是看兩張表的主外來鍵關係
內連線:inner join:多表內連線,無論連線幾張表,每次執行都是兩張表進行連線。在多張表中進行查詢,結果在一張表中。
外連線:左外連線(left outer join) 有外連線(right outer join)。左表和右表。
連線查詢的基本執行步驟:1)笛卡爾積,好好理解這個概念2)應用on篩連線選器3)新增外部行,到此from執行完畢。
當使用連線查詢的時候,如果同時要指定查詢的條件,那麼一定要使用where子句,不要直接在on條件後面跟and來編寫其他查詢條件。
內連線執行的過程是:先進行笛卡爾積,然後進行on篩選條件,如果是外連線,就新增對應表中的資料。新增外部行。

外連線的執行步驟:
1)構建笛卡爾積;
2)根據left join on條件進行行資料的篩選;
3)因為是外連線,所以要顯示左表中的所有記錄,所以接下來要進行"新增外部行"。把左表中哪些沒有篩選出來的哪些資料再新增到當前的查詢結果集中

自連線:
外連線:是指多張表進行連線。
什麼叫自連線:自己和自己連線,把表中的所有列都複製一份。
select * from TblArea as t1,TblArea as t2;從這兩張表中進行查詢
對錶起別名特別有用;對列起別名,漢字好理解。
關鍵是對邏輯關係的理解,弄清楚了這個,程式碼很好寫的。

查詢所有學生的姓名、年齡以及所在班級:
分析:需要從學生表和班級表中進行查詢。需在在多張表中進行查詢,這就需要用到連線。
select t1.tsName,t1.tsAge ,t2.tsClass FROM Student as ti inner join Class as t2 on t1.tsClass=t2.tClassId 這是連線條件?

查詢年齡超過20歲的所有學生的姓名、年齡以及所在班級:
select t1.tsName,t1.tsAge ,t2.tsClass FROM Student as ti inner join Class as t2 on t1.tsClass=t2.tClassId where tsAge > 20

請查詢出所有同學的姓名,年齡,英語成績,數學成績:(但是某些學生沒有參加考試)
如何理解左外連線:如果找不到對應的值,則值為空。left join關鍵字兩邊都有一張表,把左邊表中的所有資料都顯示出來;
同時右邊的表,如果存在就顯示出來,如果找不到匹配的資料,就顯示為null。
出現在left join左側的表就是左表,出現在left join右側的表就是右表。
內連線是隻顯示哪些兩張表中都匹配的資料。

USE MyFirstData;
SELECT  *
FROM    dbo.Teacher;

INSERT  INTO Teacher
VALUES  ( N'趙靈兒', 1, 18, GETDATE(), N'中國臺灣省', N'[email protected]', 15555 );

DELETE  FROM Teacher
WHERE   TeaID = 8;

SELECT  *
FROM    Teacher
WHERE   TeaName = '陳詩音';

USE MyFirstData;
INSERT  INTO PhotoType
VALUES  ( 4, '家人' );
SELECT  *
FROM    PhotoType;
SELECT  *
FROM    PhoneNum;

INSERT  INTO PhoneNum
VALUES  ( 5, 3, '大喬', 130241123, 700004 );

內連線:

--內連線查詢的使用   INNER JOIN ...on... (連線條件   笛卡爾積加上篩選條件)
--內連線:把多張表的資料在一個表中顯示
--為什麼使用表名.列名,為了防止兩張表中存在重名的列名。查詢的時候,如果表中有重複的列名,應該在列名前面加上表名。
--查詢語句的執行順序:select* from PhoneNum;執行順序是:先執行from子句,在執行select子句
SELECT  *
FROM    dbo.PhoneNum
        INNER JOIN dbo.PhotoType ON PhoneNum.pTypeId = PhotoType.ptId;
SELECT  PhoneNum.pId ,
        PhoneNum.pName ,
        PhoneNum.pCellPhone ,
        pHomePhone ,
        ptName
FROM    dbo.PhoneNum
        INNER JOIN dbo.PhotoType ON PhoneNum.pTypeId = PhotoType.ptId;

--查詢的時候,為表起一個別名。
SELECT  pn.pId ,
        pn.pName ,
        pn.pCellPhone ,
        pHomePhone ,
        ptName
FROM    dbo.PhoneNum AS pn
        INNER JOIN dbo.PhotoType ON pn.pTypeId = PhotoType.ptId;

--使用帶引數的SQL語句向資料庫中插入空值
SELECT  *
FROM    Teacher;

2、多條件查詢(模糊查詢)

模糊查詢(多條件查詢):使用帶引數的SQL語句.比如根據作者查詢資料,根據年份查詢書籍。
使用 like關鍵字進行模糊查詢。動態拼接sql語句,看看使用者輸入了哪些查詢條件或者前臺傳遞過來哪些查詢關鍵字。
1)如果使用者沒有輸入任何條件,那就直接查詢出所有的記錄。2)如果使用者輸入了條件,則根據使用者輸入的條件動態拼接sql語句。
對應專案開發中的搜尋模組。

        static void Main(string[] args)
        {
            //多條件查詢

            //查詢書籍:Books表,列:Author作者 BooKName書名 Pub出版社
            StringBuilder sb = new StringBuilder("select * from Books ");
            //建立一個list集合,用來儲存查詢條件
            List<string> where = new List<string>();
            where.Add(" BooKName like @bookName");
            where.Add(" Author like @author");
            where.Add(" Pub like @pub");
            //拼接sql語句
            //如果where集合當中的記錄條數大於0,證明使用者輸入了條件
            if (where.Count > 0)
            {
                sb.Append("where ");
                //把集合中的查詢條件通過and連線起來
                string configtion = string.Join(" and ", where);
                sb.Append(configtion);
            }

            //方法二:
            List<SqlParameter> listParameter = new List<SqlParameter>();
            listParameter.Add(new SqlParameter("@bkName",System.Data.SqlDbType.NVarChar,100));
            //sql語句中所有的引數
            SqlParameter[] pms= listParameter.ToArray();
   
            //帶引數的sql語句
            //sb.Append(" BooKName like @bookName");
            //sb.Append(" Author like @author");
            //sb.Append(" Pub like @pub");
        }

3、檢視

檢視概述:主要用於封裝複雜查詢的。
注意這四個問題.: 如何建立檢視?如何刪除檢視?檢視的作用是什麼?什麼時候使用檢視? 

資料是怎麼儲存的?
檢視是一張虛擬表,它表示一張表的部分資料或者多張表的綜合資料,其結構和資料是建立在對錶的查詢基礎上。
檢視在操作上和資料表沒有什麼區別,但是兩則的差異是其本質不同:資料表是實際儲存記錄的地方,然而檢視並不儲存任何記錄。
相同的資料表,根據不同使用者的不同需求,可以建立不同的檢視(不同的查詢語句);
檢視的目的是方便查詢,所以一般情況下不能對檢視進行增刪改。
優點:篩選表中的行/降低資料庫的複雜程度(呼叫檢視,直接一句話進行查詢),防止未經許可的使用者訪問敏感資料(看不到表名和表字段)。
檢視其實是對複雜的查詢語句的封裝。直接呼叫檢視就可以進行查詢了。
View 關鍵字的封裝
create View vw_TblArea as .... create View 檢視名稱 as ....
select * from vw_TblArea; 檢視相當於別名
SQL Server下有檢視結點
檢視只能寫(儲存)查詢語句,而儲存過程就像方法一樣,特別靈活。可以使用儲存過程替代檢視。
檢視中不能有引數。
如果檢視中的查詢語句中包含了重名的列(多個表之間)必須給列起別名。
普通檢視並不儲存資料(虛擬表),訪問的是真實表中的資料。


4、T-SQL程式設計

T-SQL程式設計:(先宣告在賦值)
宣告區域性變數:DECLARE @變數名 資料型別 [=預設值]
例如:declare @name varchar(20); declare @age int;

賦值
set @變數名=值; set用於普通的賦值
select @變數名=值; 用於從表中查詢資料並賦值,可以一次給多個變數賦值。
例如:set @name=‘張三’; set @id=1; select @name=sName FROM Student where sId=@id

輸出變數的值:(列印變數的值)
1)select以表格的方式輸出,可以同時輸出多個變數。select @name,@id
2)print以文字的方式輸出,一次只能輸出一個變數的值。print @name;print @id
print @name,@id; 這樣寫是錯誤的。

變數分類
1)區域性變數:區域性變數必須以標記@作為字首,比如 @Age int;區域性變數必須先宣告,在賦值。
2)全域性變數:全域性變數的定義必須以@@作為字首,如 @@version 全域性變數有系統維護和定義,我們只能讀取,不能修改全域性變數的值。
全域性變數又稱為系統變數。兩個@@符號開頭的一半都是系統變數。

SQL中常用的全域性變數:
@@version:SQL Server的版本資訊
@@SERVERNAME:本地計算機的名稱
@@TRANSCOUNT:當前連線開啟的事務數。
@@rowcount:受上一個SQL語句影響的行數。
@@max_connections:可以建立的同時連線的最大數目。
@@language:當前使用的語言的名稱。

PRINT @@VERSION;
PRINT @@version;
PRINT @@SERVERNAME;
PRINT @@TRANCOUNT;
PRINT @@rowcount;
PRINT @@max_connections;
PRINT @@language;

在sql裡面怎麼寫迴圈
1)sql裡面是沒有for迴圈的,使用while迴圈。如何使用while迴圈輸出100句hello?
2)在迴圈結構裡面可以使用break,continue

在sql裡面怎麼寫if-else
IF(條件表示式)
begin 相當於C#裡面的{
end 相當於C#裡面的}
else
begin 相當於C#裡面的{
end 相當於C#裡面的}
--宣告變數
DECLARE @name NVARCHAR(10);
DECLARE @age INT;

--賦值變數
SET @name = '陳如水';
SET @age = '12';
SELECT  @name = '陳如水';

--輸出,用逗號分隔的都是列名,要顯示兩列。
--必須全部選中才能執行,因為如果不賦值,直接取值,就會報錯。
SELECT  '姓名' ,
        @name;
SELECT  '年齡' ,
        @age;

--可以使用一句話來宣告多個變數,使用逗號分隔
DECLARE @UserName NVARCHAR(20) ,
    @UserAge INT;

--在sql使用while迴圈,BEGIN和end相當於開始和結束大括號
--如何改變迴圈變數的值,其實就相當於設定值
DECLARE @i INT= 1;
--宣告變數的同時進行賦值。
WHILE ( @i < 100 )
    BEGIN
        PRINT 'hello';
        SET @i = @i + 1;
    END;

--計算1到100之間的整數和
--如果 @sum INT 不賦初值,結果就是null,所以必須賦初值。
DECLARE @sum INT= 0 ,
    @j INT = 1;
WHILE ( @j <= 100 )
    BEGIN
	--記得這裡是為變數賦值
        SET @sum = @sum + @j;
        SET @j = @j + 1;
    END;
PRINT @sum;

--如何實現 if-esle結構
DECLARE @a INT = 10;
IF @a > 10
    BEGIN	
        PRINT '@a大於10';
    END;
ELSE
    IF @a > 5
        BEGIN
            PRINT '@a大於5';
        END;
    ELSE
        BEGIN
            PRINT '@a大於0';
        END;

--計算1到100之間所有奇數和
--需要一個求和變數和迴圈變數
DECLARE @k INT= 1 ,
    @sums INT= 0;
WHILE ( @k <= 100 )
    BEGIN
	--如果除2,能夠除盡
        IF @k % 2 <> 0
            BEGIN 
                SET @sums = @sums + @k;
            END;
			SET @k=@k+1;
    END;
PRINT @sums;

--如果列印的值為0,表示上一條sql語句執行沒有出錯。
PRINT @@ERROR;

5、事務

為什麼需要事務?
指訪問並可能更新資料庫中各種資料項的一個程式執行單元,也就是說多個sql語句,必須作為一個整體執行。
這些sql語句作為一個整體一起向系統提交,要麼都執行,要麼都不執行。
語法:
開始事務 begin transaction
提交事務 commit transaction
回滾事務 rollback transaction
判斷某條語句是否出錯,使用全域性變數@@error

事務分類:
1)自動提交事務:資料庫自動來做,不需要使用者考慮任何事情。insert into Teacher values();預設情況下sql使用的是自動提交事務。
2)隱式事務:set implicit transaction on | off 每次執行一條sql語句的時候,資料庫會自動幫助我們開啟一個事務,但是需要我們手動提交事務或者回滾事務。不手動提交或者回滾,sql語句執行不能成功,顯示一直正在執行中。
3)顯示事務:需要開發人員手動開啟、回滾、提交事務。1)如果想要把一個查詢結果賦值給變數,則必須用()括起來

事務的幾個特性:
原子性、一致性、隔離性(多個事務之間)、永續性:事務完成後,它對於系統的影響是永久性的,該修改即使出現系統故障也將一直保持。

在轉賬之前最好通過if-else判斷,不要讓程式發生異常或者錯誤。

如何保證兩條語句要麼同時執行成功要麼同時執行失敗?使用事務來保證。
把要執行的程式碼放置到一個事務裡面;如果其中的某一行行執行出錯,那就讓程式碼回滾。
1)開啟一個事務 begin transaction
2)declare @sum int;每執行一條sql語句,就記上這樣一句話:set @sum=@sum+@@error。只要有人惡化一條sql語句出錯,sum的值就不是0。
3)if @sum<>0 說明其中的程式碼執行出錯。
begin
說明其中的程式碼執行出錯。
直接進行回滾
rollback;
end
else
begin
//如果程式執行沒有出錯,就進行提交程式碼。
commit
end

6、儲存過程

1)其實就是在資料庫中執行方法(函式),它和c#裡面的方法一樣,由儲存過程名/儲存過程引數組成/可以有返回結果。
2)if--else、while、變數、insert等,都可以在儲存過程中使用。
3)使用儲存過程的優點:執行速度快,在資料庫中儲存的儲存過程語句都是編譯過的。允許模組化程式設計,類似方法的複用。提高系統安全性,防止SQL注入;減少網路流通量,只要傳輸儲存過程的名字。
4)系統儲存過程:由系統定義,存放在master資料庫中。名稱以“sp_”開頭或者“xp_”開頭,自定義的儲存過程可以以usp_開頭。
5)自定義的儲存過程:由使用者在自己的資料庫中建立的儲存過程usp。
6)使用儲存過程也有缺點:如果在伺服器端寫了過多的儲存過程,當用戶訪問量比較大時,所有的壓力都有資料庫來承擔了。把業務邏輯也寫到儲存過程裡面了,業務邏輯應在在C#程式碼裡面的。一旦伺服器出現瓶頸,優化起來比較困難。注意控制使用儲存過程的數量。

儲存過程和函式在本質上沒有區別。函式只能返回一個變數,而儲存過程可以返回多個。一般來說儲存過程實現額功能要複雜一些,而函式實現的功能針對性比較強。
把儲存過程當做一個獨立的部分來執行。
把常用的程式碼封裝到儲存過程裡面,直接呼叫儲存過程名就好了。
usp_開頭一般是使用者儲存過程。
儲存過程內部就是一段sql程式碼。
order by 索引 :按照第幾列進行排序。

系統儲存過程:直接可以拿來用。凡是以sp開頭的一般都是儲存過程。
sp_databases:列出伺服器上所有的資料庫。
sp_helpdb:報告有關指定資料庫或所有資料庫的資訊。
sp_renamedb:更改資料庫的名稱。
sp_tables:返回當前環境下所有的資料表(use關鍵字的使用)。
sp_columns:返回某個表列的資訊。
sp_help:檢視某個表的所有資訊。
sp_helpconstraint:檢視某個表的約束。
sp_helpindex:檢視某個表的索引。
sp_stored_procedures:列出當前環境下所有的儲存過程。
sp_password:新增或修改登入賬戶的密碼。
sp_helptext:顯示預設值、未加密的儲存過程、使用者定義的儲存過程、觸發器或試圖的實際文字。(獲取儲存過程的原始碼)

如何執行儲存過程?
如果是系統儲存過程,直接選中儲存過程的名字,可以直接執行;
對於使用者自定義的儲存過程,儲存過程名之前要加exec:exec 儲存過程名。

如何建立自定義的儲存過程?
定義儲存過程的語法:
create proc[edure] 儲存過程名
@引數1 資料型別=預設值 output,
@引數n 資料型別=預設值 output
as
sql語句
引數說明:引數可選,引數分為輸入引數、輸出引數,輸入引數允許有預設值。

1)儲存過程的分類: 系統儲存過程與使用者自定義儲存過程
2)執行儲存過程的關鍵字: exec 儲存過程名 [引數] ;建立儲存過程的語句:CREATE PROC usp_say_he。
3)使用者自定義儲存過程一般以usp開頭,見名知意,要使用關鍵字表示,這個儲存過程是操作哪張表的?
4)儲存過程英語: procedure
5)建立執行過程----->執行儲存過程(呼叫儲存過程),這是兩個過程,千萬別忘了。
6)自定義儲存過程的標準語法:create proc 儲存過程名 as begin 具體的業務邏輯 end。
7)如何刪除儲存過程: drop proc 儲存過程名。
8)如何檢視某個儲存過程的原始碼? exec sp_helptext 'sp_databases';

1)如何建立帶引數的儲存過程;
2)如何設定儲存過程引數的預設值?
3)如何呼叫帶引數的儲存過程?


--儲存過程的分類: 系統儲存過程與使用者自定義儲存過程
--執行儲存過程的關鍵字: EXEC;建立儲存過程的語句:CREATE PROC usp_say_he。使用者自定義儲存過程一般以use開頭
--儲存過程英語: procedure

EXEC sp_databases;
USE Activity
GO

EXEC sp_tables;
go


--返回某張表下的所有列

--自定義儲存過程(無引數與無返回值)
CREATE PROC usp_say_he
AS BEGIN
	SELECT 1+1;
END


--如何執行儲存過程
EXEC usp_say_he

--如何刪除儲存過程
DROP PROC dbo.usp_say_he;

go

--子定義儲存過程
CREATE PROC usp_select_all
AS
BEGIN
	SELECT  * FROM Teacher;
END

EXEC usp_select_all;

--建立帶引數的儲存過程
CREATE PROC usp_add_mun
@n1 int,
@n2 int
AS
BEGIN
 SELECT @n1+@n2;
END

EXEC usp_add_mun 100,500;

--建立兩個帶引數的儲存過程
CREATE PROC usp_select_Teacher
@TeaGender bit,
@TeaAge INT
AS
BEGIN
SELECT * FROM Teacher  WHERE TeaGender>=@TeaGender AND TeaAge >=@TeaAge;
END

--把引數寫全也是可以的。
EXEC usp_select_Teacher 1,25;
EXEC usp_select_Teacher @TeaGender=1,@TeaAge=25;

--設定儲存過程引數的預設值(如果給定新值,就是用新值進行計算,沒有就是用預設值進行計算)
CREATE PROC usp_select_add
@n1 int,
@n2 INT=10
AS
BEGIN
	SELECT  @n1+@n2;
END

EXEC usp_select_add 20,20;

--帶輸出引數的儲存過程(使用輸出引數兩個地方都會用到,設定輸出引數的地方,列印輸出引數的地方)
--當在儲存過程中需要返回多個值的時候,就可以使用輸出引數來返回這些值。
--查詢年齡大於給定年齡的所有記錄
CREATE PROC usp_show_Teacher
@TeaAge int,
@recordcount int OUTPUT --這個值用於向外輸出
AS
BEGIN
	SELECT * FROM Teacher WHERE TeaAge> @TeaAge 
	--把查詢到的值賦值給變數(記錄的條數)
	SELECT @recordcount=(SELECT COUNT(*) FROM Teacher WHERE TeaAge> @TeaAge);
END

--呼叫儲存過程
--呼叫帶有輸出引數的儲存過程的時候,需要定義變數,將變數傳遞給輸出引數,在儲存過程中使用的輸出引數,其實就是你傳遞進來的變數值
--宣告一個變數
DECLARE @re INT
EXEC usp_show_Teacher 22,@recordcount=@re output;
PRINT @re;

SELECT * FROM Teacher;


--使用儲存過程來實現分頁查詢
--對Teacher實現分頁查詢,使用者傳遞進來兩個,傳遞給使用者兩個。
--對每一列都進行編號
--把分頁查詢的程式碼都封裝到一個儲存過程裡面
--在建立儲存過程的語句前面新增一句話go,就不會報錯了。
go
CREATE PROC usp_getTeacherByPage
@pagesize int=3,--每頁記錄條數,每頁預設顯示三條記錄
@pageindex INT=1, --要檢視第幾頁的資料
@recodecount int OUTPUT,  --總的記錄的條數
@pagecount int OUTPUT --總的頁數
AS 
BEGIN
	--編寫查詢語句,把使用者所要的資料查詢出來,相當於是一哥新的表
	--使用between關鍵字應該注意:and左側的數比較小,and右側的數應該比較大。
	SELECT * FROM (	SELECT *,rn=ROW_NUMBER() OVER (ORDER BY TeaID ASC) 
	FROM Teacher) AS newTable WHERE newTable.rn BETWEEN (@pageindex-1)*@pagesize+1 AND @pageindex*@pagesize

	--計算總的記錄條數
	SET @recodecount=(SELECT COUNT(*) FROM Teacher);

	--計算總的頁數 ceiling向上取整數
	SET @pagecount =CEILING(@recodecount*1.0/@pagesize)
END

--分頁查詢,儲存過程的呼叫
DECLARE @rc INT;
DECLARE @pc INT
EXEC usp_getTeacherByPage @pagesize=4,@pageindex=2,@recodecount=@rc output,@pagecount=@pc output;
PRINT @rc
PRINT @pc

7、set與select賦值區別

declare @a int
set @a=(select count(*) from Teacher)
select @a=count(*) from Teacher
print @a;
2)print每次只能列印一個變數的值。
set @a=1;
select @a=1;
3)下面這種情況是有問題的
declare @i int;
set @i=(select tsage from Teacher)
print @i;
因為查詢結果不是一個數據,而是一列資料,所以這樣賦值會報錯。
當通過set為變數賦值的時候,如果查詢語句返回的不是單個值,那麼就會報錯。

下面這種寫法不會報錯的。
declare @i int;
select @i=(select tsage from Teacher)
print @i;
但是當通過select為變數賦值的時候,如果查詢語句返回的不止一個值,那麼會將最後一個結果賦值給該變數。
相對而言,set賦值更嚴謹一些,select賦值更靈活一點。

八、如何通過ado.net來呼叫儲存過程?
1、通過ado.net呼叫儲存過程與呼叫帶引數的sql語句的區別
1)把sql語句變成儲存過程名.
2)設定SqlCommand物件的CommandType為CommandType.StoredProcedure.
3)根據儲存過程的引數來設定輸出引數的Direction屬性.
4)如果有輸出引數,需要設定輸出引數的Direction屬性為Direction=ParameterDirection.Output.

2、如果是通通過呼叫Command物件的ExecuteReader()方法來執行該儲存過程的,
那麼想要獲取輸出引數,必須等到關閉reader物件後,才能獲取輸出引數的值。

3、為什麼執行儲存過程的時候,需要設定CommandType.StoredProcedure.設定此屬性,到底做了什麼事情?

4、同一個SqlParameter不能同時用於多個SDqlCommand物件,否則會報錯。

            int pagesize = 3;
            int pageindex = 2;
            int recodecount;
            int pagecount;


            //如何通過ado.net呼叫儲存過程
            string connStr = @"data source=172.16.20.1\dev; initial catalog =MyFirstData;user ID=gungnirreader;PASSWORD=itsme999";
            using (SqlConnection conn = new SqlConnection(connStr))
            {
                //1)把sql語句修改成儲存過程的名字
                string sql1 = "usp_getTeacherByPage";
                using (SqlCommand comm = new SqlCommand(sql1, conn))
                {
                    //2)告訴命令物件,現在執行的不是sql語句,而是儲存過程
                    comm.CommandType = System.Data.CommandType.StoredProcedure;
                    //增加引數:儲存過程中有四個引數,這裡就要增加四個引數
                    SqlParameter[] spm = new SqlParameter[] {
                       //引數名稱必須和儲存過程中的引數名稱一樣
                       new SqlParameter("@pagesize",System.Data.SqlDbType.Int) {Value=pagesize},
                       new SqlParameter("@pageindex",System.Data.SqlDbType.Int) { Value=pageindex},
                       //輸出引數不需要給它賦值,只需要取值就可以啦
                       //告訴命令物件,這兩個引數是輸出引數,使用列舉值進行標識
                       new SqlParameter("@recodecount",System.Data.SqlDbType.Int) { Direction=System.Data.ParameterDirection.Output},
                       new SqlParameter("@pagecount",System.Data.SqlDbType.Int){ Direction=System.Data.ParameterDirection.Output}
                    };
                    //把引數新增到命令物件中
                    comm.Parameters.AddRange(spm);
                }
            }


            //使用sqlDataAdapter來實現
            string sql = "usp_getTeacherByPage";
            //儲存過程名稱和連線字串
            using (SqlDataAdapter adapter = new SqlDataAdapter(sql, connStr))
            {
                //查詢結果是一張資料表
                DataTable dt = new DataTable();
                //告訴adapter執行的不是sql語句,而是儲存過程
                adapter.SelectCommand.CommandType = System.Data.CommandType.StoredProcedure;
                //增加引數:儲存過程中有四個引數,這裡就要增加四個引數
                //儲存過程中有幾個引數就新增幾個引數
                SqlParameter[] spm = new SqlParameter[] {
                       //引數名稱必須和儲存過程中的引數名稱一樣
                       new SqlParameter("@pagesize",System.Data.SqlDbType.Int) {Value=pagesize},
                       new SqlParameter("@pageindex",System.Data.SqlDbType.Int) { Value=pageindex},
                       //輸出引數不需要給它賦值,只需要取值就可以啦
                       //告訴命令物件,這兩個引數是輸出引數,使用列舉值進行標識
                       new SqlParameter("@recodecount",System.Data.SqlDbType.Int) { Direction=System.Data.ParameterDirection.Output},
                       new SqlParameter("@pagecount",System.Data.SqlDbType.Int){ Direction=System.Data.ParameterDirection.Output}
                    };

                adapter.SelectCommand.Parameters.AddRange(spm);
                //所有的查詢結果都在這張表裡面
                adapter.Fill(dt);

                //如何拿到輸出引數的值(獲取輸出引數)
                string a = spm[2].Value.ToString();
                string b = spm[3].Value.ToString();

                //總共只有三步,呼叫儲存過程。如果有輸出引數,必須儲存過程呼叫完畢後才能獲取輸出引數的值,必須設定引數的方向。
                //通過ado.net呼叫儲存過程與通過ado.net呼叫sql語句的區別。
                //把sqlhelper中新增可以呼叫儲存過程的方法

                //為什麼執行儲存過程的時候,需要設定CommandType.StoredProcedure?設定該屬性,到底做了什麼事情?
                //如果不想設定CommandType.StoredProcedure,直接在儲存過程名稱前新增exec即可
            }
        }

九、靜態類建構函式的特點:
static MyClass(){
}
1)要使用static關鍵字修飾。
2)只執行一次,在第一次使用靜態類之前執行一次。
3)靜態建構函式不過載。

十、把事務轉賬封裝到一個儲存過程裡,並通過ado.net呼叫該儲存過程

--案例:建立儲存過程使用事務實現轉賬
CREATE TABLE bank( cid INT identity(1,1) PRIMARY key,balance MONEY)
SELECT * FROM  bank;
DROP TABLE bank;
INSERT INTO dbo.bank VALUES( 930.00)
INSERT INTO dbo.bank VALUES( 80.00)
INSERT INTO dbo.bank VALUES( 100000.00)

GO

CREATE PROCEDURE usp_transfer
@from INT,
@to INT,
@balance MONEY, --轉賬金額
@result int OUTPUT --轉賬結果(1表示轉賬成功2表示轉賬失敗0表示餘額不足)
AS
BEGIN
	--判斷是否執行成功,進行提交事務或者回滾事務
	--判斷金額是否足夠轉賬
	DECLARE @money MONEY
    SELECT @money=balance FROM bank WHERE cid=@from;
	
	IF(@money-@balance>=10)
	--說明金額足夠轉賬,開始轉賬
	BEGIN
	--開啟事務
	BEGIN TRANSACTION;
	DECLARE @sum INT;
	--賬戶一扣錢,更新賬戶一,從哪個賬戶轉出
	UPDATE bank SET balance=balance-@balance WHERE cid=@from;
	--如果執行不出錯,@@ERROR結果為0,
	SET @sum=@sum+@@ERROR;
	--賬戶二加錢
	UPDATE bank SET balance=balance+@balance WHERE cid=@to;
	SET @sum=@sum+@@ERROR
	--判斷轉賬是否執行成功
	IF(@sum<>0)
	BEGIN
		SET @result=2;
		ROLLBACK TRANSACTION;--轉賬失敗
	END
	ELSE
    BEGIN
		SET @result=1;
		COMMIT TRANSACTION;--轉賬成功
	END
    END
	ELSE
    BEGIN
		SET @result=0;--表示餘額不足
    END

END

DECLARE @re int
EXEC dbo.usp_transfer @from = '1', -- char(4)
    @to = '2', -- char(4)
    @balance = 100, -- money
    @result = @re OUTPUT  -- int
PRINT @re;

DECLARE @resu int
EXEC dbo.usp_transfer @from = '1', -- char(4)
    @to = '2', -- char(4)
    @balance = 8000, -- money
    @result = @resu OUTPUT  -- int
PRINT @resu;
            SqlParameter[] pms = new SqlParameter[] {
                new SqlParameter("@from",SqlDbType.Int) {Value=1},
                new SqlParameter("@to",SqlDbType.Int) { Value =2},
                new SqlParameter("@balance",SqlDbType.Money) {Value=10 },
                new SqlParameter("@result",SqlDbType.Int) { Direction=ParameterDirection.Output } };

            SqlHelper.ExecuteNonQuery("usp_transfer", CommandType.StoredProcedure, pms);

            int rows = Convert.ToInt32(pms[3].Value);
            switch (rows)
            {
                case 1: Console.WriteLine("轉賬成功!"); break;
                case 2: Console.WriteLine("轉賬失敗!"); break;
                case 0: Console.WriteLine("餘額不足!"); break;

                default:
                    Console.WriteLine("沒有匹配的項");
                    break;
            }
            Console.ReadKey();

十一、如何通過儲存過程對錶實現增刪改查?

--使用儲存過程實現對錶Teacher的增刪改查
SELECT * FROM Teacher;

--使用儲存過程向表中插入資料  GETDATE() 獲取當前的時間
go
CREATE PROCEDURE usp_insert_Teacher
@TeaName NVARCHAR(20),
@TeaGender BIT,
@TeaAge INT,
@TeaAddress NVARCHAR(20),
@TeaEmail NVARCHAR(20),
@TeaSalary MONEY
AS
BEGIN
	INSERT INTO Teacher VALUES(@TeaName,@TeaGender,@TeaAge,@TeaAddress,@TeaEmail,@TeaSalary);
END;

DROP PROC usp_insert_Teacher;
ALTER TABLE Teacher DROP COLUMN TeaBirthdat;

--忘記宣告變量了,如果沒有輸出引數就不用宣告變數。
EXECUTE usp_insert_Teacher  @TeaName='江湖',@TeaGender=1,@TeaAge=2,@TeaAddress='人間',@TeaEmail='[email protected]',@TeaSalary=1111;

--使用儲存過程刪除表中的記錄(根據主鍵id)
GO
CREATE PROC usp_delete_Teacher
@TeaID INT
AS
BEGIN
	DELETE FROM Teacher WHERE TeaID=@TeaID
END

SELECT * FROM Teacher;
EXEC usp_delete_Teacher 16;

--使用儲存過程更新表中的記錄
GO
CREATE PROCEDURE usp_update_Teacher
@TeaID INT,
@TeaName NVARCHAR(20),
@TeaGender BIT
AS
BEGIN
	UPDATE Teacher SET TeaName=@TeaName,TeaGender=@TeaGender WHERE TeaID=@TeaID
END

EXEC usp_update_Teacher 1 ,'凌霄',1;--必須和引數宣告的順序一一對應。

--使用儲存過程查詢表中的記錄
go
CREATE procedure usp_select_Teacher1
AS
BEGIN
	SELECT * FROM Teacher;
END

十一、sql裡面的try...catch..

--sql裡面的try...catch...
BEGIN TRY
	UPDATE Teacher SET TeaAge=-1 WHERE TeaID=1;
END TRY
BEGIN CATCH
	PRINT '出異常了'
END CATCH

十二、觸發器

trigger(槍上的扳機)
1)作用是:自動化操作,減少了手動操作以及出錯的機率。
2)理解:觸發器是一種特殊的儲存過程,在SQL內部把觸發器看做是儲存過程但是不能傳遞引數。
3)一般的儲存過程通過儲存過程名被直接呼叫,但是觸發器主要是通過事件進行觸發而執行。
4)處罰器是一個功能強大的工具,在表的資料發生變化時自動強制執行。觸發器還可以用於Sql server約束、預設值和規則的完整性檢查
還可以完成難以用普通約束實現的複雜功能。
5)什麼是觸發器呢? 在SQL Server裡面也就是對某一個表的某種操作,處罰某個條件,從而執行的一段程式。觸發器是一個特殊的儲存過程。
可以理解為一個特殊的儲存過程。觸發器不是使用者自己呼叫的,而是在某種情況下自動執行的。

觸發器分類:
1)DML觸發器:資料操作 刪除、更新、插入; after觸發器(for) instead of觸發器()
2)DDL觸發器:資料定義 create table; create database; alter; drop比如當一張表建立以後,立即出發一個事情。
3)inserted表與deleted表,這兩張表是做什麼的?inserted表包含新資料,insert、update觸發器會用得到。
deleted表包含舊資料,delete、update觸發器會用到。

十三、使用SqlHelper封裝儲存過程的呼叫

        //5)呼叫儲存過程
        //5.1執行增刪改的方法
        //可變引數的位置必須是在引數的最後一個位置
        public static int ExecuteNonQuery(string sql, CommandType cmdType, params SqlParameter[] pms)
        {
            using (SqlConnection conn = new SqlConnection(connStr))
            {
                using (SqlCommand comm = new SqlCommand(sql, conn))
                {
                    //用於區別是儲存過程還是sql語句
                    comm.CommandType = cmdType;
                    if (pms != null)
                    {
                        comm.Parameters.AddRange(pms);
                    }
                    conn.Open();
                    return comm.ExecuteNonQuery();
                }

            }
        }

        //5.2執行查詢,返回單個值的方法
        public static object ExecuteScale(string sql, CommandType cmd, params SqlParameter[] pms)
        {

            using (SqlConnection conn = new SqlConnection(connStr))
            {
                using (SqlCommand comm = new SqlCommand(sql, conn))
                {
                    comm.CommandType = cmd;
                    if (pms != null)
                    {
                        comm.Parameters.AddRange(pms);
                    }
                    conn.Open();
                    return comm.ExecuteScalar();
                }
            }
        }

        //5.3執行查詢,返回多行多列的方法 ExecuteReader()
        public static SqlDataReader ExecuteReader(string sql, CommandType cmd, params SqlParameter[] pms)
        {
            using (SqlConnection conn = new SqlConnection(connStr))
            {
                using (SqlCommand comm = new SqlCommand(sql, conn))
                {
                    comm.CommandType = cmd;
                    if (pms != null)
                    {
                        comm.Parameters.AddRange(pms);
                    }
                    try
                    {
                        conn.Open();
                        return comm.ExecuteReader(CommandBehavior.CloseConnection);
                    }
                    catch (Exception)
                    {
                        //關閉連線、釋放資源。
                        conn.Close();
                        conn.Dispose();
                        throw;
                    }
                }
            }
        }

        //5.4查詢資料,返回DataTable
        public static DataTable ExecuteDataTable(string sql, CommandType cmd, params SqlParameter[] pms)
        {
            DataTable dt = new DataTable();
            using (SqlDataAdapter adapter = new SqlDataAdapter(sql, connStr))
            {
                adapter.SelectCommand.CommandType = cmd;
                if (pms != null)
                {
                    adapter.SelectCommand.Parameters.AddRange(pms);

                    adapter.Fill(dt);
                }
                return dt;
            }   
        }