表结构设计
书籍表(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端设置book,SubmitChanges()都会自动处理外键赋值,一次提交完成。
查询与加载行为
一对一:立即加载(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) 即可判断 |
Previous: 一对多关系的数据插入(主从表事务)
Next: 多对多关系的实现(借助中间关系表)