今天主要介绍一下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, 请期待下文.

依然是补充内容, 还是跟继承相关, 不过这次还有模板(template).

直接看代码吧:

这是一个数组模板类, 提供了数据类型无关的一系列比较操作, 子类需要重载的就是Compare() 这个纯虚函数了. 比如有一个字符串的类:

这样的写法似乎是理所应当的, 但是考虑一下之前笔记1.1 中关于vptr, vbtl 的内容, 使用虚函数首先会层加4 个byte 的内存开销, 而且later binding 的操作过程相对于其它运算以及函数调用操作是非常慢的. 于是M$ 的童鞋们想出了不用虚函数, 也能实现动态绑定的方法, 还是继续看代码:

在模板参数中, 我们加入了子类的类型, 所以在父类中, 就可以用static_cast 来转成特定的子类类型来进行操作, 而不是到vptr 那边兜个圈子了. 而接下来只要保证String 类继承Array 类, 就不会有类型不对之类的发满出现了:

这样的做法, 既节省了vptr 和vbtl 的内存开销, 又大大提高了performance.

可能会有这样的疑问: a) 定义类的时候, 把自己的类型作为模板参数可以么? 答案是可以, template 的标准并没有说不可以. b) 定义Array 类的时候, 并不知道有Compare() 这个函数, 也可以调用? 丸子告诉你, 你一定是java写多了, C++ 就是这样的, 的确有些类型不安全.

以上. 刚才说了这是M$ 的人想出来的, 而这也是ATL(Active Template Library) 的基础.

首先解释一下, COM 这个东西真的非常的难入门啊. 1.1 只是为了说明一些补充内容, 不算正式笔记嗯.

本文主要说一下C++ 中多重继承的一些问题.

要自己写一个COM 组件, 最最最简单的大概是这样的一个结构: 写2 个interface 继承IUnknown 接口, 然后再写一个实现类实现这两个interface.

这里假设的是我们用的是C++ 来实现COM 组件. 因为C++ 中没有语言级别的interface 的支持, 所以SDK 中interface 实际上被typedef 成了struct, 以保证所有申明的接口都默认就是public 的访问权限. 于是这就涉及到了C++ 中多继承的概念. 先不说COM, 假设已经有如下的类定义:

可以把ClassA 想像成IUnknown 接口, ClassB 和ClassC 想像成接口类, ClassD 就是实现类. COM 的标准是不允许使用虚拟基类, 即被注释掉的virtual 关键字, 理由是各厂家的编译器在实现这个feature 的时候, 二进制的格式不统一. 这里牵涉到一个vptr(virtual pointer), vtbl(virtual table) 的概念. 简单来说, C++ 中任何带有虚函数的类都会存在vptr 和vtbl, 它们的作用就是负责动态绑定. vptr 和vtbl 都是编译器生成而程序员是不能手动控制的(有些编译器指令大概可以). vptr 是一个指针指向vtbl, vtbl 是一个类似数组的东西, 它保存了当前类中所有虚函数的实际绑定地址.

更详细的内容可以参考: <<Inside the C++ Object Model>> 第4.3 节. 然后有一段代码可以帮助更好的理解vptr 和vtbl: http://www.go4expert.com/forums/showthread.php?t=8403

知道了以上这些之后, 再来看如下代码:

以下是我机器上的执行结果:

惊讶吧? 作为父类实例的pC 指针跟作为子类实例的pD 指针居然不相等. 为什么呢? 就是因为vptr 的影响. 注意: 只有多重继承的时候才会这样. 单继承的时候指针的转换还是相等的, 比如pB 和pD 指针. 而相对的, 因为包含ClassA 的两重数据, pA1 和pA2 也是不相等的.

让我们把virtual 关键字加上再试试结果看会怎么样吧?

我们看到pB 和pC 之间差了4 个byte, 而之前是1 个byte. 看来虚拟基类果然把C++ 的vptr 结构改了呢. 具体怎么改的我也暂时没能力探明=v=. 说这个只是为了证明: C++ 中的虚拟基类是会破坏COM 的二进制兼容性的.

以上.