今天主要介绍一下LINQ(Language Integrated Query) 在ADO.NET 中的使用, 即LINQ to ADO.NET.

LINQ to ADO.NET 有两个model: LINQ to DataSet 和LINQ to SQL. 我不想讲LINQ 的语法, 主要讲架构.

首先是LINQ to DataSet. 显然, 这套API 就是在DataSet 这套API 上加入了LINQ 的支持. 这个附加的namespace叫做System.Data.DataExtension.dll. 于是我们看到了一个为了LINQ 在.NET 3.5 新加的特性叫做extension method. Extension method 的作用是扩展现有类, 但是不修改源码, 不使用继承或者组合. 只要新加几个函数, 就能让原来的类包含新的方法, 像这些方法本来就被定义了一样. 比如有一个DataTableExtensions 类, 它就在DataTable 类上新加入了AsEnumerable() 方法, 从而把DataTable 类转化成LINQ 兼容的接口类型IEnumerable<T>. 关于extension method 请查阅MSDN.

然后是LINQ to SQL. 这套API 的目的是要提供数据库到代码的一致的映射关系, 也就是说, 我们不要直接操作int, string 等基本数据类型, 而是操作具体的代码层的映射类. 可以跟java 中的hibernate 比对一下, hibernate 里的映射类叫做POJO(Plain Ordinary Java Objects), 而.NET 中则叫做实体类(entity class). 丸子已经很久没写过java 了, java 的东西几乎都忘了. 翻了些网页发现javaee 持久性框架也使用了entiry class 的概念, 跟hibernate 不同的是, 大量使用了annotation, 从而使hibernate 里出现的mapping 文件没有必要使用了. 具体参照: http://salto-db.sourceforge.net/salto-db-generator/plugins/ejb3hibernatedao.html. 而.NET 中与annotation 对应的则是attribute.

要使用LINQ to SQL, 就必须要创建实体类. 那我们用什么对象来访问实体类呢? .NET 中有一个叫做DataContext 的类, 对比java 中hibernate 的话, 可以理解为一个Session 对象. 一般的DataContext 的用法大概是这样:

这里的XXTable 类假设是已经定义的实体类. 这样以后就可以对这个xxTable 对象进行操作了. 更好的方法是定义一个strongly typed 的DataContext 类:

这又是一个非常神奇的类. 假设MyTable1 和MyTable2 都是已定义的实体类, 所有我们所要做的只是简单的把它们定义成public 的field. 每次当我们调用MyDataContext 的时候, 这些table 的field 会自动被填充. 根据reflector 的trace, 大概是用到了reflection 的机制. 之所以要写一个strongly type 的DataContext, 当然是为了不要繁琐的调用GetTable() 这个方法.

不过, strongly type 的DataContext 的最主要的作用, 还是从对entity class 的操作转换为对数据库的操作. 再回忆一下, 为什么要有entity class? 因为LINQ 的需要, 需要一个跟数据库一一映射的代码类. 于是, 所有的东西都联系起来了.

vs2008 提供了wizard 来生产这样的DataContext, 菜单位于project–>add new item–>data–>linq to sql classes. 我们有了这个DataContext 之后, 所有的操作应该都是OO 的了, 也会方便很多.

最后, 从.NET 3.0 开始, assembly 的位置似乎发生了变化. v2.0 的位置在: C:\Windows\Microsoft.NET\Framework, 而v3.0/v3.5 的位置则在: C:\Program Files\Reference Assemblies\Microsoft\Framework.

Disconnected Layer, 在内存中进行把数据库的操作, 然后把这些操作更新回数据库.

这里有一个data adaptor 的概念. 它使用DataSet 类来进行内存中和数据库中数据的移动和更新. Disconnect Layer 只保持最短时间的数据库连接, 所有的数据库操作都是针对内存中的DataSet 的. 所有操作完成后, 再一次完全更新回实际的数据库. 一个DataSet 类中包含三个collection: DataTableCollection, DataRelationCollection 和PropertyRelation, 分别表示所有的table, 所有的relation, 以及这个dataset 中一些附加的属性.

这里又有个东西叫做data-binding. 所谓的数据绑定, 意思就是说把数据(data)和界面(UI) 联系起来, 当数据发生变化的时候, 界面也会做出相应的改变. 好处是节省了很多手动需要refresh 的代码, 坏处确实这个机制会比较慢. 在windows form 中, 我们可以把一个DataTable 类的实例绑定到一个DataGridView 的DataSource 属性上. DataTable 这个类另外支持一个filter criteria 的功能, 其实就是一个查询条件. 这个功能个人觉得比较弱, 没有java 中hibernate 的HQL 的OO 特性, 也不像hibernate 的Criteria 类完全强类型那么好用.

有一个神奇的类DbCommandBuilder. 这个类可以简化与DataTable 类的CRUD (create/read/update/delete) 操作. 已经提过, 在Disconnected Layer 中, 是通过data adaptor 来进行CRUD 操作的. 拿SqlDataAdaptor 这个类来说, 使用前需要指定它的四个Command 属性: SelectCommand, InsertCommand, DeleteCommand, UpdateCommand. 于是有一个SqlCommandBuilder 类, 它可以根据设定的SelectCommand 来生产其它三个Command 对象. 猜想一下, 应该是根据SelectCommand 可以拿到的DB Scheme 来构造其它Command 的, 而事实就是如此. 缺点是使用DbCommandBuilder 有诸多限制, 请查阅MSDN.

最后, 要提到一个DAL(Data Access Library) 的概念, 可以跟java 中DAO 联系一下, 其实就是要封装了一些数据库的操作. 在windows form 的DataGridView 的设计界面上, 我们不但可以用wizard 来绑定数据, 同时还生成了所谓的DAL. 生产的代码就是strong-type 的DataSet 和DataTable 类型. 另外, 即使不使用windows form, vs2008 也提供了生产DAL 的wizard, 菜单位于project–>add new iterm–>data–>dataset. 注意, 这里生产的代码还不是OO 的, 我们操作的依然是基本数据类型, 并没有java 中hibernate 的所谓entiry class 相关概念. 关于entiry class, 会在之后的LINQ 中介绍.

即日起将开始记录.NET 相关的一些笔记, 将会涉及ADO.NET, WPF, WCF, WF. 今天主要是ADO.NET 的内容.

ADO(Active Data Object) 是M$ 的一个数据访问模型, ADO.NET 自然就是这个东西的.NET 升级版. ADO.NET 可以以两种方式使用: 连接的(connected) 和非连接的(disconnected).

Connected Layer, 可以想象成java 里的jdbc 的数据库访问模型. 我们通过一个Connection 对象连接到数据库, 然后用这个Connection 对象创建一个Statement 对象用来select/insert/delete/update 数据库, 最后调用这个Statement 的executeQuery()/executeUpdate() 方法来完成实际的数据库操作.

这些东西在.NET 里的对应关系大概是这样: Connection–>Connection, Statement–>Command, ResultSet–>DataReader. 这里有一个data provider 的概念, 一个data provider 定义了一系列的数据类型, 用来针对某个特定的数据库类型. 比如针对SQLServer 和Oracle, 分别有(SqlConnection, OracleConnection 类), (SqlCommand, OracleCommand 类) 等. 从类库中来看的话, 一个data provider 实际对应了一个namespace, 比如SQLServer–>System.Data.SqlClient, Oracle–>System.Data.OracleClient. 而在这些namespace 中, 又分别有所谓的provider factory, 比如SqlClientFactory, OracleClientFactory 类, 这些factory 类都继承DbProviderFactory 这个基类.

也许有些晕了, 丸子sensei 告诉你, 这里就是一个Abstract Factory 的设计模式. 把DbProviderFactory 这个类看出抽象基类, coding 的时候把数据库相关的具体实现类用基类来引用. 我们知道一个具体的factory 实际上就是用来创建数据库相关的具体操作类的. 所以通过abstract factory 的这一层抽象, 我们做到了全部用抽象方法的调用. 但是还有一个问题, 那就是当我们把一个具体的DbProviderFactory 赋给基类来使用, 是要手动改代码的, 有没有一中方法可以随便换data provider 而不用改代码呢? 显然, 就是使用一个叫做DbProviderFactories 的类, 这个类有个方法可以接受string 来返回一个DbProviderFactory 的具体实现类.

其实java 用的也是类似的abstract factory 模式, 你一定用 Class.forName(“xxx.xxx.xxxDriver”).newInstance() 来注册一个driver, 然后再用DriverManager 类的static 方法来返回一个Connection 对象. jdk6.0 中开始包含了jdbc4.0 的新特性, Class.forName() 可以不必调用了.

关于Disconnected Layer, 请期待下文.