预加载关联 (Eager Loading)
预加载是一种在查询主实体时同时加载其关联数据的技术。IBest ORM 提供了强大的预加载功能,支持嵌套关联和复杂查询场景。
基础用法
使用 With() 方法
typescript
// 预加载单个关联
const users = await orm.Session(User)
.With('profile')
.FirstWithRelations();
// 预加载多个关联
const users = await orm.Session(User)
.With(['profile', 'orders', 'roles'])
.FirstWithRelations();
使用 Preload() 方法
typescript
// Preload 是 With 的别名
const users = await orm.Session(User)
.Preload(['profile', 'orders'])
.FirstWithRelations();
详细用法指南
查询方法
预加载必须使用专门的关联查询方法:
typescript
// FirstWithRelations() - 查询单条记录及关联
const userWithRelations = await orm.Session(User)
.With('profile')
.Where('id', 1)
.FirstWithRelations();
// FindWithRelations() - 查询多条记录及关联
const usersWithRelations = await orm.Session(User)
.With(['profile', 'articles'])
.FindWithRelations();
重要提醒
预加载查询必须使用 FirstWithRelations()
或 FindWithRelations()
方法。 普通的 First()
和 Find()
方法不会加载关联数据!
复杂嵌套示例
typescript
// 查询用户的完整信息:档案、文章、文章评论、用户角色
const completeUserData = await orm.Session(User)
.With(['profile', 'articles.comments', 'roles'])
.Where('id', 1)
.FirstWithRelations();
console.log('完整用户数据:', completeUserData);
// 输出:
// {
// id: 1,
// name: "张三",
// email: "zhangsan@example.com",
// profile: {
// id: 1,
// user_id: 1,
// avatar: "avatar1.jpg",
// bio: "热爱技术的程序员"
// },
// articles: [
// {
// id: 1,
// title: "TypeScript入门指南",
// content: "TypeScript是JavaScript的超集...",
// author_id: 1,
// comments: [
// {
// id: 1,
// content: "写得很好,学到了!",
// article_id: 1,
// user_id: 2
// }
// ]
// }
// ],
// roles: [
// {
// id: 1,
// name: "admin",
// description: "管理员"
// }
// ]
// }
不同关联类型的预加载
Has One 关联预加载
typescript
// 用户档案(一对一)
const userWithProfile = await orm.Session(User)
.With(['profile'])
.Where('id', 1)
.FirstWithRelations();
// 结果中 profile 是单个对象或 null
console.log('用户档案:', userWithProfile.profile);
Has Many 关联预加载
typescript
// 用户文章(一对多)
const userWithArticles = await orm.Session(User)
.With(['articles'])
.Where('id', 1)
.FirstWithRelations();
// 结果中 articles 是数组
console.log('文章数量:', userWithArticles.articles?.length || 0);
Belongs To 关联预加载
typescript
// 文章作者(多对一)
const articleWithAuthor = await orm.Session(Article)
.With(['author'])
.Where('id', 1)
.FirstWithRelations();
// 结果中 author 是单个对象或 null
console.log('文章作者:', articleWithAuthor.author?.name);
Many To Many 关联预加载
typescript
// 用户角色(多对多)
const userWithRoles = await orm.Session(User)
.With(['roles'])
.Where('id', 1)
.FirstWithRelations();
// 结果中 roles 是数组
console.log('角色列表:', userWithRoles.roles?.map(role => role.name));
批量预加载
typescript
// 查询所有用户及其档案和文章
const allUsersWithData = await orm.Session(User)
.With(['profile', 'articles'])
.FindWithRelations();
allUsersWithData.forEach(user => {
console.log(`用户: ${user.name}`);
console.log(`档案: ${user.profile?.bio || '无'}`);
console.log(`文章数: ${user.articles?.length || 0}`);
console.log('---');
});
// 分页查询与预加载
const usersPage = await orm.Session(User)
.With(['profile', 'articles'])
.Limit(10)
.Offset(0)
.FindWithRelations();
条件查询与预加载
typescript
// 查询活跃用户及其文章
const activeUsersWithArticles = await orm.Session(User)
.With(['articles'])
.Where('status', 'active')
.FindWithRelations();
// 查询特定时间的文章及作者
const recentArticlesWithAuthors = await orm.Session(Article)
.With(['author'])
.Where('created_at', '2024-01-01')
.FindWithRelations();
// 多条件查询
const filteredUsersWithData = await orm.Session(User)
.With(['profile', 'articles'])
.Where('status', 'active')
.Where('created_at', '2023-01-01')
.FindWithRelations();
嵌套关联预加载
支持多层级的关联预加载,使用点号分隔关联路径。
typescript
// 预加载订单及其订单项
const users = await orm.Session(User)
.With(['orders.items'])
.FindWithRelations();
// 预加载多个嵌套关联
const users = await orm.Session(User)
.With(['profile', 'orders.items', 'roles'])
.FindWithRelations();
// 更深层的嵌套
const users = await orm.Session(User)
.With(['orders.items.product.category'])
.FindWithRelations();
专用查询方法
FirstWithRelations()
查询单个实体并预加载关联:
typescript
const user = await orm.Session(User)
.With(['profile', 'orders.items', 'roles'])
.Where('email', 'john@example.com')
.FirstWithRelations();
// 访问预加载的关联数据
console.log(user.profile.bio);
console.log(user.orders[0].items);
console.log(user.roles.map(role => role.name));
FindWithRelations()
查询多个实体并预加载关联:
typescript
const users = await orm.Session(User)
.With(['profile', 'orders'])
.Where('status', 'active')
.FindWithRelations();
users.forEach(user => {
console.log(`${user.name}: ${user.orders.length} orders`);
});
性能优化
选择性预加载
只预加载需要的关联,避免不必要的数据传输:
typescript
// 好的做法:只预加载需要的关联
const users = await orm.Session(User)
.With(['profile']) // 只预加载用户资料
.FindWithRelations();
// 避免:预加载所有关联
const users = await orm.Session(User)
.With(['profile', 'orders', 'roles', 'permissions', 'settings'])
.FindWithRelations();
实际应用示例
用户详情页面
typescript
async function getUserDetails(userId: number) {
const user = await orm.Session(User)
.With(['profile', 'orders.items', 'roles'])
.Where('id', userId)
.FirstWithRelations();
return {
user: {
id: user.id,
name: user.name,
email: user.email,
bio: user.profile?.bio,
avatar: user.profile?.avatar
},
orders: user.orders.map(order => ({
id: order.id,
product_name: order.product_name,
amount: order.amount,
items: order.items
})),
roles: user.roles.map(role => role.name)
};
}
订单列表页面
typescript
async function getOrdersList(page: number = 1, limit: number = 20) {
const orders = await orm.Session(Order)
.With(['user.profile', 'items'])
.OrderBy('created_at', 'DESC')
.Offset((page - 1) * limit)
.Limit(limit)
.FindWithRelations();
return orders.map(order => ({
id: order.id,
product_name: order.product_name,
amount: order.amount,
user: {
name: order.user.name,
avatar: order.user.profile?.avatar
},
itemCount: order.items.length
}));
}
仪表板统计
typescript
async function getDashboardStats() {
const users = await orm.Session(User)
.With(['orders', 'roles'])
.FindWithRelations();
return {
totalUsers: users.length,
totalOrders: users.reduce((sum, user) => sum + user.orders.length, 0),
adminUsers: users.filter(user =>
user.roles.some(role => role.name === 'admin')
).length
};
}
常见问题
Q: 预加载的关联为空数组或 null?
A: 检查以下几点:
- 关联配置是否正确(外键、目标类等)
- 数据库中是否存在关联数据
- 外键值是否正确
Q: 预加载性能较差?
A: 优化建议:
- 只预加载必需的关联
- 使用分页限制结果集大小
- 考虑使用延迟加载替代深层嵌套预加载
Q: 嵌套预加载不工作?
A: 确保:
- 关联路径正确(使用点号分隔)
- 每层关联都已正确定义
- 中间实体的关联配置正确
与级联操作结合
预加载可以与级联操作结合使用,实现复杂的数据操作:
级联创建后预加载
typescript
// 级联创建用户及关联数据
const userWithData = new User();
userWithData.name = "张三";
userWithData.profile = { bio: "软件工程师" };
userWithData.orders = [
{ product_name: "笔记本", amount: 8999 }
];
// 级联创建
await orm.Create(userWithData, { cascade: true });
// 预加载查询创建的数据
const createdUser = await orm.Session(User)
.With(['profile', 'orders'])
.Where('name', '张三')
.FirstWithRelations();
级联更新前预加载
typescript
// 预加载现有数据
const user = await orm.Session(User)
.With(['profile', 'orders'])
.Where('id', userId)
.FirstWithRelations();
// 修改数据
user.profile.bio = "高级软件工程师";
user.orders.push({ product_name: "鼠标", amount: 199 });
// 级联更新
await orm.Save(user, { cascade: true });