首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

Castle ActiveRecord 實戰(4):一對多關聯映射全解析——HasMany 與 BelongsTo 雙向關聯與級聯策略

     編輯:關於.NET

資料庫結構:外鍵關聯

CREATE TABLE Blogs (
    blog_id int IDENTITY PRIMARY KEY,
    blog_name varchar(50),
    blog_author varchar(50)
);

CREATE TABLE Posts (
    post_id int IDENTITY 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
);

雙向映射:一端 HasMany,一端 BelongsTo

Blog 端(一的一方)

[ActiveRecord("Blogs")]
public class Blog : ActiveRecordBase {
    [PrimaryKey(PrimaryKeyType.Native, "blog_id")]
    public int Id { get; set; }

    [Property("blog_name")] public string Name { get; set; }
    [Property("blog_author")] public string Author { get; set; }

    [HasMany(typeof(Post), Table = "posts", ColumnKey = "post_blogid")]
    public IList Posts { get; set; }
}

Post 端(多的一方)

[ActiveRecord("Posts")]
public class Post : ActiveRecordBase {
    [PrimaryKey(PrimaryKeyType.Native, "post_id")]
    public int Id { get; set; }

    [Property("post_title")] public string Title { get; set; }
    [Property(Column = "post_contents", ColumnType = "StringClob")] public string Contents { get; set; }
    [Property("post_categories")] public string Category { get; set; }
    [Property("post_created")] public DateTime Created { get; set; }
    [Property("post_published")] public bool Published { get; set; }

    [BelongsTo("post_blogid")]
    public Blog Blog { get; set; }
}

HasMany 關鍵屬性詳解

屬性說明常用值
Cascade級聯操作範圍None / SaveUpdate / Delete / All / AllDeleteOrphan
Inverse是否由關聯端維護外鍵(建議 true)true / false
Lazy延遲載入true / false
Where固定過濾條件(資料表欄位名)"post_published = 1"
BatchSize批次抓取筆數(優化 N+1)10, 25

BelongsTo 關鍵屬性詳解

屬性說明常用值
Column外鍵欄位名"post_blogid"
Cascade級聯操作None / SaveUpdate / Delete / All
NotNull外鍵不可為 Nulltrue / false
OuterJoin急切外連接抓取Auto / True / False
Update/Insert是否包含在 UPDATE/INSERT SQLtrue / false

級聯操作實戰:從 SaveUpdate 到 AllDeleteOrphan

場景 1:新增 Blog 同時新增 Posts

[Test]
public void TestCascadingSave() {
    var blog = new Blog { Name = "Terrylee's Tech Space", Author = "Terrylee" };
    using (var tran = new TransactionScope()) {
        try {
            blog.Create();
            for (int i = 1; i <= 10; i++) {
                var post = new Post {
                    Title = $"This is my {i} post",
                    Category = "Castle",
                    Blog = blog // 設定關聯
                };
                post.Save();
            }
            tran.VoteCommit();
        } catch { tran.VoteRollBack(); }
    }
}

場景 2:更新 Blog 時級聯儲存新增的 Posts

需在 HasMany 設 Cascade = ManyRelationCascadeEnum.SaveUpdate:

[HasMany(typeof(Post), Table = "posts", ColumnKey = "post_blogid", 
    Cascade = ManyRelationCascadeEnum.SaveUpdate)]
public IList Posts { get; set; }
[Test]
public void TestCascadingUpdate() {
    var blog = Blog.Find(5);
    using (var tran = new TransactionScope()) {
        try {
            for (int i = 1; i <= 5; i++) {
                var post = new Post {
                    Title = $"New post {i}",
                    Category = "Castle",
                    Blog = blog
                };
                blog.Posts.Add(post); // 加入集合
                // post.Save() 可省略,SaveUpdate 級聯會自動持久化
            }
            blog.Update();
            tran.VoteCommit();
        } catch { tran.VoteRollBack(); }
    }
}

場景 3:移除集合項目時自動刪除孤兒記錄

設 Cascade = ManyRelationCascadeEnum.AllDeleteOrphan:

[Test]
public void TestCascadingUpdateDel() {
    var blog = Blog.Find(7);
    var before = blog.Posts.Count;
    using (var tran = new TransactionScope()) {
        try {
            blog.Posts.RemoveAt(0); // 移除第一篇
            blog.Update(); // 自動刪除對應 Post 記錄
            tran.VoteCommit();
        } catch { tran.VoteRollBack(); }
    }
    Assert.AreEqual(before - 1, Blog.Find(7).Posts.Count);
}

場景 4:刪除 Blog 級聯刪除所有 Posts

設 Cascade = ManyRelationCascadeEnum.All 或 AllDeleteOrphan:

[Test]
public void TestCascadingDelete() {
    var blog = Blog.Find(5);
    using (var tran = new TransactionScope()) {
        try {
            blog.Delete(); // 連帶刪除所有 Posts
            tran.VoteCommit();
        } catch { tran.VoteRollBack(); }
    }
}

Inverse = true:避免雙向更新外鍵衝突

在 HasMany 加上 Inverse = true,表示外鍵由 BelongsTo 端(Post.Blog)維護:

[HasMany(typeof(Post), Table = "posts", ColumnKey = "post_blogid", 
    Inverse = true, Cascade = ManyRelationCascadeEnum.AllDeleteOrphan)]
public IList Posts { get; set; }

這樣 Blog.Save/Update 時不會嘗試更新 post_blogid,由 Post 端統一管理,避免重複 UPDATE 與死結。

多對一:與一對多映射相同,僅視角不同

若從 Post 角度看,屬於一個 Blog,即為 BelongsTo;從 Blog 角度看,擁有多個 Posts,即為 HasMany。映射設定完全一致,無需額外學習。

總結

一對多映射核心在於 HasMany(集合端)與 BelongsTo(參考端)雙向配合。關鍵在於:正確設定 ColumnKey/Column 對應外鍵、配置 Inverse=true 避免雙向維護衝突、根據業務需求選擇 Cascade 模式(推薦 AllDeleteOrphan 實現完整生命週期管理)。掌握這些,即可靈活處理主從關聯業務。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved