通过实例介绍NHibernate中提供两个实用工具SchemaExport工具利用持久化类和映射文件生成数据库架构。SchemaUpdate工具通过持久化类和映射文件更新数据库架构。
本节内容
引入
SchemaExport工具
SchemaUpdate工具
实例分析
结语
引入
我其实都是一直先编写持久化类和映射文件,然后使用SchemaExport工具生成数据库架构。这样的方式就是领域驱动设计/开发(DDD,Domain Driven Design/Development)。我的理解是系统的设计应该基于对象模型,主要考虑对象的设计和逻辑上,然后按照对象模型建立数据库关系模型,这才是现在面向对象开发的步骤,并不是上一篇先设计数据库然后再设计对象。用一幅图可以形象的说明领域驱动设计:
当在设计时,我们的领域模型需要改变,只需修改NHibernate结构和应用程序,不需要修改数据库架构,只要利用SchemaExport工具重新生成数据库架构就可以了。但是使用数据库只是其中一种方式,我们也可以使用XML文件来存储数据。
SchemaExport工具
NHibernate的hbm2dll提供SchemaExport工具:给定一个连接字符串和映射文件,不需输入其他东西就可以按照持久化类和映射文件自动生成数据库架构,现在SchemaExport工具还不是很强大,但是一般应用足够了,它还是一个相当原始的API还在不断改进。
SchemaExport工具就是把DDL脚本输出到标准输出,同时/或者执行DDL语句。SchemaExport工具提供了三个方法,分别是Drop()、Create()、Execute(),前两个方法实质是调用Execute()方法。通常使用Execute()方法来生成数据库架构的。
SchemaUpdate工具
在NHibernate2.0中新添加SchemaUpdate工具,可以用来更新数据库架构。但是我觉得没有什么作用,因为它不能Drop现有的表或列,也不能更新现有的列,只能添加新的表和列。如果我需要删除表或者列或者修改其中列,SchemaUpdate工具就显得无能为力了。
引入
我其实都是一直先编写持久化类和映射文件,然后使用SchemaExport工具生成数据库架构。这样的方式就是领域驱动设计/开发(DDD,Domain Driven Design/Development)。我的理解是系统的设计应该基于对象模型,主要考虑对象的设计和逻辑上,然后按照对象模型建立数据库关系模型,这才是现在面向对象开发的步骤,并不是上一篇先设计数据库然后再设计对象。用一幅图可以形象的说明领域驱动设计:
当在设计时,我们的领域模型需要改变,只需修改NHibernate结构和应用程序,不需要修改数据库架构,只要利用SchemaExport工具重新生成数据库架构就可以了。但是使用数据库只是其中一种方式,我们也可以使用XML文件来存储数据。
SchemaExport工具
NHibernate的hbm2dll提供SchemaExport工具:给定一个连接字符串和映射文件,不需输入其他东西就可以按照持久化类和映射文件自动生成数据库架构,现在SchemaExport工具还不是很强大,但是一般应用足够了,它还是一个相当原始的API还在不断改进。
SchemaExport工具就是把DDL脚本输出到标准输出,同时/或者执行DDL语句。SchemaExport工具提供了三个方法,分别是Drop()、Create()、Execute(),前两个方法实质是调用Execute()方法。通常使用Execute()方法来生成数据库架构的。
SchemaUpdate工具
在NHibernate2.0中新添加SchemaUpdate工具,可以用来更新数据库架构。但是我觉得没有什么作用,因为它不能Drop现有的表或列,也不能更新现有的列,只能添加新的表和列。如果我需要删除表或者列或者修改其中列,SchemaUpdate工具就显得无能为力了。
实例分析
知道了上面的知识就好好实战一下:看看具体怎么使用呢?
1.SchemaExport工具实战
通常我们使用生成数据库架构代码实例像这样:
Configuration cfg=new Configuration(); cfg.Configure(); SchemaExport export =new SchemaExport(cfg); export.Execute(....);
1.准备工作
现在数据访问测试层新建一SchemaExportFixture.cs文件用于测试生成实战。声明一个全局变量_cfg,编写 [SetUp]方法在每个测试方法执行之前调用:
[TestFixture] public class SchemaExportFixture { private Configuration _cfg; [SetUp] public void SetupContext() { _cfg = new Configuration(); _cfg.Configure(); } //测试...... }
2.测试Drop(script, export)方法
[Test] public void DropTest() { var export = new SchemaExport(_cfg); export.Drop(true, true); }
Drop(script, export)方法根据持久类和映射文件执行删除数据库架构。有两个参数,第一个为True就是把DDL语句输出到控制台,第二个为True就是根据持久类和映射文件执行删除数据库架构操作,经过调试可以发现Drop(script, export)方法其实质是执行了Execute(script, export, true, true)方法。
3.测试Create(script, export)方法
[Test] public void CreateTest() { var export = new SchemaExport(_cfg); export.Create(true, true); }
Create(script,export)方法根据持久类和映射文件先删除架构后创建删除数据库架构。有两个参数,第一个为True就是把DDL语句输出到控制台,第二个为True就是根据持久类和映射文件先执行删除再执行创建操作,经过调试可以发现这个方法其实质是执行Execute(script,export, false, true)方法。
4.测试Execute(script, export, justDrop, format)方法
[Test] public void ExecuteTest() { var export = new SchemaExport(_cfg); export.Execute(true, true, false, false); }
Execute(script, export, justDrop, format)方法根据持久类和映射文件先删除架构后创建删除数据库架构。有四个参数,第一个为True就是把DDL语句输出到控制台;第二个为True就是根据持久类和映射文件在数据库中先执行删除再执行创建操作;第三个为false表示不是仅仅执行Drop语句还执行创建操作,这个参数的不同就扩展了上面两个方法;第四个参数为false表示不是格式化输出DDL语句到控制台,是在一行输出的。
所谓格式化输出就像这样:
一行输出就像这样:
5.测试Execute(script, export, justDrop, format, connection, exportOutput)方法
[Test] public void ExecuteOutTest() { var export = new SchemaExport(_cfg); var sb = new StringBuilder(); TextWriter output = new StringWriter(sb); export.Execute(true, false, false, false, null, output); }
Execute(script, export, justDrop, format, connection, exportOutput)方法根据持久类和映射文件先删除架构后创建删除数据库架构。有六个参数,第一个为True就是把DDL语句输出到控制台;第二个为false就是不执行DDL语句;第五个为自定义连接。当export为true执行语句时必须打开连接。该方法不关闭连接,null就是使用默认连接,最后一个参数自定义输出,这里我输出到TextWriter中。
2.SchemaUpdate工具实战
现在数据访问测试层新建一SchemaUpdateFixture.cs文件用于测试生成实战。先声明一个全局变量_cfg:
private Configuration _cfg;
这里我用另外一种方式配置映射文件,先定义两个映射XML分别代表旧的和新的这样才能体现测试更新数据库架构的意义。
旧映射XML:这里我使用Product持久化类,由于在之前我们定义了Product持久化类,这里直接模拟定义映射XML:拥有主键ProductId和Name字段。
public const string product_xml = "<?xml version='1.0' encoding='utf-8' ?>" + "<hibernate-mapping xmlns='urn:nhibernate-mapping-2.2'" + " assembly='DomainModel'" + " namespace='DomainModel'>" + " <class name='DomainModel.Entities.Product,DomainModel'>" + " <id name='ProductId'>" + " <generator class='native'/>" + " </id>" + " <property name='Name'/>" + " </class>" + "</hibernate-mapping>";
新映射XML:更新上面映射XML:主键ProductId(没有改变);Name字段:添加不可为空和长度为50;另外增加了Cost字段,类型为float不可为空。
public const string newproduct_xml = "<?xml version='1.0' encoding='utf-8' ?>" + "<hibernate-mapping xmlns='urn:nhibernate-mapping-2.2'" + " assembly='DomainModel'" + " namespace='DomainModel'>" + " <class name='DomainModel.Entities.Product,DomainModel'>" + " <id name='ProductId'>" + " <generator class='native'/>" + " </id>" + " <property name='Name' not-null='true' length='50' />" + " <property name='Cost' type='float' not-null='true'/>" + " </class>" + "</hibernate-mapping>";
测试前利用旧映射XML创建数据库架构:使用[SetUp]在测试前执行,按照旧映射XML创建数据库架构并格式化输出DDL语句:
[SetUp] public void SetupContext() { //模拟旧系统 _cfg = new Configuration(); _cfg.Configure(); _cfg.AddXml(product_xml); var export = new SchemaExport(_cfg); export.Execute(true, true, false, true); }
测试更新数据库架构:使用SchemaUpdate类提供的唯一的Execute(script, doUpdate)方法按照新映射XML更新数据库架构:
[Test] public void UpdateExistingDatabaseSchemaTest() { _cfg = new Configuration(); _cfg.Configure().AddXml(newproduct_xml); var update = new SchemaUpdate(_cfg); update.Execute(true, true); }
测试输出结果如图所示,如果你觉得不放心再看看数据库Product表。
看到了吗?这显然不是我要求的,首先按照旧映射XML创建了数据库架构,但是更新数据库架构显得无能为力,仅仅增加了Cost字段,我想更新Name字段属性为不可为空和长度为50,但是SchemaUpdate工具不能做到!我觉得这个类目前还没有什么作用,期待下一个版本来完善。
结语
这篇文章通过实例介绍NHibernate中提供两个实用工具SchemaExport工具利用持久化类和映射文件生成数据库架构。SchemaUpdate工具通过持久化类和映射文件更新数据库架构。
引入
上篇我们初步探索了SchemaExport工具使用,知道如何使用SchemaExport工具和SchemaUpdate工具利用NHibernate持久化类和映射文件删除、创建、更新数据库架构,这篇具体分析如何为表字段增加一些约束?如何生成存储过程?如何生成视图?使用SchemaExport工具帮你搞定。
实例分析
1.表及其约束
众所周知,SchemaExport工具根据映射文件来生成数据库架构,在映射文件中通过Class映射可以很方便的生成数据库表。但是这篇我们看看映射的条件,所以我重新定义两个实体CategorySchema和ProductSchema,一对多关系。
Step1:两个实体持久化类编写代码如下:
public class CategorySchema { public virtual Guid Id { get; set; } public virtual string Name { get; set; } } public class ProductSchema { public virtual Guid Id { get; set; } public virtual string Name { get; set; } public virtual int UnitsOnStock { get; set; } public virtual CategorySchema CategorySchema { get; set; } }
Step2:为两个实体映射,使用最简方式,编写代码如下:
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="DomainModel" namespace="DomainModel"> <class name="DomainModel.Entities.CategorySchema,DomainModel"> <id name="Id"> <generator class="guid"/> </id> <property name="Name"/> </class> <class name="DomainModel.Entities.ProductSchema,DomainModel"> <id name="Id"> <generator class="guid"/> </id> <property name="Name"/> <many-to-one name="CategorySchema" class="DomainModel.Entities.CategorySchema,DomainModel"/> </class> </hibernate-mapping>
Step3:编写测试用例用于生成数据库架构:
[Test] public void ExecuteSchemaTest() { var export = new SchemaExport(_cfg); export.Execute(true, true, false, true); }
Step4:测试!NHibernate生成语句如下:
if exists (select 1 from sys.objects where object_id = OBJECT_ID(N'[FK45BBFB51BC9515A6]') AND parent_object_id = OBJECT_ID('ProductSchema')) alter table ProductSchema drop constraint FK45BBFB51BC9515A6 if exists (select * from dbo.sysobjects where id = object_id(N'ProductSchema') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table ProductSchema if exists (select * from dbo.sysobjects where id = object_id(N'CategorySchema') and OBJECTPROPERTY(id, N'IsUserTable') = 1) drop table CategorySchema create table ProductSchema ( Id UNIQUEIDENTIFIER not null, Name NVARCHAR(255) null, CategorySchema UNIQUEIDENTIFIER null, primary key (Id) ) create table CategorySchema ( Id UNIQUEIDENTIFIER not null, Name NVARCHAR(255) null, primary key (Id) ) alter table ProductSchema add constraint FK45BBFB51BC9515A6 foreign key (CategorySchema) references CategorySchema
仔细看看生成的语句,都按默认的值生成了表,Name列字符串类型NVARCHAR(255),默认为null;外键默认一数字字符串等。
1.设置非空类型和长度
在映射文件中为ProductSchema实体的Name属性添加:not-null="true"表示非空类型,length="50":列长度设置为50,代码片段如下:
<property name="Name" not-null="true" length="50"/>
测试,生成语句如下:
create table ProductSchema ( Id UNIQUEIDENTIFIER not null, Name NVARCHAR(50) not null, CategorySchema UNIQUEIDENTIFIER null, primary key (Id) )
2.设置外键Foreign Keys
在映射文件设置外键名称,注意有的需要两边都要设置才生效,代码片段如下:
<many-to-one name="CategorySchema" class="DomainModel.Entities.CategorySchema,DomainModel" foreign-key="FK_Product_Category"/>
生成语句如下:
if exists (select 1 from sys.objects where object_id = OBJECT_ID(N'[FK_Product_Category]') AND parent_object_id = OBJECT_ID('ProductSchema')) alter table ProductSchema drop constraint FK_Product_Category ... alter table ProductSchema add constraint FK_Product_Category foreign key (CategorySchema) references CategorySchema
3.设置Unique约束
我们要求Name字段唯一,添加Unique约束,代码片段如下:
<property name="Name" not-null="true" length="50" unique="true"/>
生成语句如下:
create table ProductSchema ( Id UNIQUEIDENTIFIER not null, Name NVARCHAR(50) not null unique, CategorySchema UNIQUEIDENTIFIER null, primary key (Id) )
还有一种unique-key约束,同时为两个属性设置unique-key约束。我们为Customer持久化类的FirstName和LastName属性添加Unique约束:
编写映射,设置FirstName和LastName的Unique约束为UK_Person_Name:
<property name="FirstName" not-null="true" length="50" unique-key="UK_Customer_Name"/> <property name="LastName" not-null="true" length="50" unique-key="UK_Customer_Name"/>
生成语句如下:
create table Customer( CustomerId INT IDENTITY not null, FirstName NVARCHAR(50) not null, LastName NVARCHAR(50) not null, primary key (CustomerId), unique (FirstName, LastName) )
4.设置索引Index
<property name="Name" not-null="true" length="50" unique="true" index="IDX_Product_Name" />
生成语句如下:
create table ProductSchema (Id UNIQUEIDENTIFIER...) create table CategorySchema (Id UNIQUEIDENTIFIER...) create index IDX_Product_Name on ProductSchema (Name)
5.设置Check约束
我们为UnitsOnStock值设置大于等于0:
<property name="UnitsOnStock" not-null="true" > <column name="UnitsOnStock" check="UnitsOnStock >= 0"/> </property>
生成语句如下:
create table ProductSchema ( Id UNIQUEIDENTIFIER not null, Name NVARCHAR(50) not null unique, UnitsOnStock INT null check( UnitsOnStock >= 0) , CategorySchema UNIQUEIDENTIFIER null, primary key (Id) )
好了,还有很多设置大家自己探索啦!知道了这些我们在写映射的时候就注意啦!我之前映射文件属性映射为什么写的比较全呢?就是这个原因,便于生成数据库架构是自己约束的并不是默认的。
2.存储过程、视图
除了表,我们还有存储过程和视图。怎么利用SchemaExport工具生成存储过程和视图呢?在映射文件中提供了database-object元素用来创建和删除数据库对象。
<database-object> <create>创建存储过程或视图语句等数据库对象</create> <drop>删除存储过程或视图语句等数据库对象</drop> </database-object>
1.存储过程
还记得我们在NHibernate之旅(17):探索NHibernate中使用存储过程(下)中创建的三个存储过程了吗?当时我们在数据库中手写创建的啊。现在完全可以不用那种古老的方法啦。我们在映射文件中编写database-object:在创建数据库架构时创建名为entitySProcs的存储过程,在删除数据库架构时删除名为entitySProcs的存储过程。现在使用SchemaExport工具不仅仅生成了表,还生成了存储过程。还剩两个存储过程就留给大家去完成吧。
<database-object> <create> CREATE PROCEDURE entitySProcs AS SELECT CustomerId,Version,Firstname,Lastname FROM Customer </create> <drop> DROP PROCEDURE entitySProcs </drop> </database-object>
2.视图
还记得我们在NHibernate之旅(14):探索NHibernate中使用视图中创建了第一个视图了吗?那时我们也是在数据库中手动创建的,现在我们可以自动创建了!打开CustomerView.hbm.xml文件,添加database-object:在创建数据库架构时创建名为viewCustomer的视图,在删除数据库架构时删除名为viewCustomer的视图。
<database-object> <create> CREATE VIEW [dbo].[viewCustomer] AS SELECT DISTINCT c.CustomerId, c.Firstname, c.Lastname, o.OrderId, o.OrderDate FROM dbo.Customer AS c INNER JOIN dbo.[Order] AS o ON c.CustomerId = o.OrderId INNER JOIN dbo.OrderProduct AS op ON o.OrderId = op.[Order] INNER JOIN dbo.Product AS p ON op.Product = p.ProductId GROUP BY c.CustomerId, c.Firstname, c.Lastname, o.OrderId, o.OrderDate </create> <drop>drop view dbo.viewCustomer</drop> </database-object>
测试生成数据库架构,出现错误“数据库中已存在名为'viewCustomer'的对象”。这是什么原因呢?看看NHibernate生成语句:
create table viewCustomer ( CustomerId INT IDENTITY NOT NULL, Firstname NVARCHAR(255) null, Lastname NVARCHAR(255) null, OrderId INT null, OrderDate DATETIME null, primary key (CustomerId) )
观察NHibernate生成SQL语句发现NHibernate利用Class映射自动生成了viewCustomer表,因为NHibernate见到Class映射就认为是表,它不知道这里映射的是视图,视图和表在映射文件中没有什么区别。我们修改一下这个映射文件,在database-object元素上面再添加一个database-object用于删除NHibernate生成的表。
<database-object> <create>drop table dbo.viewCustomer</create> <drop>drop table dbo.viewCustomer</drop> </database-object>
这个database-object的意思就是在创建数据库架构时删除NHibernate自动生成的表viewCustomer,在删除数据库架构时删除表viewCustomer。为什么删除两次呢?因为我们有可能只Drop,那么NHibernate就执行database-object中的Drop。
这样在CustomerView.hbm.xml文件中就有两个database-object了,总体的意思就是在创建数据库架构时删除NHibernate自动生成的表viewCustomer并创建名为viewCustomer的视图,在删除数据库架构时删除名为viewCustomer表和名为viewCustomer的视图。
结语
好了,终于把SchemaExport工具研究透彻了,应该没有说漏别的东西,就到这里了。下面看看NHibernate对象去,什么状态啦,缓存啦。
本系列链接:NHibernate之旅系列文章导航