代码改变世界

走进Linq-Linq to SQL How do I(3)

2008-08-06 01:07  横刀天笑  阅读(3263)  评论(11编辑  收藏
NHibernate是使用XML作为映射的配置文件,Caslte中的ActiveRecord(底层还是使用NHibernate)是使用Attribute的方式做映射配置。一个是非侵入的,一个是侵入的。有人喜欢用XML做配置,说这样灵活,修改配置无需重新编译,有人喜欢使用Attribute的配置方式,说这样可维护性好,可以得到编译期的检查。

不过在Linq里,这一切都不是问题。Linq to SQL这几种方式都支持,而且走的更远。

Attribute方式配置

XMl文件的配置方式

使用SqlMetal命令行工具生成配置代码

使用Visual Studio的设计器生成映射配置代码

 

Attribute方式配置

前面几篇我们都是使用这种Attribute的方式的,在Linq to SQL的第一篇我就介绍了,这些映射的Attribute都存在于System.Data.Linq.Mapping命名空间下。在前面我们已经出现了TableAttribute,ColumnAttribute, AssociationAttribute。实际上还有DataBaseAttributeProviderAttributeFunctionAttribute。本篇后面的内容将在不同的地方对他们做全面的介绍。

Table

Table特性是加在类上面的,不能重复的加,也不能继承。Table,顾名思义,就是用来定义类和数据库表之间的映射的,Table是映射定义的关键点,如果没有给一个类加上Table特性,那么即使这个类里面的属性或字段加上了Column也是无效的,实际上,前面几篇中的例子,如果没有给映射类加Table特性在运行时会抛出一个异常:System.InvalidOperationException,说你的类没有映射为一个TableTable特性只有一个属性Name,用于在你的类名和数据库表名不同的时候来定义。

Column

Column特性就复杂得多了。不过可喜的是,这些属性的命名都很好,只要见到名字了,基本上就能把意思给猜出来了。

AutoSync

AutoSync:自动同步。这个属性是一个枚举类型:

public enum AutoSync

{

    Default,

    Always,

    Never,

    OnInsert,

    OnUpdate

}

这个属性的意思是,执行insertupdate操作后,这个类的属性如何和数据库表的那个被修改列的字段进行同步。

AutoSync. Default,自动选择,默认就是这个,一般是如果该列在数据库里有默认值,ColumnIsDbGenerated属性标记为true的时候则同步。

AutoSync. Always,总是进行同步

AutoSync.Never,从来不同步

AutoSync.OnInsert,在执行插入操作后同步,像我们博客园的那个例子,BlogId是主键,自增的,插入数据库的时候我们并不提供,而是数据库自动生成的,那这个时候我们的类插入以后这个Id如何同步呢?

AutoSync. OnUpdate,在更新的时候同步

 

实际上我们可以看看,这个AutoSync是如何影响Linq to SQL的行为的(打开前面我们曾经建的那个博客园的例子,如果你没有创建一个强烈建议你回到前几篇按照那里的步骤新建)

我们执行一下对Post的插入:

            DataContext dbContext = new DataContext(ConfigurationManager.ConnectionStrings["CnBlogs"].ConnectionString);

 

            dbContext.Log = Console.Out;

            var post = new Post {

                BlogId = 1,

                Title = "Linq to SQL How do I(3)",

                Body = "废话一堆",

            };

 

            dbContext.GetTable<Post>().InsertOnSubmit(post);

            dbContext.SubmitChanges();

看看生成的SQL代码:

insert的代码下面,我们还会看到一个select的代码,将postidcreatedate给查询回来,但我们并没有执行查询操作啊,原来这就是AutoSync在使坏,在默认的时候IsDbGeneratedtrue的列是被会查出来返回的。为了检查一下说的是不是对的,我将PostTitle属性修改一下:

        [Column(AutoSync=AutoSync.Always)]

        public string Title { get; set; }

然后再执行上面 插入代码,看看生成的SQL又将如何呢:

呵呵,生成的select语句也将Title给查回来了。

现在应该明白这个AutoSync的意义了吧,你可以自己一一试验。

CanBeNull

这个属性可以指定对应的数据库表的列是否允许是null的,如果这个列不能为null,那你给这个属性赋个null的时候,是要触发异常的。不过要记住,null并不代表是一个空字符串或者零,关于null的更多内容,你可以参见园子Anytao的佳作。

DbType

如果你想使用DataContextCreateDataBase方法从映射类创建数据库表,那么最好指定这个,这样你就可以明确的指定出你的这个列在数据库表中的DbType是啥,不然Linq to SQL会从属性的类型推断DbType,这有可能不怎么适合,比如这个string类型:

        [Column(DbType="NVarchar(50) not null")]

        public string Title { get; set; }

在这里我们是不是发现了,我们还可以从映射类创建数据库,都说ORM是以O为主,可我们实践的时候总是先建立数据库,然后根据数据库创建Object,那这不是ROM了么,从这里的信息表示,你可以先设计好Domain Object,并建立好Object之间的关系,最后使用CreateDataBase方法来创建数据库表。关于CreateDataBase更多的信息在后面我会更进一步的说明的。

Expression

我只能说Linq to SQL做的太周到了,连这个都考虑了。Expression用来表示一个计算列,什么意思?意思是这个属性是数据库表的列通过计算获取的,比如,假如我们数据库里有一列price存储的是美元,可是取出来的时候我们要求用人民币表示:

        [Column(Expression="price * 6")]

        public float Price

        { get; set; }

上面的例子只是用来说明Expression这个属性的用途,而这个例子的做法实在是不可取,对于美元转人民币这种汇率问题,汇率是时刻波动的,所以这样硬编码,实在是糟糕的很,各位读了以后BS一下,一笑而过吧。

IsDbGenerated

从名字上就可以看出来了,这个表明这个属性的值是数据库产生的,不需要我们的程序赋值,比如这个自增的列,比如这个有默认值的列(时间,我们可以用SQL getdate()函数设置值,而无需我们在程序里指定)

IsPrimaryKey

是主键吗?如果是就要指定这个属性了,如果你的主键是多个列组成的,那么就在多个属性上加吧。关于主键,在后面还会进一步介绍。

IsVersionUpdateCheck

这两个在这里就不说了,后面会有大篇幅的介绍的。

这个Column特性是从抽象特性Data继承来的,从这个特性继承了两个属性:

Name

这个是当你的属性名字和这个列名不一致的时候指定列名用的。

Storage

有的时候啊,你的属性里的set里面应用了复杂的业务逻辑,而Linq to SQL在将值从数据库取出,然后赋值给这个属性的时候,默认是要使用这个set的,这个时候在这种情况下(从数据库取值赋给类的属性),你并不想执行这个set里面的逻辑,想把这个值直接给属性背后的那个私有字段:

        private float _price;

        /// <summary>

        /// 由于某些原因,当从数据库取值给Price属性的时候

        /// 你不想给这个值乘以20

        /// </summary>

        [Column(Storage="_price")]

        public float Price

        {

            get { return _price; }

            set{_price = _price * 20;}

Column的就到此为止了,我们现在来介绍Association特性。

Association

这个特性是用来建立实体之间关系的。在前面的例子里我们看到了:

        /// <summary>

        /// 一个博客有零篇或多篇文章,

        /// </summary>

        [Association(ThisKey="Id",OtherKey = "BlogId")]

        public EntitySet<Post> Posts { get; set; }

Association有两个最重要的属性就是ThisKeyOtherKey

ThisKey用来标识和别的对象关联的键,如果没有指明就用本类属性上标识有IsPrimaryKey的了。OtherKey用来定义关联的类的键,如果没有指定就用关联的那个类的标识列了。

Association特性也是从Data特性继承来的,也有NameStorage属性。Storage属性和Column是一样的,这里的Name属性是CreateDataBase利用映射类动态创建数据库的时候建立关系用的,这个Name就是关系名。关于Association的更多信息你还是参看上一篇文章。

我们用的最多的就是上面三个Attribute了,不过Linq to SQL还提供好几个Attribute,我想这个放在后面相应的地方再做介绍。

 

XML文件的配置方式

有心的你也许发现了,DataContext类有好几个重载的构造函数,我们常用的是:

DataContext(string fileOrConnectionString)

还有一个:

DataContext(string fileOrConnectionString,MappingSource mapping)

这个MappingSource是干吗的呢?

System.Data.Linq.Mapping命名空间下,你会发现这样的个关系:

看到它的两个子类的名称你也许就会猜出十之八九了吧,我们前面所使用的就是AttributeMappingSouce映射,除此之外还可以使用XML作为映射的配置文件哦,从上面的图看,XmlMappingSource还有几个静态的方法,他们可以以各种形式的Xml数据源来构建XmlMappingSource实例:

XmlMappingSource mapping = XmlMappingSource.FromXml(File.ReadAllText(@“e:"cnblogs"map.xml”));

DataContext dbContext = new DataContext(connectionString,mapping);

用这种方式构建DataContext对象我们就可以使用Xml作映射了。

我们就来看看这个XML映射文件的格式:

<?xml version="1.0" encoding="utf-16"?>

<Database Name="Cnblogs" xmlns="http://schemas.microsoft.com/linqtosql/mapping/2007">

 <Table Name="posts">

    <Type Name="Yuyijq.Linq.Cnblogs.Domain.Post">

      <Column Name="postid" Member="Id" IsPrimaryKey="true" IsDbGenerated="true" />

      <Column Name="blogid" Member="BlogId" />

      <Column Name="title" Member="Title" />

      <Column Name="body" Member="Body" />

      <Column Name="createdate" Member="CreateDate" IsDbGenerated="true"/>

    </Type>

 </Table>

</Database>

大家可以看到,这个文件有一个根元素DataBase,这个对应着Attribute里的DataBaseAttribute,每个DataBase元素可以包含有一个或多个Table,每个Table元素有一个Type元素,这个是用来指定这个Table和哪个实体类对应的。一个Type元素有一个或多个ColumnAssociation元素。从上面的Xml中可以看出,它的用法和Attribute的一样,只是多了一个Member,这个是用来指定实体类的属性的,其余的意义和Attribute的是一样的。 

基本上每个ORM工具都提供了代码生成工具,我想如果没有代码生成使用ORM的工作量也是不小的,靠手动的去写实体类和映射文件纯粹是个体力活。Linq to SQL也不例外,为你提供了强大的工具。

使用SqlMetal命令行工具生成配置代码

SqlMetal是个命令行工具,这样你就可以很好的将其与MSBuild等进行集成,以达到自动化的目的。只要写个批处理文件,映射的东东都自动生成了。

关于SqlMetal的用法你只要在命令行里输入SqlMetal /?就可以了,这里就不做过多的介绍。

值得注意的是:我们写的实体类往往希望他们能通过WebServiceWCF传递,那么就要求它们是可系列化的,为此微软为SqlMetal工具准备了这个开关:

/serialization:<option>option有两个值:NoneUnidirectional,默认是None

使用Visual Studio的设计器生成映射配置代码

毕竟整天伴随着我们的是VS,所以还是来个可视化的工具比较过瘾。

你只要在你的项目当中新建一个LINQ to SQL Classes类型的新项,然后从服务器浏览器里把表拖到设计界面上,然后用工具栏里面的工具,连接好各个表之间的关系就OK了。

VS会为我们生成三个文件:一个以dbml为后缀的文件(其实这个文件就是个xml格式的文件,它和刚才只用xml作映射配置的文件格式是类似的)一个C#代码文件,注意到这个代码文件是个局部类,该类和dbml文件共同编译成一个类,这个和asp.net里面的aspx和它的后台代码使用的方式类似。还有一个是以layout为后缀名的,这个文件是设计器使用的,用来存储设计器中各表在界面上的位置,和我们的程序无关。

关于可视化的设计器的使用这里也不做介绍了,自己动动手,然后看看生成的几个文件里到底是什么东西,加上前面对手动操作的详细讲解,我想理解这个将是很容易的事情。

 

后记

本来想用三篇的长度将如何使用部分,看来还是不行。这篇文章比较长,但东西不是很多。

下一篇将是How do I的最后一篇,将对DataContext,做UpdateDeleteRowVersionLinq to SQL中的事务和延迟计算、延迟加载等内容做详细介绍。