一对一关系配置与延迟加载机制

表结构设计

书籍表(book)

CREATE TABLE book (
    id INT IDENTITY(1,1) PRIMARY KEY,
    name NVARCHAR(50) NULL,
    bcid INT NULL,
    CONSTRAINT FK_book_bookcase FOREIGN KEY (bcid) REFERENCES bookcase(id)
);Code language: PHP (php)

标签表(booklabel)— 与 book 一对一

CREATE TABLE booklabel (
    id INT IDENTITY(1,1) PRIMARY KEY,
    name NVARCHAR(200) NULL,
    bid INT NULL UNIQUE,
    CONSTRAINT FK_booklabel_book FOREIGN KEY (bid) REFERENCES book(id)
);Code language: PHP (php)

关键点:bid 字段必须加 UNIQUE 约束。这样数据库层面保证了一本书只能有一个标签,一个标签只能属于一本书,LINQ to SQL 设计器才会将其识别为一对一关系而非一对多。


一对一关系的实体结构

设置外键并生成 LINQ to SQL 类后,实体中不再是一对多时的 EntitySet<T>,而是:

// book 类中
private EntityRef<booklabel> _booklabel;

// booklabel 类中
private EntityRef<book> _book;Code language: PHP (php)

EntityRef<T> 表示单个引用,区别于一对多中的 EntitySet<T>(集合)。


插入数据:正向关联(先创建书,再挂标签)

using (var db = new DataClasses1DataContext())
{
    var book = new book();
    book.name = "我是一本书";
    book.bcid = 1;

    var label = new booklabel();
    label.name = "这里解答很多我们生活中的不明白的疑问";
    label.bid = 1;

    // 通过导航属性关联(不需要手动赋值 bid)
    book.booklabel = label;

    db.book.InsertOnSubmit(book);
    db.SubmitChanges();
}Code language: JavaScript (javascript)

插入数据:反向关联(先创建标签,再挂书)

using (var db = new DataClasses1DataContext())
{
    var bc = db.bookcase.First(m => m.id == 13);

    var book = new book();
    book.name = "我是一本书2";
    book.bookcase = bc;

    var bl = new booklabel();
    bl.name = "这是是一本书 的标记2";

    // 反向关联:标签引用书
    bl.book = book;

    db.booklabel.InsertOnSubmit(bl);
    db.SubmitChanges();
}Code language: JavaScript (javascript)

两种写法等效:​ 不管是从 book 端设置 booklabel,还是从 label 端设置 bookSubmitChanges() 都会自动处理外键赋值,一次提交完成。


查询与加载行为

一对一:立即加载(Eager)

// 查书时,label 对象会被一起加载出来
var book = db.book.First(m => m.id == 8);

// 查 label 时,book 对象也会被一起加载出来,且 book 中的书架也会加载
var booklabel = db.booklabel.First(m => m.id == 1);Code language: JavaScript (javascript)

一对一关系中,访问任意一个对象,其关联的另一个对象已经被加载,不需要额外查询。

一对多:延迟加载(Deferred)

using (var db = new DataClasses1DataContext())
{
    // 此时只查了书架,书的集合并未加载
    var bookcase = db.bookcase.First(m => m.id == 13);

    // 第一次访问 .book 时,才执行数据库查询
    var books = bookcase.book.ToList();

    // foreach 遍历也会触发查询
    foreach (var item in bookcase.book)
    {
        Console.WriteLine(item.name);
        if (item.booklabel != null)
        {
            Console.WriteLine("   " + item.booklabel.name);
        }
    }
}Code language: JavaScript (javascript)

加载行为对比

关系类型关联端加载方式说明
一对一知道书 → 取标签立即加载访问 book.booklabel 直接有值
一对一知道标签 → 取书立即加载访问 label.book 直接有值,且书关联的书架也有
一对多知道书架 → 取书列表延迟加载首次访问 bookcase.book 时才查数据库
一对多知道书 → 取书架立即加载访问 book.bookcase 直接有值

底层机制

一对一关系的实体字段:

// book 实体内部
private EntityRef<booklabel> _booklabel;

// booklabel 实体内部
private EntityRef<book> _book;Code language: PHP (php)

EntityRef<T> 使用 EntityRef 包装器,在一对一中负责单引用管理立即加载;而一对多中使用的是 EntitySet<T>,负责集合管理延迟加载


关键要点

要点说明
一对一的前提外键字段必须有 UNIQUE 约束,否则 LINQ to SQL 识别为一对多
外键赋值通过导航属性赋值(book.booklabel = label),不需要手动设 bid
事务安全SubmitChanges() 一次提交,自动处理主从关系
加载差异一对一立即加载,一对多延迟加载(多端首次访问才查)
判断关联是否存在if (item.booklabel != null) 即可判断

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注