步步学LINQ to SQL:为实体类添加关系【转】
【IT168 专稿】本文详细为你阐述了如何在你的应用程序中实现LINQ to SQL。附件的示例程序包括了这里探讨的所有代码,还提供了一个简单的WPF图形界面程序来显示通过数据绑定返回的结果集。
本部分描述如何实现表间的映射关系:M:1,1:M和M:M。但是这里不会讨论1:1的映射关系,你可以在M:1的关系中发现这种1:1的映射关系。因此,从这里开始,我们将使用Book作为示例为你一步一步讲述这一实现过程。
映射M:1的关系
Book 对象与Category 对象是多对一的关系(M:1),因为一本书仅能属于某一个类别(并且每个类别能够包涵很多本书):
在数据库中,Book.catalog字段作为该表的外键,而在Category中作为主键。然而,在你的对象模型中,你很可能想让book.Catalog表示一个实际的Catalog对象(不仅仅是ID)。此时,你可以通过创建两个私有字段来实现这一映射关系,然后再对Category对象暴露一个公有属性。
1.添加一个私有字段以进行其他表的关联
添加一个私有字段,将其映射到Book.category数据库表的外键列。
如果允许该字段为NULL,使用一个空类型即可实现(如,采用Int?的方式)。
我将这个字段命名为categoryId(为了区别于我们后面将要创建的公有属性Category)。这意味着在Column特性上我必须得设置Name参数,因为我的字段名字和数据库表的字段名称不同:
2. 添加一个引用其他表的私有EntityRef类型字段
添加一个私有类型的EntityRef字段以实现对Category实例的引用。虽然这会使得公有属性Category扮演一个后台字段的角色,但是通过使用EntityRef类型,仍然能达到延迟加载的目的(意思是LINQ不会即时从数据库获取数据直到我们真正想要数据的时候)。初始化EntityRef字段实例,以阻止NullReferenceExceptions发生。万一在某个地方你需要使用该对象的实例(如,Category)而此时又没有任何实例(Book)可用。
3. 使用[Association]特性添加一个公有属性进行类的关联
最后,创建类型为public的Category属性,该属性用于接收实际的Category实例。 使用下面的参数为该属性添加Association特性:
• 数据库两表之间的是关系名称Name(在本例为FK_Books_BookCategories))
• IsForeignKey = true标记指定该类对应数据表的外键(在下面的ThisKey参数中进行了指定)。
• 使用刚才创建的字段为该特性设置两个参数:
(1)ThisKey用于指定到其它表的外键:categoryId。
(2)Storage指定EntityRef用于接收其他类的实例:_category。
在属性内部,代码的getter/setter访问器用于使用category的Entity属性,它包涵实际的Category实例:
IsForeignKey =true, Storage ="_category", ThisKey ="categoryId" )]
public Category Category{
get { return _category.Entity; }
set { _category.Entity = value; }
}
从M:1的关系上访问数据
现在你可以通过这种关系以面向对象的方式访问Category的数据。例如,book.Category.Name:
string categoryName = book.Category.Name;
}
在关联表中的M:1关系
现在,咱们一起看看如何在M:M关联表中实现M:1的关系。当我们遇到表之间的关系为M:M时,正如这里的BookAuthors,当然,连接表的关系仍然是由两个M:1关系构成。
例如,BookAuthor包涵一个到Book的M:1关系和一个到Author的M:1关系来构成M:M的关系:
不幸的是,你仍然需要创建一个类来映射这个关联表。然而,由于外键的原因,它仅仅用于映射的关系,你可以通过设置类的访问修饰符为internal来保持仅对外提供一个公共接口。
1. 使用[Table]特性创建一个内部类用于映射需要连接的表
以BookAuthors类相同的方式创建为其他实体类,但是不要将其标记为public:
using System.Data.Linq.Mapping;
namespace LINQDemo
{
[Table( Name ="BookAuthors" )]
class BookAuthor{}
}
2. 映射两表之间的M:1关系,指定它们将其作为主键
为Book和Author创建一个M:1的关系并以相同的方式为Book:Catalog创建关系。注意数据库中BookAuthors表的关系以下面的方式命名:
• BookAuthor:Authors关系被命名为 FK_BookAuthors_Authors
• BookAuthor:Books 关系被命名为FK_BookAuthors_Books
分别在它的两个Column特性上为其添加IsPrimaryKey = true的属性以指示BookAuthors的主键由这两个值构成:
class BookAuthor
{
[Column( IsPrimaryKey =true, Name ="Author" )] privateint 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" )] privateint 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; }
}
}
虽然我们已经探讨了M:1的关系,但不幸的是,你仍然还不能使用BookAuthor做一些有趣的事,当我们在下面讨论M:M的关系时,将继续回到表连接的探讨话题上来。但首先,通过理解如何映射1:M的关系,我们可以看看Book:Catalog的关系来完整理解M:M的关系。
映射1:M关系
添加一个1:M的关系使得你可以获得一个属于Category的所有书籍列表。
1. 在其它类中映射外键
即使你正在为Category添加关联关系,它仍然需要知道如何关联到Book本身。因此,你仅需要确保你的Book类已经映射到了对应的列,该列应该是关联到Category的外键。如果你这样实现,那么你已经完成了1:M的关系,因此这时你所要做的就是指定字段名:categoryId:
publicclass Book
{
...
[Column( Name ="Category" )] privateint? categoryId;
2. 映射你自己的主键
LINQ会比较Book的外键和Category的外键,因此你需要映射Category.Id并标识其作为主键([Column (IsPrimaryKey = true)])。再次,如果你继续这个步骤,你已经在实体类中完成了该过程的创建。因此,所要做的仅仅是为该属性名Id添加一个注释:
publicclass Category
{
[Column ( IsPrimaryKey =true, IsDbGenerated =true )] publicint Id { get; set; }
...
3. 添加一个私有的EntitySet类型以实现对其他表的引用
添加一个私有的EntitySet字段来接收属于该Category的书籍。这将让我们的公有Books属性扮演字段的角色。类似地,EntityRef,EntitySet会引起对Books的加载延迟直到我们实际访问它的时候(因此,当我们需要查看一个类别的时候,我们不必每次都返回所有书的列表)。
初始化EntitySet字段以避免NullReferenceExceptions异常的发生,比如在你没有设置两边关系的时候(如一个类别没有书籍的时候)
4. 为相关联的类添加属性
最后创建公有Books属性,它用于接收在对应类别中的书籍。为该属性添加一个Association特性。并设置数据库关系的名称参数为Name (FK_Books_BookCategories),以及使用刚才创建这个字段的特性所需要的三个参数:
·OtherKey指定关联到其他类(Book)的字段,该字段接收所关联的外键:categoryId
·ThisKey指定你的主键(OtherKey所应该匹配的字段): Id
·Storage指定你的EntitySet类型,该类型用来存储相关联的Books集合:_books.
在属性内部,代码getter/setter访问修饰符使用了 _books,它包涵一个实际Book实例的ICollection集合:
Storage ="_books", OtherKey ="categoryId", ThisKey ="Id" )]
public ICollection Books {
get { return _books; }
set { _books.Assign( value ); }
}
从1:M 关系上访问数据
现在,你可以通过使用category.Books属性访问每个类别中的Books列表:
foreach( Book book in category.Books ){
string bookTitle = book.Title;
}
}
映射M:M 关系
最后,添加M:M关系,这可以允许你直接从Book实例中访问每个目录下下的所有作者,以及从Author实例中直接访问某个作者写的所有书籍。
通过创建BookAuthor类,说明你已经完成了该任务的大部分工作了。并且,你也在前面部分已经了解到当你需要创建1:M时如何实现这一功能。现在,是该将它们整合起来的时候了。再次,我们将使用Book作为示例进行阐述。
1. 从类到关联的表上添加一个1:M的关系
Book到BookAuthor 它存在一个1:M的关系,每本书都可以有多个作者,但是每本书作者仅仅属于一本书。因此,你必须得从先前已有的部分开始进行如下四个步骤:
·bookId. 在BookAuthor类中映射到Book的外键。你已经这样实现了,会调用它的:bookId。
·为Book对象定义主键。你也已经这样实现了,会调用它的:Id。
·Add an EntitySet that references the other table: _bookAuthors. 添加一个引用其他表的EntitySet类型。
·添加一个BookAuthors属性(将其设置为私有的,因为这里仅仅用于帮助获得作者列表),该属性添加了Association特性并为其设置前面部分设置的三个参数值。
添加Book对象代码的第3和第4步如下:
publicclass 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中实现相同的功能(因为每个作者都可以有多本书),但是使用authorId取代bookId:
publicclass 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. 添加一个公有属性,通过1:M的关系使用LINQ 来检索枚举数据。
最后,通过创建Authors属性从该书实例的私有书作者列表中检索所有作者。例如,如果你有一本LINQ In Action的书,该书就有三个作者:Fabrice Marguerie, Steve Eichert, 和Jim Wooley,那么LINQ In Action Book实例将包涵三个BookAuthors的列表:
你想要做的就是检索作者列表,因此调用者甚至不必了解有这样一个中间表的存在。你可以通过使用LINQ来查询该私有BookAuthors属性并仅仅返回所查找的作者:
select ba.Author;
现在,你可以在Book中将其包装成一个名位为Authors的公有、只读属性,并且(可选)通过在结果集中调用toList()返回一个ICollection集合,正如我在这里所实现的那样,为其提供一些更多的公共接口:
return ( from ba in BookAuthors select ba.Author ).ToList( ); } }
当然,在Author中你可以实现相同的功能来返回一个作者的所有书籍:
return ( from ba in BookAuthors select ba.Book ).ToList( ); } }
从M:M的关系中访问数据
现在,你可以完全地如愿操作你的对象模型。下面是一些例子:
foreach( var book in bookCatalog.Books ) {
string title = book.Title;
decimal price = book.Price;
string category = book.Category.Name;
ICollection authors = book.Authors;
ICollection otherBooksInCategory = book.Category.Books;
}
// Starting from Authors...
foreach( var author in bookCatalog.Authors ) {
string name = author.Name;
ICollection books = author.Books;
}
// Starting from Categories...
foreach( var category in bookCatalog.Categories ){
string name = category.Name;
foreach( var book in category.Books ){
string bookTitle = book.Title;
ICollection bookAuthors = book.Authors;
}
}
从未使用/分配警告
有件事情你可能需要注意的是你为LINQ创建的中间变量(虽然仅仅获得了对Association特性的引用,但是其它部分从来未使用过)将会引起Visual Studio提示为警告,消息为这些值从未使用和分配。当然,它们实际上都被使用被进行了值的分配,但是后台编译器却不能探测到。你可以抑制这些警告的发生,正如我在附件代码中阐述的那样,使用pragma 预处指令取代上面需要映射到数据库的每个类或字段:
#pragma warning disable 0169, 0649
步步学LINQ to SQL:为实体类添加关系【转】的更多相关文章
- 步步学LINQ to SQL:使用LINQ检索数据【转】
[IT168 专稿]该系列教程描述了如何采用手动的方式映射你的对象类到数据表(而不是使用象SqlMetal这样的自动化工具)以便能够支持数据表之间的M:M关系和使用实体类的数据绑定.即使你选择使用了自 ...
- 步步学LINQ to SQL:将类映射到数据库表【转】
[IT168 专稿]该系列教程描述了如何采用手动的方式映射你的对象类到数据表(而不是使用象SqlMetal这样的自动化工具)以便能够支持数据表之间的M:M关系和使用实体类的数据绑定.即使你选择使用了自 ...
- LINQ to SQL 建立实体类
使用LINQ to SQL时,需要首先建立用于映射数据库对象的模型,也就是实体类.在运行时,LINQ to SQL 根据LINQ表达式或查询运算符生成SQL语句,发送到数据库进行操作.数据库返回后,L ...
- LINQ to SQL 建立实体类 (转)
http://www.cnblogs.com/DebugLZQ/archive/2012/11/14/2770449.html 使用LINQ to SQL时,需要首先建立用于映射数据库对象的模型,也就 ...
- 回顾:Linq To SQL语法 - 实体类
第一篇博客,还望各位大神勿喷 小弟在此代码奉上........ 借用NorthWind数据库,实现一个商品展示的小功能.上代码: 添加对Linq的引用 using System.Data.Linq;/ ...
- 一步一步学Linq to sql(二):DataContext与实体
DataContext DataContext类型(数据上下文)是System.Data.Linq命名空间下的重要类型,用于把查询句法翻译成SQL语句,以及把数据从数据库返回给调用方和把实体的修改写入 ...
- 一步一步学Linq to sql(五):存储过程
普通存储过程 首先在查询分析器运行下面的代码来创建一个存储过程: create proc sp_singleresultset as set nocount on select * from cust ...
- (转载)一步一步学Linq to sql系列文章
现在Linq to sql的资料还不是很多,本人水平有限,如果有错或者误导请指出,谢谢. 一步一步学Linq to sql(一):预备知识 一步一步学Linq to sql(二):DataContex ...
- Entity Framework中的实体类添加复合主键
使用Code First模式实现给实体类添加复合主键,代码如下: using System; using System.Collections.Generic; using System.Compon ...
随机推荐
- 添加python第三方插件时出现的问题
当我安装beautifulsoup4时出现了如下错误: Fatal error in launcher: Unable to create process using '""F:\ ...
- JNI的替代者—使用JNA访问Java外部功能接口
摘自:http://www.cnblogs.com/lanxuezaipiao/p/3635556.html JNI的替代者-使用JNA访问Java外部功能接口 1. JNA简单介绍 先说JNI(Ja ...
- hdu 4355 Party All the Time(三分搜索)
Problem Description In the Dark forest, there is a Fairy kingdom where all the spirits will go toget ...
- Phoenix中Sequence的用法
Phoenix--HBase的JDBC驱动 序列(Sequence)是Phoenix提供的允许产生单调递增数字的一个SQL特性,序列会自动生成顺序递增的序列号,以实现自动提供唯一的主键值. 使用C ...
- QT_编程基础
简单介绍 Qt是一个由奇趣科技开发的跨平台C++图形用户界面应用程序开发框架.它既能够开发GUI程式,也可用于开发非GUI程式,比方控制台工具和server. Qt是面向对象语言,易于扩展,而且同意组 ...
- nand烧写分析/内核在启动过程中式如何将这个文件映射成/目录及各子目录的?
我用的是ramdisk.image.gz,烧写在flash的0x10140000处 我不太明白内核在启动过程中式如何将这个文件映射成/目录及各子目录的? 如果ramdisk.image.gz在flas ...
- JAXB
注解
JAXB(Java API for XML Binding),它提供了一个便捷的方式高速Java对象XML转变.于JAX-WS(Java的WebService规范之中的一个)中,JDK1.6 自带的版 ...
- Serializable在C#中的作用——.net中的对象序列化
序列化是指将对象实例的状态存储到存储媒体的过程,在此过程中,先将对象的公共字段和私有字段以及类的名称(包括类所在的程序集)转换为字节流,然后再把字节流写入数据流,在随后对对象进行反序列化时,将创建出与 ...
- VS插件集
Unit Test Generator 很好用的测试插件 注:在VS2015中,改名为Test generator Nunit extension了. ReSharperPlatformVs11 ...
- Java——单例设计模式
设计模式:解决某一类问题最行之有效的方法.Java中23种设计模式:单例设计模式:解决一个类在内存中只存在一个对象. 想要保证对象唯一.1,为了避免其他程序过多建立该类对象.先禁止其他程序建立该类对象 ...