LinQ&EF任我行(三)--LinQ to SQL (轉)

(原創:灰灰蟲的家http://hi.baidu.com/grayworm)
LinQ to SQL可以用來取代傳統的基於SQL語句的查詢操作。在以後的數據訪問層(DAL)中,我們可以使用LinQ to SQL實現數據庫的CRUD操作,在執行的時候.net框架會把LinQ to SQL查詢表達式轉換成對應的SQL語句再去執行。使用LinQ to SQL可以藉助於LinQ語法大大簡化我們數據訪問的代碼量,並且還具有編譯檢查、智能感知和強類型表達式等優點。
LinQ to SQL從嚴格意義上來說不能算是一個ORM框架,它只對SQL Server起作用,並不能實現對各種關係型數據庫進行透明的映射,所以我們通常把LinQ to SQL稱爲SQL Mapping框架。
LinQ to SQL都是對Table<TEntity>進行操作,對於“一對多”的關聯(如:Customers.Orders)它會使用EntitySets集合類型的成員來表示而對於“多對一”的關聯(如:Order.Customer)它會使用EntityRefs類型成員來表示。LinQ to SQL並不支持“多對多”的直接關聯操作。

一、LinQ to SQL的ORM
LinQ to SQL不但僅僅實現了對象/關係之間的映射,還提供了一個簡單易用的圖形化界面工具。通過這個工具可以爲SQL200X每個表生成一個實體類,並在底層有關聯的表的實體類之間生成一個實體關聯,把數據庫中表和表之間的“關聯關係”徹底轉換爲對象與對象之間的“關聯關係”。通過此關聯可以直接訪問到該對象和與該對象相關聯的其它對象,不用再通過Join子句來實現多表關聯查詢了。這種關聯實體的功能是LinQ和實體框架的重要功能。
這種把數據庫中的表和外鍵封裝成類和類之間的關聯的優勢在於開發人員可以把數據庫中的抽象數據設計成現實生活中的對象,依照現實生活中的對象來管理代碼世界中的對象數據。

使用LinQ to SQL的圖形化界面快速實現表與實體之間的映射
1.在項目中添加新項,選擇"LinQ to SQL Classes"



《圖1》
2.打開“服務器資源管理器(Server Explorer)”選中要映射的表,並把它們拖到.dbml的設計器界面中。



《圖2》
通過上面兩步,不用寫代碼就可以實現出表與實體之間的映射。下面我們看看生成的代碼結構。



《圖3》
從圖中我們看出,在我們這簡單的拖動過程中,VS爲我們生成了三個文件:MyDB.dbml、MyDB.dbml.layout、MyDB.designer.cs
這三個類的代碼分別如下所示:



《圖4》
MyDB.dbml-XML文件,描述了表與實體之間的映射關係。如果我們直接從“服務器資源管理器”中向.dbml拖動表來使用LINQ的話,那些XML映射文件並不會起做用。因爲LinQ還有映射方式:使用Attributes來映射,當我們從“服務器資源管理器”中直接拖動表到.dbml上去的時候,就是使用Attribute來映射的。在下面的MyDB.designer.cs文件中我們可以看到類和屬性的上都帶有Attributes屬性,它用來描述實體類與表的映射關係。這種使用Attributes來實現映射,不是一種很好的映射方式,因爲映射沒有與代碼完全分離,修改映射的過程本還需要對源代碼進行修改,但這種使用LinQ的方式最爲簡單,所以也就成爲大多數LinQ程序員的選擇。
MyDB.dbml.layout-XML文件,描述了在LinQ to SQL圖形化設計界面中圖示的佈局位置信息
MyDB.designer.cs-CSharp源代碼文件,根據數據庫的表結構,生成的實體類和DataContext類。
如果只從應用的角度上來講,我們不用分析這三個文件的代碼。但對於MyDB.designer.cs其中的類我們需要大體來看一下。
MyDB.desinger.cs文件中包含了系統自動生成的類,這些類大部份是數據庫中表所對應的實體類,類的名子一般是以表名進行命名,如:Info,Family,Work,Nation,Title。另外還有一個類MyDBDataContext類,從字面意思上理解,該類是“MyDB數據上下文”,所謂的“上下文”就是指“硬盤上的關係型數據庫”和“內存中的實體對象”,MyDBDataContext類的作用就是在“硬盤上的關係型數據庫”和“內存中的實體對象”之間起一個橋樑的作用。這個類的代碼中包含一系列的Table<TEntity>型的屬性,可以使用它們來把數據庫中記錄的集合變換成內存中對象的集合,以便操作。
在MyDB.desinger.cs文件的實體類中,包含諸多成員變量和屬性的定義,這個成員變量除了對應於數據庫中字段名,但還出現了EntitySet<TEntity>和EntityRef<TEntity>兩種類型的成員變量,正如我們上面所講到的,這兩種類型的變量分別用於實現對象與對象之間的關聯關係。EntitySet<TEntity>是實體集合的引用,它指向與當前對象相關聯的其它實體集合(1:m);EntityRef<TEntity>是實體對象引用,它指向與當前對象相關聯的其它實體對象的引用(m:1)。



《圖5》
上面是Info表的實體類,除了_Code,_Name,_Sex,_Nation,_Birthday等數據庫字段變量外,還有_Families和_Works,這兩個成員變量對應Family表和Work表中,與當前對象相關聯的實體對象的集合;而_Nation1則是與當前對象相關聯的民族實體對象。

二、使用LinQ to SQL實現數據庫的查詢操作
LinQ to SQL的數據庫操作是大多數程序員所鍾情的功能,因爲它能夠把LinQ查詢表達式自動轉換爲相應的SQL語句進行處理,這樣就不用再花太多的時間去編寫實體類和數據訪問類了。LinQ to SQL的查詢語句與LinQ to Objects語法很相似,只是LinQ to Objects是把LinQ表達式轉換爲中間語言,而LinQ to SQL是把LinQ表達式轉換爲SQL語句,送到數據庫去執行。
下面以一個簡單的例子來展示一下LinQ to SQL的使用,具體語法請參照上一篇文章。
示例:
在Info表中查詢回族男生和漢族的女生中姓張的同學的信息
第一步:新建MyDB.dbml,並從服務器資源管理器中把相應的表拖進MyDB.dbml
第二步:添加新頁面
第三步:實例化MyDBDataContext對象
MyDBDataContext context = new MyDBDataContext();
三步:編寫LinQ查詢
var q = from p in context.Infos where p.Name.StartsWith("張") &&((p.Nation == "n002" && p.Sex == true) || (p.Nation == "n001" && p.Sex == false) ) select p;

var q = context.Infos.Where(p => p.Nation == "n002").Where(p => p.Sex == true).Concat(context.Infos.Where(p => p.Nation == "n001").Where(p => p.Sex == false)).Where(p=>p.Name.StartsWith("張"));
第四步:把查詢序列轉換成集合,並顯示結果
var list = q.ToList();
            foreach (Info item in list)
            {
                Console.WriteLine(item.Code + "\t" + item.Name + "\t" + (item.Sex.Value?"男":"女") + "\t" + item.Nation1.Name);
            }

三、使用LinQ實現增、刪、改操作
(一)添加操作
第一步:實例化DataContext對象
MyDBDataContext context = new MyDBDataContext();

第二步:生成實體對象
            Info data = new Info
            {
                Code = "x004",
                Name = "馬大哈",
                Sex = false,
                Nation = "n001",
                Birthday = new DateTime(1989, 12, 28)
            };

第三步:向DataContext對象的Table<TEntity>集合中註冊添加上一步中生成的實體對象
context.Infos.InsertOnSubmit(data);
也可以使用Table<TEntity>集合的InsertAllOnSubmit(IEnumberable<T>)方法一次註冊添加多個新對象,以便一次性向數據庫插入這些數據。

第四步:使用DataContext對象提交更改
context.SubmitChanges();

(二)修改操作
第一步:實例化DataContext對象
MyDBDataContext context = new MyDBDataContext();

第二步:使用DataContext對象查詢數據庫中需要修改的內容,返回對應的實體對象
Info data = context.Infos.Where(p => p.Code == "x004").First();

第三步:修改上一步中實體對象中的值
            data.Name = "馬也";
            data.Sex = true;
            data.Nation = "n002";

第四步:使用DataContext對象提交更改
context.SubmitChanges();

修改數據時,由於要修改的數據本身就是數據庫中現有的數據,所以不用像插入操作那樣使用context.Infos.InsertOnSubmit(data)註冊數據,直接修改數據並提交更改即可,

(三)刪除操作
第一步:實例化DataContext對象
MyDBDataContext context = new MyDBDataContext();

第二步:使用DataContext對象查詢數據庫中需要修改的內容,返回對應的實體對象
Info data = context.Infos.Where(p => p.Code == "x004").First();

第三步:向DataContext對象的Table<TEntity>集合中註冊刪除上一步中查出的對象
            context.Infos.DeleteOnSubmit(data);
也可以使用Table<TEntity>集合的DeleteAllOnSubmit(IEnumberable<T>)方法一次註冊刪除多個新對象,以便一次性向數據庫刪除這些數據。

第四步:使用DataContext對象提交更改
context.SubmitChanges();

上面的增、刪、改查代碼只有在context.SubmitChanges()調用的時候才提交數據庫執行對應操作。我們可以在執行context.SubmitChanges()之前編寫多項數據的增、刪、改代碼,然後使用context.SubmitChanges()實現一次性提交。context.SubmitChanges()方法自身帶有事務功能,我們不必手動編寫事務實現數據庫的修改。

四、使用LinQ調用存儲過程
在開發人員與DBA之間總是存在一種爭論:使用在程序中使用存儲過程是不是一種較好的解決方案?下面我們從四方面來看一下:
1.訪問控制:
如果使用存儲過程,可以爲數據庫創建自定義的用戶或角色,並授權他們訪問指定的存儲過程,以提高數據操作的安全性。

2.SQL注入攻擊
這種攻擊通常是在執行動態SQL語句的時候發生,保存動態SQL語句的變量被惡意用戶利用,通過輸入的內容與原有SQL語句進行組合形成新的、具有攻擊性的語句來對數據庫操作。LinQ to SQL使用參數化查詢方式來使用動態T-SQL語句,這種參數化的T-SQL語句能夠很好的避免SQL注入攻擊。所以並不是只有存儲過程才能夠解決SQL注入攻擊

3.性能
在SQL Server6.5或更早版本中,對存儲過程可以實現部份編譯的功能,當調用存儲過程的時候可以加快SQL代碼執行速度。在SQL Server7.0和以後的版本中對於存儲過程和SQL語句都具有編譯功能。因此,一般說來在SQL Server7.0以後的數據庫版本,存儲過程的執行與參數化的SQL代碼執行效率沒有什麼太大的區別

4.結構獨立性
如果數據庫的結構發生了變化,那與之相關的存儲過程也需要重新編寫、測試。如果軟件升級時,不但需要重新編寫、測試存儲過程,還需要涉及到存儲過程的更新。如果使用DAL來實現數據庫操作的話,可以藉助於代碼生成工具,來實現數據訪問代碼的“半自動修改”,使用DAL層可以把數據庫結構改變的影響控制在一個有限的局部範圍內。

(一)添加存儲過程
從服務器資源管理器中把存儲過程直接拖放到.dbml界面中,在界面的右側會出現相應的存儲過程。



《圖6》
右擊.dbml界面中的存儲過程,點擊“屬性”,打開屬性面版,可以在這裏修改存儲過程的屬性,在這裏我們修改比較多的是Return Type,它代表存儲過程的返回類型。



《圖7》
在這個拖動過程中,會在.designer.cs中生成兩段代碼:一段是實體類用來代表存儲過程用到的實體對象(這個類只在調用增、刪、改的存儲過程中出現),如圖8;另一段是在DataContext類中,用來實現存儲過程的調用,如圖9。



《圖8》



《圖9》
下面我們來看如何使用存儲過程實現CRUD的操作。

(二)使用存儲過程實現查詢操作
1.把存儲過程拖到.dbml界面中去。
2.編寫代碼調用存儲過程



《圖10》
在調用存儲過程中,可以直接使用DataContext調用對應的存儲過程調用方法,如果存儲過程需要參數的話,我們可以把參數值直接傳遞給方法。
在LinQ中存儲過程調用默認返回的類型是ISingleResult<T>類型,它實現了IEnumerable<T>接口,可以使用ToList()方法把結果轉換爲List<T>,也可以直接綁定到ObjectDataSource控件中。ISingleResult<T>很像Table<TEntity>但比Table<TEntity>更簡單一些。

(三)使用存儲過程實現增、刪、改操作
使用存儲過程實現增、刪、改操作可以有兩種方式來實現:一種是使用DataContext對象直接調用存儲過程調用方法。另一種是修改實體類的默認方法,把增、刪、改操作默認方法指定爲相當的存儲過程。
第一種方法,使用DataContext對象直接調用存儲過程的調用方法

1.把存儲過程拖到.dbml界面中去。
2.編寫代碼調用存儲過程



《圖11》

第二種方法,修改實體類的默認方法 
1.把存儲過程拖到.dbml界面中去。
2.在.dbml文件中相應實體類上右擊,選擇“屬性”,打開屬性面版。



《圖12》
3.在屬性面版的Default Methods類別中單擊相應的方法,打開對話框。



《圖13》
在class下拉列表中選擇要操作的實體類,在Behavior下拉列表中選擇要對錶進行的操作,在Customize下拉列表中選擇對應的存儲過程,在最下面的二維表格中選擇存儲過程參數與類的屬性的對應關係。
4.編寫代碼對數據庫進行增、刪、改操作



《圖14》

五、使LinQDataSource數據源控件
在Web應用程序中,可以使用數據源控件來向界面提供綁定的數據。LinQ to SQL能夠爲兩種數據源對象提供數據:ObjectDataSource和LinqDataSource
LinqDataSource控件是VS2008中的新增的數據源控件,它只能綁定到LinQ to SQL的DataContext.Table<TEntity>對象。用它向Web控件提供數據,並可以實現對數據的排序和分頁操作。使用GridView、DetailsView、FormView等控件與LinqDataSource控件綁定的時候,不需要編寫代碼就可以實現數據的插入、修改、刪除操作。
ObjectDataSource控件也可以使用LinQ to SQL獲取數據,因爲DataContext.Table<TEntity>對象實現了IEnumerable<T>接口。當然我們也可以把Table<TEntity>對象轉換成其它集合對象實現綁定。
下面我們一起來看一下,如何使用LinQ to SQL來實現數據綁定
第一步:添加LinQ to SQL類文件.dbml,並從“服務器資源管理器”拖動表到.dbml設計界面,生成LinQ to SQL類。
第二步:從工具箱中把LinqDataSource控件拖到界面中來。
第三步:在LinqDataSource控件的智能菜單中單擊“配置數據源”,打開配置嚮導窗口。
第四步:在"Choose your context object"下拉列表中選擇***DataContext。點擊“Next”。



《圖15》
第五步:在Table下拉列表中選擇要在界面中顯示的Table集合,在下面的列的列表中選擇要顯示的列。點擊“Next”。



《圖16》
第六步:(可選)如果想獲取表中一部份數據的話,點擊右側的where按鈕,在彈出對話框中來配置where表達式。這個配置界面與SqlDataSource和ObjectDataSource控件很像,在此不多說了。點擊“OK”。



《圖17》
第七步:(可選)如果要對顯示的數據進行排序的話,請在圖16中點擊“OrderBy”按鈕,在彈出對話框中設置排序規則。點擊“OK”。



《圖18》
第八步:(可選)如果想讓LinqDataSource控件具有增、刪、改的功能,在圖16中點擊“Advanced”按鈕,在彈出下面對話框,在對話框中有三個複選框,只要選中它們,就可以實現增、刪、改的功能。



《圖19》
第九步:點擊“Finish”按鈕完成配置工作。
第十步:從工具箱中拖GridView到界面中來,設置DataSourceID爲上面的LinqDataSource控件的ID。運行頁面,出現效果如下:



《圖20》

從圖20中我們可以看出,表中的數據都被顯示出來了,但是“Nation”這一列中顯示的是代號,因爲這一列是外鍵列,如何把它顯示成民族名稱呢。
在上面綁定數據的基礎上,繼續進行如下設置
第十一步:打開GridView的編輯列對話框。把EntityRef型字段(Nation1)添加到選中列中,再刪除原有的Nation綁定列。



《圖21》
第十二步:把Nation1轉換爲模板列。
第十三步:修改模板列的綁定代碼如下:



《圖22》
運行頁面,看到民族名稱顯示出來了。



《圖23》
至於Sex和Birthday字段的格式化顯示,請參閱另外兩篇文章:GridView詳解 和 ListView詳解


六、延遲加載與即時加載
延遲加載(Lazy Loading):只有在我們需要數據的時候纔去數據庫讀取加載它。
優點:較好的性能,有效節省內存資源。
缺點:會產生多次與數據庫之間的交互。
即時加載(Eager Loading):在加載數據時就把該對象相關聯的其它表的數據一起加載到內存對象中去。
優點:能夠一次性地把數據全加載到內存,不用反覆與數據庫之間進行交互操作
缺點:佔用服務器內存較多,在加載相關聯數據的時候,會用到連接查詢,會降低性能。
在默認情況下,LinQ to SQL加載數據使用的是延遲加載。比如:在我加載某個Info對象的時候,並不會立即加載該Info對象的Works、Families和Nation1三個成員對象,只有在我們訪問到該對象的這三個成員的時候纔會動態加載該Info對象相對應的三個對象的內容。

與延遲加載密切相關的屬性有兩個:DataContext.DeferredLoadingEnabledDataContext.LoadOptions
DataContext.DeferredLoadingEnabled:是否採用延遲加載。True-(默認值)採用延遲加載;False-不採用延遲加載(也不採用即時加載,加載的時候只加載當前對象的數據。當我們訪問相關聯子對象的時候也不會動態加載子對象的數據)
DataContext.LoadOptions:加載選項,用來指定那些子對象採用即時加載方式。

首先,我們來看看在默認情況下,即“延遲加載”的情況下,DataContext對象的結構:



《圖24》
從圖24我們可以看出,在默認情況下,DataContext對象DeferredLoadingEnabled=true,即採用了延遲加載的方式。LoadOptions=null,即沒有對任何子對象進行即時加載操作。
下面我們再看看DataContext中Table<TEntity>的情況:



《圖25》
從圖25中我們可以看出,當我們訪問Info對象的Works集合時,集合中會包含對應的數據,這些數據就是在訪問該Works屬性時動態從數據庫中提取出來的。圖中雖然有個IsDeffered=false,但這不代表該Works集合沒有延遲加載,它代表Works集合是否正處於延遲狀態,還未被執行查詢。
再從生成的SQL語句中來看



《圖26》
我們看到生成的SQL語句僅僅是對Info表的數據進行查詢。並沒有查詢Works表和Families表中的數據,這兩個表中的數據是在後面被延遲加載的。

下面我們再看看“非延遲加載”的情況。
需要大家注意的是:“非延遲加載”並不一定是“即時加載”,它兩個不是一個概念。
要實現“非延遲加載”需要把DataContext對象的DeferredLoadingEnabled屬性設爲False。
然後我們再來看DataContext中的Table<TEntity>的情況:



《圖27》
從上圖中我們看出在“非延遲加載”情況下,並不會自動加載子對象的數據。
它所生成的語句與 圖26是一樣的。不一樣的是,當我們訪問Works集合時並沒有動態加載相應的數據。

最後我們再來看看“即時加載”,“即時加載”與DataContext.DeferredLoadingEnabled的值沒有太大的關係。它主要與DataContext.LoadOptions有關。
“即時加載”就是在加載Info對象的時候不只加載該對象的基本信息,它會把該對象的相關子對象(EntitySet和EntityRef)的數據一起加載出來,而不是等到用到的時候再加載。
下面我們看看如何使用“即時加載”來加載Info對象。



《圖28》
從上圖中我們可以看出,雖然DataContext.DeferredLoadingEnabled=false,但由於我使用了“即時加載”來加載了“Nation1”的數據,所以Nation1是有內容的。而Works和Families依然是Count=0
下面我們再看看“即時加載”生成的SQL語句



《圖29》
從圖中我們看到這是一個左連接的SQL語句,在查詢的時候,不再只查Info表的數據,而是把相應的Nation表的數據一起查出來了。這就是即時加載的原理。

注意:對於一個DataContext對象,DataContext.LoadOptions屬性只能賦一次值,一旦賦完值,我們將不能修改這個值了。因此,雖然DataLoadOptions提供了靈活的“即時加載”功能,但使用的時候一定要考慮清楚再使用。


附:LinQ的查詢管道



《圖30》

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章