Ember关联关系 #

一、关联关系概述 #

Ember Data支持三种主要的关联关系:

关系类型 装饰器 示例
一对一 belongsTo 用户-资料
一对多 hasMany 文章-评论
多对多 hasMany 文章-标签

二、一对一关系 #

2.1 定义 #

javascript
// app/models/user.js
import Model, { attr, hasOne } from '@ember-data/model';

export default class UserModel extends Model {
  @attr('string') name;
  @hasOne('profile') profile;
}
javascript
// app/models/profile.js
import Model, { attr, belongsTo } from '@ember-data/model';

export default class ProfileModel extends Model {
  @attr('string') bio;
  @belongsTo('user') user;
}

2.2 使用 #

javascript
// 获取关联
const profile = await user.profile;

// 设置关联
user.profile = newProfile;
await user.save();

三、一对多关系 #

3.1 定义 #

javascript
// app/models/post.js
import Model, { attr, hasMany } from '@ember-data/model';

export default class PostModel extends Model {
  @attr('string') title;
  @hasMany('comment') comments;
}
javascript
// app/models/comment.js
import Model, { attr, belongsTo } from '@ember-data/model';

export default class CommentModel extends Model {
  @attr('string') body;
  @belongsTo('post') post;
}

3.2 使用 #

javascript
// 获取所有评论
const comments = await post.comments;

// 添加评论
const comment = this.store.createRecord('comment', {
  body: 'Great post!',
  post: post,
});
await comment.save();

// 遍历评论
for (const comment of comments) {
  console.log(comment.body);
}

3.3 反向关联 #

javascript
// app/models/post.js
export default class PostModel extends Model {
  @hasMany('comment', { inverse: 'post' }) comments;
}

// app/models/comment.js
export default class CommentModel extends Model {
  @belongsTo('post', { inverse: 'comments' }) post;
}

四、多对多关系 #

4.1 定义 #

javascript
// app/models/post.js
import Model, { attr, hasMany } from '@ember-data/model';

export default class PostModel extends Model {
  @attr('string') title;
  @hasMany('tag') tags;
}
javascript
// app/models/tag.js
import Model, { attr, hasMany } from '@ember-data/model';

export default class TagModel extends Model {
  @attr('string') name;
  @hasMany('post') posts;
}

4.2 使用 #

javascript
// 添加标签
post.tags.pushObject(tag);
await post.save();

// 移除标签
post.tags.removeObject(tag);
await post.save();

// 获取所有标签
const tags = await post.tags;

五、异步关联 #

5.1 异步加载 #

javascript
export default class PostModel extends Model {
  @belongsTo('user', { async: true }) author;
  @hasMany('comment', { async: true }) comments;
}

5.2 模板中使用 #

handlebars
{{! 使用await}}
<p>作者:{{await @post.author.name}}</p>

{{#each (await @post.comments) as |comment|}}
  <p>{{comment.body}}</p>
{{/each}}

5.3 JavaScript中使用 #

javascript
const author = await post.author;
const comments = await post.comments;

六、关联选项 #

6.1 async #

javascript
// 异步加载(默认)
@belongsTo('user', { async: true }) author;

// 同步加载
@belongsTo('user', { async: false }) author;

6.2 inverse #

javascript
// 显式指定反向关联
@hasMany('comment', { inverse: 'post' }) comments;

// 禁用反向关联
@hasMany('comment', { inverse: null }) comments;

6.3 polymorphic #

javascript
// 多态关联
export default class CommentModel extends Model {
  @attr('string') body;
  @belongsTo('commentable', { polymorphic: true }) commentable;
}
javascript
// 被关联的模型
export default class PostModel extends Model {
  @hasMany('comment', { as: 'commentable' }) comments;
}

export default class PhotoModel extends Model {
  @hasMany('comment', { as: 'commentable' }) comments;
}

七、关联操作 #

7.1 获取关联 #

javascript
// 获取关联ID(不加载)
const authorId = post.belongsTo('author').id();

// 检查关联是否存在
const hasAuthor = post.belongsTo('author').value() !== null;

// 加载关联
const author = await post.author;

7.2 设置关联 #

javascript
// 设置belongsTo
post.author = newUser;

// 添加hasMany
post.comments.pushObject(newComment);

// 移除hasMany
post.comments.removeObject(comment);

// 清空hasMany
post.comments.clear();

7.3 关联状态 #

javascript
// 检查是否已加载
const isLoaded = post.hasMany('comments').value() !== null;

// 检查是否正在加载
const isLoading = post.hasMany('comments').isLoading();

// 检查是否有未保存更改
const hasChanged = post.hasMany('comments').hasBeenFetched();

八、预加载关联 #

8.1 include参数 #

javascript
// app/routes/posts/show.js
model(params) {
  return this.store.findRecord('post', params.post_id, {
    include: 'author,comments',
  });
}

8.2 多层预加载 #

javascript
model(params) {
  return this.store.findRecord('post', params.post_id, {
    include: 'author,comments.author,comments.likes',
  });
}

九、自引用关联 #

9.1 树形结构 #

javascript
// app/models/category.js
import Model, { attr, belongsTo, hasMany } from '@ember-data/model';

export default class CategoryModel extends Model {
  @attr('string') name;
  @belongsTo('category', { inverse: 'children' }) parent;
  @hasMany('category', { inverse: 'parent' }) children;
}

9.2 使用 #

javascript
// 获取父分类
const parent = await category.parent;

// 获取子分类
const children = await category.children;

// 设置父分类
category.parent = parentCategory;
await category.save();

十、关联最佳实践 #

10.1 合理使用异步关联 #

javascript
// 推荐 - 异步关联
@belongsTo('user', { async: true }) author;

// 仅在必要时使用同步关联
@belongsTo('user', { async: false }) author;

10.2 明确反向关联 #

javascript
// 好的做法 - 明确指定
@hasMany('comment', { inverse: 'post' }) comments;
@belongsTo('post', { inverse: 'comments' }) post;

// 避免 - 依赖隐式推断
@hasMany('comment') comments;
@belongsTo('post') post;

10.3 预加载关联 #

javascript
// 好的做法 - 预加载
model(params) {
  return this.store.findRecord('post', params.post_id, {
    include: 'author,comments',
  });
}

// 避免 - N+1查询
model(params) {
  return this.store.findRecord('post', params.post_id);
  // 然后在模板中逐个加载关联
}

十一、总结 #

关联关系要点:

关系 装饰器 说明
一对一 belongsTo 单向或双向
一对多 hasMany + belongsTo 双向关联
多对多 hasMany 双向关联
多态 polymorphic 动态类型

合理设计关联关系是数据建模的关键。

最后更新:2026-03-28