Skip to content

预加载关联 (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: 检查以下几点:

  1. 关联配置是否正确(外键、目标类等)
  2. 数据库中是否存在关联数据
  3. 外键值是否正确

Q: 预加载性能较差?

A: 优化建议:

  1. 只预加载必需的关联
  2. 使用分页限制结果集大小
  3. 考虑使用延迟加载替代深层嵌套预加载

Q: 嵌套预加载不工作?

A: 确保:

  1. 关联路径正确(使用点号分隔)
  2. 每层关联都已正确定义
  3. 中间实体的关联配置正确

与级联操作结合

预加载可以与级联操作结合使用,实现复杂的数据操作:

级联创建后预加载

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 });