本文詳細(xì)為你闡述了如何在你的應(yīng)用程序中實(shí)現(xiàn)LINQ to SQL。附件的示例程序包括了這里探討的所有代碼,還提供了一個(gè)簡(jiǎn)單的WPF圖形界面程序來(lái)顯示通過(guò)數(shù)據(jù)綁定返回的結(jié)果集。
本部分描述如何實(shí)現(xiàn)表間的映射關(guān)系:M:1,1:M和M:M。但是這里不會(huì)討論1:1的映射關(guān)系,你可以在M:1的關(guān)系中發(fā)現(xiàn)這種1:1的映射關(guān)系。因此,從這里開(kāi)始,我們將使用Book作為示例為你一步一步講述這一實(shí)現(xiàn)過(guò)程。
映射M:1的關(guān)系
Book 對(duì)象與Category 對(duì)象是多對(duì)一的關(guān)系(M:1),因?yàn)橐槐緯?shū)僅能屬于某一個(gè)類別(并且每個(gè)類別能夠包涵很多本書(shū)):

在數(shù)據(jù)庫(kù)中,Book.catalog字段作為該表的外鍵,而在Category中作為主鍵。然而,在你的對(duì)象模型中,你很可能想讓book.Catalog表示一個(gè)實(shí)際的Catalog對(duì)象(不僅僅是ID)。此時(shí),你可以通過(guò)創(chuàng)建兩個(gè)私有字段來(lái)實(shí)現(xiàn)這一映射關(guān)系,然后再對(duì)Category對(duì)象暴露一個(gè)公有屬性。
1、添加一個(gè)私有字段以進(jìn)行其他表的關(guān)聯(lián)
添加一個(gè)私有字段,將其映射到Book.category數(shù)據(jù)庫(kù)表的外鍵列。
如果允許該字段為NULL,使用一個(gè)空類型即可實(shí)現(xiàn)(如,采用Int?的方式)。
我將這個(gè)字段命名為categoryId(為了區(qū)別于我們后面將要?jiǎng)?chuàng)建的公有屬性Category)。這意味著在Column特性上我必須得設(shè)置Name參數(shù),因?yàn)槲业淖侄蚊趾蛿?shù)據(jù)庫(kù)表的字段名稱不同:
[Column( Name="Category")]privateint?categoryId; |
2、添加一個(gè)引用其他表的私有EntityRef類型字段
添加一個(gè)私有類型的EntityRef字段以實(shí)現(xiàn)對(duì)Category實(shí)例的引用。雖然這會(huì)使得公有屬性Category扮演一個(gè)后臺(tái)字段的角色,但是通過(guò)使用EntityRef類型,仍然能達(dá)到延遲加載的目的(意思是LINQ不會(huì)即時(shí)從數(shù)據(jù)庫(kù)獲取數(shù)據(jù)直到我們真正想要數(shù)據(jù)的時(shí)候)。初始化EntityRef字段實(shí)例,以阻止NullReferenceExceptions發(fā)生。萬(wàn)一在某個(gè)地方你需要使用該對(duì)象的實(shí)例(如,Category)而此時(shí)又沒(méi)有任何實(shí)例(Book)可用。
privateEntityRef _category=newEntityRef( ); |
3、使用[Association]特性添加一個(gè)公有屬性進(jìn)行類的關(guān)聯(lián)
最后,創(chuàng)建類型為public的Category屬性,該屬性用于接收實(shí)際的Category實(shí)例。 使用下面的參數(shù)為該屬性添加Association特性:
● 數(shù)據(jù)庫(kù)兩表之間的是關(guān)系名稱Name(在本例為FK_Books_BookCategories))
● IsForeignKey = true標(biāo)記指定該類對(duì)應(yīng)數(shù)據(jù)表的外鍵(在下面的ThisKey參數(shù)中進(jìn)行了指定)。
● 使用剛才創(chuàng)建的字段為該特性設(shè)置兩個(gè)參數(shù):
(1)ThisKey用于指定到其它表的外鍵:categoryId。
(2)Storage指定EntityRef用于接收其他類的實(shí)例:_category。
在屬性內(nèi)部,代碼的getter/setter訪問(wèn)器用于使用category的Entity屬性,它包涵實(shí)際的Category實(shí)例:
[Association( Name = "FK_Books_BookCategories", IsForeignKey = true, Storage = "_category", ThisKey = "categoryId" )] public Category Category{ get { return _category.Entity; } set { _category.Entity = value; } } |
從M:1的關(guān)系上訪問(wèn)數(shù)據(jù)
現(xiàn)在你可以通過(guò)這種關(guān)系以面向?qū)ο蟮姆绞皆L問(wèn)Category的數(shù)據(jù)。例如,book.Category.Name:
foreach( var book in bookCatalog.Books ) { string categoryName = book.Category.Name; } |
在關(guān)聯(lián)表中的M:1關(guān)系
現(xiàn)在,咱們一起看看如何在M:M關(guān)聯(lián)表中實(shí)現(xiàn)M:1的關(guān)系。當(dāng)我們遇到表之間的關(guān)系為M:M時(shí),正如這里的BookAuthors,當(dāng)然,連接表的關(guān)系仍然是由兩個(gè)M:1關(guān)系構(gòu)成。
例如,BookAuthor包涵一個(gè)到Book的M:1關(guān)系和一個(gè)到Author的M:1關(guān)系來(lái)構(gòu)成M:M的關(guān)系:

不幸的是,你仍然需要?jiǎng)?chuàng)建一個(gè)類來(lái)映射這個(gè)關(guān)聯(lián)表。然而,由于外鍵的原因,它僅僅用于映射的關(guān)系,你可以通過(guò)設(shè)置類的訪問(wèn)修飾符為internal來(lái)保持僅對(duì)外提供一個(gè)公共接口。
1、使用[Table]特性創(chuàng)建一個(gè)內(nèi)部類用于映射需要連接的表
以BookAuthors類相同的方式創(chuàng)建為其他實(shí)體類,但是不要將其標(biāo)記為public:
using System.Data.Linq; using System.Data.Linq.Mapping; namespace LINQDemo { [Table( Name = "BookAuthors" )] class BookAuthor{} } |
2、映射兩表之間的M:1關(guān)系,指定它們將其作為主鍵
為Book和Author創(chuàng)建一個(gè)M:1的關(guān)系并以相同的方式為Book:Catalog創(chuàng)建關(guān)系。注意數(shù)據(jù)庫(kù)中BookAuthors表的關(guān)系以下面的方式命名:
● BookAuthor:Authors關(guān)系被命名為 FK_BookAuthors_Authors
● BookAuthor:Books 關(guān)系被命名為FK_BookAuthors_Books
分別在它的兩個(gè)Column特性上為其添加IsPrimaryKey = true的屬性以指示BookAuthors的主鍵由這兩個(gè)值構(gòu)成:
[Table( Name = "BookAuthors" )] class BookAuthor { [Column( IsPrimaryKey = true, Name = "Author" )] private int authorId; private EntityRef _author = new EntityRef( ); [Association( Name = "FK_BookAuthors_Authors", IsForeignKey = true, Storage = "_author", ThisKey = "authorId" )] public Author Author { get { return _author.Entity; } set { _author.Entity = value; } } Column( IsPrimaryKey = true, Name = "Book" )] private int bookId; private EntityRef _book = new EntityRef( ); [Association( Name = "FK_BookAuthors_Books", IsForeignKey = true, Storage = "_book", ThisKey = "bookId" )] public Book Book { get { return _book.Entity; } set { _book.Entity = value; } } } |
雖然我們已經(jīng)探討了M:1的關(guān)系,但不幸的是,你仍然還不能使用BookAuthor做一些有趣的事,當(dāng)我們?cè)谙旅嬗懻揗:M的關(guān)系時(shí),將繼續(xù)回到表連接的探討話題上來(lái)。但首先,通過(guò)理解如何映射1:M的關(guān)系,我們可以看看Book:Catalog的關(guān)系來(lái)完整理解M:M的關(guān)系。
映射1:M關(guān)系
添加一個(gè)1:M的關(guān)系使得你可以獲得一個(gè)屬于Category的所有書(shū)籍列表。
1、在其它類中映射外鍵
即使你正在為Category添加關(guān)聯(lián)關(guān)系,它仍然需要知道如何關(guān)聯(lián)到Book本身。因此,你僅需要確保你的Book類已經(jīng)映射到了對(duì)應(yīng)的列,該列應(yīng)該是關(guān)聯(lián)到Category的外鍵。如果你這樣實(shí)現(xiàn),那么你已經(jīng)完成了1:M的關(guān)系,因此這時(shí)你所要做的就是指定字段名:categoryId:
[Table( Name = "Books" )] public class Book { ... [Column( Name = "Category" )] private int? categoryId; |
2、映射你自己的主鍵
LINQ會(huì)比較Book的外鍵和Category的外鍵,因此你需要映射Category.Id并標(biāo)識(shí)其作為主鍵([Column (IsPrimaryKey = true)])。再次,如果你繼續(xù)這個(gè)步驟,你已經(jīng)在實(shí)體類中完成了該過(guò)程的創(chuàng)建。因此,所要做的僅僅是為該屬性名Id添加一個(gè)注釋:
[Table (Name="BookCategories")] public class Category { [Column ( IsPrimaryKey = true, IsDbGenerated = true )] public int Id { get; set; } ... |
3、添加一個(gè)私有的EntitySet類型以實(shí)現(xiàn)對(duì)其他表的引用
添加一個(gè)私有的EntitySet字段來(lái)接收屬于該Category的書(shū)籍。這將讓我們的公有Books屬性扮演字段的角色。類似地,EntityRef,EntitySet會(huì)引起對(duì)Books的加載延遲直到我們實(shí)際訪問(wèn)它的時(shí)候(因此,當(dāng)我們需要查看一個(gè)類別的時(shí)候,我們不必每次都返回所有書(shū)的列表)。
初始化EntitySet字段以避免NullReferenceExceptions異常的發(fā)生,比如在你沒(méi)有設(shè)置兩邊關(guān)系的時(shí)候(如一個(gè)類別沒(méi)有書(shū)籍的時(shí)候)
private EntitySet _books = new EntitySet(); |
4、為相關(guān)聯(lián)的類添加屬性
最后創(chuàng)建公有Books屬性,它用于接收在對(duì)應(yīng)類別中的書(shū)籍。為該屬性添加一個(gè)Association特性。并設(shè)置數(shù)據(jù)庫(kù)關(guān)系的名稱參數(shù)為Name (FK_Books_BookCategories),以及使用剛才創(chuàng)建這個(gè)字段的特性所需要的三個(gè)參數(shù):
● OtherKey指定關(guān)聯(lián)到其他類(Book)的字段,該字段接收所關(guān)聯(lián)的外鍵:categoryId
● ThisKey指定你的主鍵(OtherKey所應(yīng)該匹配的字段):Id
● Storage指定你的EntitySet類型,該類型用來(lái)存儲(chǔ)相關(guān)聯(lián)的Books集合:_books.
在屬性內(nèi)部,代碼getter/setter訪問(wèn)修飾符使用了 _books,它包涵一個(gè)實(shí)際Book實(shí)例的ICollection集合:
[Association( Name = "FK_Books_BookCategories", Storage = "_books", OtherKey = "categoryId", ThisKey = "Id" )] public ICollection Books { get { return _books; } set { _books.Assign( value ); } } |
從1:M 關(guān)系上訪問(wèn)數(shù)據(jù)
現(xiàn)在,你可以通過(guò)使用category.Books屬性訪問(wèn)每個(gè)類別中的Books列表:
foreach( var category in bookCatalog.Categories ){ foreach( Book book in category.Books ){ string bookTitle = book.Title; } } |
映射M:M 關(guān)系
最后,添加M:M關(guān)系,這可以允許你直接從Book實(shí)例中訪問(wèn)每個(gè)目錄下下的所有作者,以及從Author實(shí)例中直接訪問(wèn)某個(gè)作者寫的所有書(shū)籍。
通過(guò)創(chuàng)建BookAuthor類,說(shuō)明你已經(jīng)完成了該任務(wù)的大部分工作了。并且,你也在前面部分已經(jīng)了解到當(dāng)你需要?jiǎng)?chuàng)建1:M時(shí)如何實(shí)現(xiàn)這一功能。現(xiàn)在,是該將它們整合起來(lái)的時(shí)候了。再次,我們將使用Book作為示例進(jìn)行闡述。
1、從類到關(guān)聯(lián)的表上添加一個(gè)1:M的關(guān)系
Book到BookAuthor 它存在一個(gè)1:M的關(guān)系,每本書(shū)都可以有多個(gè)作者,但是每本書(shū)作者僅僅屬于一本書(shū)。因此,你必須得從先前已有的部分開(kāi)始進(jìn)行如下四個(gè)步驟:
● bookId. 在BookAuthor類中映射到Book的外鍵。你已經(jīng)這樣實(shí)現(xiàn)了,會(huì)調(diào)用它的:bookId。
● 為Book對(duì)象定義主鍵。你也已經(jīng)這樣實(shí)現(xiàn)了,會(huì)調(diào)用它的:Id。
● Add an EntitySet that references the other table: _bookAuthors. 添加一個(gè)引用其他表的EntitySet類型。
● 添加一個(gè)BookAuthors屬性(將其設(shè)置為私有的,因?yàn)檫@里僅僅用于幫助獲得作者列表),該屬性添加了Association特性并為其設(shè)置前面部分設(shè)置的三個(gè)參數(shù)值。
添加Book對(duì)象代碼的第3和第4步如下:
[Table( Name = "Books" )] public class Book { ... private EntitySet _bookAuthors = new EntitySet( ); [Association( Name = "FK_BookAuthors_Books", Storage = "_bookAuthors", OtherKey = "bookId", ThisKey = "Id" )] private ICollection BookAuthors { get { return _bookAuthors; } set { _bookAuthors.Assign( value ); } } |
雖然你可以在Author中實(shí)現(xiàn)相同的功能(因?yàn)槊總€(gè)作者都可以有多本書(shū)),但是使用authorId取代bookId:
[Table( Name = "Authors" )] public class Author { ... private EntitySet _bookAuthors = new EntitySet( ); [Association( Name = "FK_BookAuthors_Authors", Storage = "_bookAuthors", OtherKey = "authorId", ThisKey = "Id" )] private ICollection BookAuthors { get { return _bookAuthors; } set { _bookAuthors.Assign( value ); } } |
2、添加一個(gè)公有屬性,通過(guò)1:M的關(guān)系使用LINQ 來(lái)檢索枚舉數(shù)據(jù)。
最后,通過(guò)創(chuàng)建Authors屬性從該書(shū)實(shí)例的私有書(shū)作者列表中檢索所有作者。例如,如果你有一本LINQ In Action的書(shū),該書(shū)就有三個(gè)作者:Fabrice Marguerie, Steve Eichert, 和Jim Wooley,那么LINQ In Action Book實(shí)例將包涵三個(gè)BookAuthors的列表:
