前面几篇文章简单的介绍了ActiveRecord中的基本映射以及构建配置信息,本文我们用ActiveRecord里面的Blog,Post例子来实现One-Many/Many-One关联。
主要内容
1.准备数据表结构
2.编写实体类并介绍HasMany和BlongsTo特性
3.构建配置信息
4.编写测试代码
摘要:前面几篇文章简单的介绍了ActiveRecord中的基本映射以及构建配置信息,本文我们用ActiveRecord里面的Blog,Post例子来实现One-Many/Many-One关联。
主要内容
1.准备数据表结构
2.编写实体类并介绍HasMany和BlongsTo特性
3.构建配置信息
4.编写测试代码
一.准备数据表结构
在这个例子中,我们引入了两个对象Blog、Post,它们之间的关系是一对多,即一个Blog有多篇Post。需要用到的数据表结构如下
CREATE TABLE Blogs (

blog_id int IDENTITY(1, 1) PRIMARY KEY,

blog_name varchar(50),

blog_author varchar(50)

)


CREATE TABLE Posts (

post_id int IDENTITY(1, 1) PRIMARY KEY,

post_title varchar(50),

post_contents text,

post_categories varchar(50),

post_blogid int FOREIGN KEY REFERENCES Blogs (blog_id),

post_created datetime,

post_published bit

)
二.编写实体类
首先我们来看Blog实体类的编写,需要用到HasMany特性,这时我们会在Blog实体类中定义一个Posts属性,用它来表示该Blog所发表的所有Posts,代码如下
[ActiveRecord("Blogs")]

public class Blog : ActiveRecordBase



{

//……

private IList _posts;


[HasMany(typeof(Post), Table="posts", ColumnKey="post_blogid")]

public IList Posts


{


get
{ return _posts; }


set
{ _posts = value; }

}

}
HasManyAttribute说明
属性
|
说明
|
示例
|
Cascade
|
指明哪些操作会从父对象级联到关联的对象,相关的操作见后面,如果不指定,则为None
|
Cascade=ManyRelationCascadeEnum.All
|
Inverse
|
指定是否级联操作
|
Inverse =true|false
|
Schema
|
指定Schema的名字
|
Schema="ARDemo"
|
Table
|
指定持久化类所关联的数据库表名,如果表名与类名相同,可以省略
|
Table="posts"
|
ColumnKey
|
指定关联类的一个属性,这个属性将会和本外键相对应。
|
ColumnKey="post_blogid"
|
Where
|
指定一个附加SQL的Where子句
|
Where="IsPost = 0"
|
Lazy
|
指定是否延迟加载关联对象
|
Lazy=true|false
|
Cascade的类型值有如下几种
类型
|
说明
|
None
|
不进行级联操作
|
SaveUpdate
|
进行级联Save/Update操作
|
Delete
|
进行级联Delete操作
|
All
|
进行级联Save/Update/Delete操作
|
AllDeleteOrphan
|
进行级联Save/Update/Delete操作,并删除无相关父对象的子对象
|
在Post实体类中,我们需要定义一个Blog类型的属性,并且用到BlongsTo特性,即一个Post属于某一个Blog,代码如下:
[ActiveRecord("Posts")]

public class Post : ActiveRecordBase



{

//……

private Blog _blog;


[BelongsTo("blogid")]

public Blog Blog


{


get
{ return _blog; }


set
{ _blog = value; }

}

}
BelongsToAttribute说明
属性
|
说明
|
示例
|
Cascade
|
指定级联操作
|
Cascade=CascadeEnum.SaveUpdate
|
Column
|
列名,外键字段名
|
BelongsTo("blogid")
Column="blogid"
|
Insert
|
是否允许增加
|
Insert=true|false
|
NotNull
|
是否允许为空
|
NotNull =true|false
|
OuterJoin
|
是否允许外连接抓取
|
OuterJoin=OuterJoinEnum.True
|
Unique
|
是否唯一
|
Unique =true|false
|
Update
|
是否允许更新
|
Update =true|false
|
Cascade类型如下
选项
|
说明
|
None
|
默认值,不进行级联操作
|
All
|
进行级联Save/Update/Delete操作
|
SaveUpdate
|
进行级联Save/Update操作
|
Delete
|
进行级联Delete操作
|
OuterJoin选项有三个:Auto,True,False
最后完整的Blog实体类如下

/**//// <summary>

/// Blog 的摘要说明。

/// </summary>
[ActiveRecord("Blogs")]

public class Blog : ActiveRecordBase



{

private int _id;


private String _name;


private String _author;


private IList _posts;


[PrimaryKey(PrimaryKeyType.Native, "blog_id")]

public int Id


{


get
{ return _id; }


set
{ _id = value; }

}


[Property("blog_name")]

public String Name


{


get
{ return _name; }


set
{ _name = value; }

}


[Property("blog_author")]

public String Author


{


get
{ return _author; }


set
{ _author = value; }

}


[HasMany(typeof(Post), Table="posts", ColumnKey="post_blogid")]

public IList Posts


{


get
{ return _posts; }


set
{ _posts = value; }

}


public static void DeleteAll()


{

DeleteAll( typeof(Blog) );

}


public static Blog[] FindAll()


{

return (Blog[]) FindAll( typeof(Blog) );

}


public static Blog Find(int id)


{

return (Blog) FindByPrimaryKey( typeof(Blog), id );

}

}
完整的Post类如下


/**//// <summary>

/// Post 的摘要说明。

/// </summary>
[ActiveRecord("Posts")]

public class Post : ActiveRecordBase



{

private int _id;


private String _title;


private String _contents;


private String _category;


private DateTime _created;


private bool _published;


private Blog _blog;


public Post()


{

_created = DateTime.Now;

}


public Post(Blog blog, String title, String contents, String category) : this()


{

_blog = blog;

_title = title;

_contents = contents;

_category = category;

}


[PrimaryKey(PrimaryKeyType.Native,"post_id")]

public int Id


{


get
{ return _id; }


set
{ _id = value; }

}


[Property("post_title")]

public String Title


{


get
{ return _title; }


set
{ _title = value; }

}


[Property(Column="post_contents",ColumnType="StringClob")]

public String Contents


{


get
{ return _contents; }


set
{ _contents = value; }

}


[Property("post_categories")]

public String Category


{


get
{ return _category; }


set
{ _category = value; }

}


[BelongsTo("post_blogid")]

public Blog Blog


{


get
{ return _blog; }


set
{ _blog = value; }

}


[Property("post_created")]

public DateTime Created


{


get
{ return _created; }


set
{ _created = value; }

}


[Property("post_published")]

public bool Published


{


get
{ return _published; }


set
{ _published = value; }

}


public static void DeleteAll()


{

ActiveRecordBase.DeleteAll( typeof(Post) );

}


public static Post[] FindAll()


{

return (Post[]) ActiveRecordBase.FindAll( typeof(Post) );

}

}
三.构建配置信息
这里我采用上篇中将过的XML配置方式
<?xml version="1.0" encoding="utf-8" ?>

<activerecord>

<config>

<add key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver" />

<add key="hibernate.dialect" value="NHibernate.Dialect.MsSql2000Dialect" />

<add key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider" />

<add key="hibernate.connection.connection_string" value="Data Source=.;Initial Catalog=test;Integrated Security=SSPI" />

</config>

</activerecord>
四.编写测试代码
1.级联增加:新增一个Blog,并同时添加相关的Post到数据表中
[Test]

public void TestCascadingSave()



{

//创建Blog对象

Blog blog = new Blog();

blog.Name="Terrylee's Tech Space";

blog.Author = "Terrylee";


//执行事务,持久化对象到数据库

using(TransactionScope tran = new TransactionScope())


{

try


{

blog.Create();


for(int i=1;i<11;i++)


{

//创建Post对象

Post post = new Post();

post.Title="This is my "+i.ToString()+" post";

post.Category="Castle";

post.Blog = blog;

post.Save();

}


tran.VoteCommit();

}

catch


{

tran.VoteRollBack();

}

}// The changes will be sent to the DB when the session is disposed here

}
2
.级联更新:
首先我们为一个已经存在的Blog增加多个Post
[Test]

public void TestCascadingUpdate()



{

//找到ID为5的Blog

Blog blog = Blog.Find(5);


//执行事务,持久化对象到数据库

using(TransactionScope tran = new TransactionScope())


{

try


{

for(int i=1;i<5;i++)


{

//创建Post对象

Post post = new Post();

post.Title="This is my "+i.ToString()+" post";

post.Category="Castle";


post.Save();


//注意这句

blog.Posts.Add(post);

}


blog.Update();


tran.VoteCommit();

}

catch


{

tran.VoteRollBack();

}

}

}
当然上面的更新代码也可以这样去写:
[Test]

public void TestCascadingUpdate()



{

//找到ID为5的Blog

Blog blog = Blog.Find(5);


//执行事务,持久化对象到数据库

using(TransactionScope tran = new TransactionScope())


{

try


{

for(int i=1;i<5;i++)


{

//创建Post对象

Post post = new Post();

post.Title="This is my "+i.ToString()+" post";

post.Category="Castle";


//在这儿指定它们的关联

post.Blog = blog;

post.Save();

}


tran.VoteCommit();

}

catch


{

tran.VoteRollBack();

}

}

}
但是如果我们去掉post.Save()这句话,就会发现Post并没有增加到库中:
[Test]

public void TestCascadingUpdate()



{

//找到ID为5的Blog

Blog blog = Blog.Find(5);


//执行事务,持久化对象到数据库

using(TransactionScope tran = new TransactionScope())


{

try


{

for(int i=1;i<5;i++)


{

//创建Post对象

Post post = new Post();

post.Title="This is my "+i.ToString()+" post";

post.Category="Castle";


//注释掉这句

//post.Save();


blog.Posts.Add(post);

}


blog.Update();


tran.VoteCommit();

}

catch


{

tran.VoteRollBack();

}

}

}
此时,必须修改我们的Blog类,设置级联操作为SaveUpdate,上面的代码才可以正常执行
// 


[HasMany(typeof(Post), Table="posts", ColumnKey="post_blogid",Cascade=ManyRelationCascadeEnum.SaveUpdate)]

public IList Posts



{


get
{ return _posts; }


set
{ _posts = value; }

}
下面再测试一个删除某一个Blog的某些Post后,再保存
[Test]

public void TestCascadingUpdateDel()



{

//找到ID为7的Blog

Blog blog = Blog.Find(7);

int expectedCount = blog.Posts.Count;


using(TransactionScope tran = new TransactionScope())


{

try


{

blog.Posts.RemoveAt(0);


blog.Update();


tran.VoteCommit();

}

catch


{

tran.VoteRollBack();

}

}


int actualCount = Blog.Find(7).Posts.Count;


Assert.AreEqual(expectedCount-1,actualCount);

}
上面这段代码测试可以通过,但是我们会发现表Posts中会有一些记录没有BlogId,修改Blog实体类重新设置级联操作,就可以正常删除了:
// 


[HasMany(typeof(Post), Table="posts", ColumnKey="post_blogid",Cascade=ManyRelationCascadeEnum.AllDeleteOrphan)]

public IList Posts



{


get
{ return _posts; }


set
{ _posts = value; }

}
3.级联删除
删除一个Blog对象后,对应的Post对象应该全部删除
[Test]

public void TestCascadingDelete()



{

//找到ID为7的Blog对象

Blog blog = Blog.Find(5);


using(TransactionScope tran = new TransactionScope())


{

try


{

blog.Delete();


tran.VoteCommit();

}

catch


{

tran.VoteRollBack();

}

}

}
同样要注意设置级联操作。
关于One-Many关联映射就介绍这么多了,至于Many-One关联同One-Many,只不过对HasMany和BlongsTo设置的位置不一样而已,在下一篇文章中我会介绍在ActiveRecord中实现Many-Many关联映射。
参考资料
Castle的官方网站http://www.castleproject.org
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?