【IT168 专稿】本文详细为你阐述了如何在你的应用程序中实现LINQ to SQL。附件的示例程序包括了这里探讨的所有代码,还提供了一个简单的WPF图形界面程序来显示通过数据绑定返回的结果集。

  第一篇:步步学LINQ to SQL:将类映射到数据库表

  第二篇:步步学LINQ to SQL:使用LINQ检索数据

  本部分描述如何实现表间的映射关系: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参数,因为我的字段名字和数据库表的字段名称不同:

  [Column( Name ="Category" )] privateint? categoryId;

  2. 添加一个引用其他表的私有EntityRef类型字段

  添加一个私有类型的EntityRef字段以实现对Category实例的引用。虽然这会使得公有属性Category扮演一个后台字段的角色,但是通过使用EntityRef类型,仍然能达到延迟加载的目的(意思是LINQ不会即时从数据库获取数据直到我们真正想要数据的时候)。初始化EntityRef字段实例,以阻止NullReferenceExceptions发生。万一在某个地方你需要使用该对象的实例(如,Category)而此时又没有任何实例(Book)可用。

  private EntityRef _category =new EntityRef( );

  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实例:

  [Association( Name ="FK_Books_BookCategories",

  IsForeignKey =true, Storage ="_category", ThisKey ="categoryId" )]

  public Category Category{

  get { return _category.Entity; }

  set { _category.Entity = value; }

  }

  从M:1的关系上访问数据

  现在你可以通过这种关系以面向对象的方式访问Category的数据。例如,book.Category.Name:

  foreach( var book in bookCatalog.Books ) {

  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;

  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的主键由这两个值构成:

  [Table( Name ="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:

  [Table( Name ="Books" )]

  publicclass Book

  {

  ...

  [Column( Name ="Category" )] privateint? categoryId;

2. 映射你自己的主键

  LINQ会比较Book的外键和Category的外键,因此你需要映射Category.Id并标识其作为主键([Column (IsPrimaryKey = true)])。再次,如果你继续这个步骤,你已经在实体类中完成了该过程的创建。因此,所要做的仅仅是为该属性名Id添加一个注释:

  [Table (Name="BookCategories")]

  publicclass Category

  {

  [Column ( IsPrimaryKey =true, IsDbGenerated =true )] publicint Id { get; set; }

  ...

  3. 添加一个私有的EntitySet类型以实现对其他表的引用

  添加一个私有的EntitySet字段来接收属于该Category的书籍。这将让我们的公有Books属性扮演字段的角色。类似地,EntityRef,EntitySet会引起对Books的加载延迟直到我们实际访问它的时候(因此,当我们需要查看一个类别的时候,我们不必每次都返回所有书的列表)。

  初始化EntitySet字段以避免NullReferenceExceptions异常的发生,比如在你没有设置两边关系的时候(如一个类别没有书籍的时候)

  private EntitySet _books =new EntitySet();

  4. 为相关联的类添加属性

  最后创建公有Books属性,它用于接收在对应类别中的书籍。为该属性添加一个Association特性。并设置数据库关系的名称参数为Name (FK_Books_BookCategories),以及使用刚才创建这个字段的特性所需要的三个参数:

  ·OtherKey指定关联到其他类(Book)的字段,该字段接收所关联的外键:categoryId

  ·ThisKey指定你的主键(OtherKey所应该匹配的字段): Id

  ·Storage指定你的EntitySet类型,该类型用来存储相关联的Books集合:_books.

  在属性内部,代码getter/setter访问修饰符使用了 _books,它包涵一个实际Book实例的ICollection集合:

  [Association( Name ="FK_Books_BookCategories",

  Storage ="_books", OtherKey ="categoryId", ThisKey ="Id" )]

  public ICollection Books {

  get { return _books; }

  set { _books.Assign( value ); }

  }

  从1:M 关系上访问数据

  现在,你可以通过使用category.Books属性访问每个类别中的Books列表:

  foreach( var category in bookCatalog.Categories ){

  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步如下:

  [Table( Name ="Books" )]

  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:

  [Table( Name ="Authors" )]

  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属性并仅仅返回所查找的作者:

  IEnumerable authors = from ba in BookAuthors

  select ba.Author;

  现在,你可以在Book中将其包装成一个名位为Authors的公有、只读属性,并且(可选)通过在结果集中调用toList()返回一个ICollection集合,正如我在这里所实现的那样,为其提供一些更多的公共接口:

  public ICollection Authors { get {

  return ( from ba in BookAuthors select ba.Author ).ToList( ); } }

  当然,在Author中你可以实现相同的功能来返回一个作者的所有书籍:

  public ICollection Books { get {

  return ( from ba in BookAuthors select ba.Book ).ToList( ); } }

  从M:M的关系中访问数据

  现在,你可以完全地如愿操作你的对象模型。下面是一些例子:

  // Starting from Books...

  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 预处指令取代上面需要映射到数据库的每个类或字段:

  // disable never used/assigned warnings for fields that are being used by LINQ

  #pragma warning disable 0169, 0649

步步学LINQ to SQL:为实体类添加关系【转】的更多相关文章

  1. 步步学LINQ to SQL:使用LINQ检索数据【转】

    [IT168 专稿]该系列教程描述了如何采用手动的方式映射你的对象类到数据表(而不是使用象SqlMetal这样的自动化工具)以便能够支持数据表之间的M:M关系和使用实体类的数据绑定.即使你选择使用了自 ...

  2. 步步学LINQ to SQL:将类映射到数据库表【转】

    [IT168 专稿]该系列教程描述了如何采用手动的方式映射你的对象类到数据表(而不是使用象SqlMetal这样的自动化工具)以便能够支持数据表之间的M:M关系和使用实体类的数据绑定.即使你选择使用了自 ...

  3. LINQ to SQL 建立实体类

    使用LINQ to SQL时,需要首先建立用于映射数据库对象的模型,也就是实体类.在运行时,LINQ to SQL 根据LINQ表达式或查询运算符生成SQL语句,发送到数据库进行操作.数据库返回后,L ...

  4. LINQ to SQL 建立实体类 (转)

    http://www.cnblogs.com/DebugLZQ/archive/2012/11/14/2770449.html 使用LINQ to SQL时,需要首先建立用于映射数据库对象的模型,也就 ...

  5. 回顾:Linq To SQL语法 - 实体类

    第一篇博客,还望各位大神勿喷 小弟在此代码奉上........ 借用NorthWind数据库,实现一个商品展示的小功能.上代码: 添加对Linq的引用 using System.Data.Linq;/ ...

  6. 一步一步学Linq to sql(二):DataContext与实体

    DataContext DataContext类型(数据上下文)是System.Data.Linq命名空间下的重要类型,用于把查询句法翻译成SQL语句,以及把数据从数据库返回给调用方和把实体的修改写入 ...

  7. 一步一步学Linq to sql(五):存储过程

    普通存储过程 首先在查询分析器运行下面的代码来创建一个存储过程: create proc sp_singleresultset as set nocount on select * from cust ...

  8. (转载)一步一步学Linq to sql系列文章

    现在Linq to sql的资料还不是很多,本人水平有限,如果有错或者误导请指出,谢谢. 一步一步学Linq to sql(一):预备知识 一步一步学Linq to sql(二):DataContex ...

  9. Entity Framework中的实体类添加复合主键

    使用Code First模式实现给实体类添加复合主键,代码如下: using System; using System.Collections.Generic; using System.Compon ...

随机推荐

  1. LintCode-A + B 用位操作模拟加法

    class Solution { public: /* * @param a: The first integer * @param b: The second integer * @return: ...

  2. wireshark----教你如何抓包

    wireshark----教你如何抓包 wireshark是一款强大的抓包工具,走过路过一定不要错过就是了,当你学习TCP/IP协议的时候,学习使用wireshark 抓包正是理论联系实际最好的方法, ...

  3. ZOJ-3410Layton's Escape(优先队列+贪心)

    Layton's Escape Time Limit: 2 Seconds      Memory Limit: 65536 KB Professor Layton is a renowned arc ...

  4. 如何区分监督学习(supervised learning)和非监督学习(unsupervised learning)

    监督学习:简单来说就是给定一定的训练样本(这里一定要注意,样本是既有数据,也有数据对应的结果),利用这个样本进行训练得到一个模型(可以说是一个函数),然后利用这个模型,将所有的输入映射为相应的输出,之 ...

  5. 记事本写hello world_Java

    1 新建记事本,重命名为hello.java 2 打开hello.java,编辑并保存 class Hello { public static void main(String[] args) { S ...

  6. jsp定时方法

    jsp定时方法 $(function(){ totaladd(); //定时时触发的函数 setInterval(totaladd,);//设置定时1000=1秒 }); function total ...

  7. hdu 4750 Count The Pairs(并查集+二分)

    Problem Description With the 60th anniversary celebration of Nanjing University of Science and Techn ...

  8. java学习之部分笔记

    1.枚举类型 2.String的方法Index的用法.StringBuffer 的用法.Math的用法.Date类 3.用abstract修饰的类就是抽象类.抽象方法不能有主体.抽象类中,可以有抽象方 ...

  9. Reverse Linked List II java

    public static ListNode reverseBetween(ListNode head, int m, int n) { ListNode pre=head,current=head, ...

  10. 小项目--反eclass

    前言—— 最近会把前一段时间闲的无聊写的一些很小的项目写一些博客,用来练练手. 引子—— 最近班里有个很讨厌的软件,,,,教育局规定每个学校要上传多媒体使用记录,所以学校就给班里每台电脑上装了一个比较 ...