Relations(Static)
The following is to use the module test-vona as an example to explain the usage of static relations
Four kinds of Relations
Vona ORM provides 4 kinds of relations:
| Name | Description |
|---|---|
| hasOne | 1:1 |
| belongsTo | 1:1/n:1 |
| hasMany | 1:n. It can realize the functions of main-details and main-details(multi-level) |
| belongsToMany | n:n |
hasOne
1. Define the relation
import { ModelPostContent } from './postContent.ts';
@Model({
entity: EntityPost,
relations: {
postContent: $relation.hasOne(ModelPostContent, 'postId', { columns: ['id', 'content'] }),
},
})
class ModelPost {}| Name | Description |
|---|---|
| relations.postContent | Relation Name |
| $relation.hasOne | 1:1 |
| ModelPostContent | Taget Model |
| 'postId' | Foreign key |
| columns | List of fields to query |
WARNING
Any changes to the relations node require executing Vona Tools: Generate .metadata to generate the corresponding type definition
2. Using relations
The hasOne relation defined in the model can be used for all CRUD operations. Use include to specify the relation to be operated on, for example, postContent: true. Then, when the system operates on the Model Post, it will also operate on the Model PostContent
class ServicePost {
async relationHasOne() {
// insert
const postCreate = await this.scope.model.post.insert(
{
title: 'Post001',
postContent: {
content: 'This is a post',
},
},
{
include: {
postContent: true,
},
},
);
// get
const post = await this.scope.model.post.get(
{
id: postCreate.id,
},
{
include: {
postContent: true,
},
},
);
// update
await this.scope.model.post.update(
{
id: postCreate.id,
title: 'Post001-Update',
postContent: {
content: 'This is a post-changed',
},
},
{
include: {
postContent: true,
},
},
);
// delete
await this.scope.model.post.delete(
{
id: postCreate.id,
},
{
include: {
postContent: true,
},
},
);
}
}belongsTo
1. Define the relation
@Model({
entity: EntityPostContent,
relations: {
post: $relation.belongsTo(() => ModelPostContent, () => ModelPost, 'postId', { columns: '*' }),
},
})
class ModelPostContent {}| Name | Description |
|---|---|
| relations.post | Relation Name |
| $relation.belongsTo | 1:1/n:1 |
| ModelPostContent | Source Model |
| ModelPost | Target Model |
| 'postId' | Foreign key |
| columns | List of fields to query |
2. Using relations
The belongsTo relation defined in the Model is only used for query operation. Use include to specify the relation to be queried, such as post: true, then the system will query the Model Post while querying the Model PostContent
class ServicePost {
async relationBelongsTo() {
const postContent = await this.scope.model.postContent.select({
include: {
post: true,
},
});
console.log(postContent[0]?.post?.title);
}
}hasMany
1. Define the relation
import { ModelProduct } from './product.ts';
@Model({
entity: EntityOrder,
relations: {
products: $relation.hasMany(() => ModelProduct, 'orderId', {
columns: ['id', 'name', 'price', 'quantity', 'amount'],
}),
},
})
class ModelOrder {}| Name | Description |
|---|---|
| relations.products | Relation Name |
| $relation.hasMany | 1:n |
| ModelProduct | Target Model |
| 'orderId' | Foreign key |
| columns | List of fields to query |
2. Using relations
The hasMany relation defined in the Model can be used for all CRUD operations. Use include to specify the relation to be operated, such as products: true, then the system will operate on the Model Product while operating on the Model Order
class ServiceOrder {
async relationHasMany() {
// insert
const orderCreate = await this.scope.model.order.insert(
{
orderNo: 'Order001',
products: [
{ name: 'Apple' },
{ name: 'Pear' },
],
},
{
include: {
products: true,
},
},
);
// get
const _order = await this.scope.model.order.get(
{
id: orderCreate.id,
},
{
include: {
products: true,
},
},
);
// update
await this.scope.model.order.update(
{
id: orderCreate.id,
orderNo: 'Order001-Update',
products: [
// create product: Peach
{ name: 'Peach' },
// update product: Apple
{ id: orderCreate.products?.[0].id, name: 'Apple-Update' },
// delete product: Pear
{ id: orderCreate.products?.[1].id, deleted: true },
],
},
{
include: {
products: true,
},
},
);
// delete
await this.scope.model.order.delete(
{
id: orderCreate.id,
},
{
include: {
products: true,
},
},
);
}
}- When updating main table data, you can also update detail table data simultaneously (including Insert/Update/Delete operations)
belongsToMany
1. Define the relation
Defining an n:n relation requires an intermediate Model. For example, if the Model User and Model Role are n:n, you need to provide an intermediate Model RoleUser
@Model({
entity: EntityUser,
relations: {
roles: $relation.belongsToMany('test-vona:roleUser', 'test-vona:role', 'userId', 'roleId', { columns: ['id', 'name'] }),
},
})
class ModelUser {}| Name | Description |
|---|---|
| relations.roles | Relation Name |
| $relation.belongsToMany | n:n |
| 'test-vona:roleUser' | Middle Model |
| 'test-vona:role' | Target Model |
| 'userId' | Foreign key |
| 'roleId' | Foreign key |
| columns | List of fields to query |
2. Using relations
The belongsToMany relation defined in the Model can be used for all CRUD operations. It is important to emphasize that the CRUD operations here are for the intermediate Model, not the target Model. By specifying the relation to be operated on by include, such as roles: true, the system will operate on the intermediate Model RoleUser at the same time as the Model User
class ServiceUser {
async relationBelongsToMany() {
// insert: roles
const roles = await this.scope.model.role.insertBulk([
{ name: 'role-family' },
{ name: 'role-friend' },
]);
const roleIdFamily = roles[0].id;
const roleIdFriend = roles[1].id;
// insert: user
const userCreate = await this.scope.model.user.insert(
{
name: 'Tom',
roles: [{
id: roleIdFamily,
}],
},
{
include: {
roles: true,
},
},
);
// get: user
await this.scope.model.user.get(
{
id: userCreate.id,
},
{
include: {
roles: true,
},
},
);
// update: user
await this.scope.model.user.update(
{
id: userCreate.id,
roles: [
// delete
{ id: roleIdFamily, deleted: true },
// insert
{ id: roleIdFriend },
],
},
{
include: {
roles: true,
},
},
);
// delete: user
await this.scope.model.user.delete(
{
id: userCreate.id,
},
{
include: {
roles: true,
},
},
);
}
}autoload
As you can see from the previous demonstration, if you want to operate on a relation, you need to specify the corresponding relation option using include
If the relation you want to operate on is frequently, you can set autoload: true on the relation, thus omitting the include option
Tree structure
Next, we implement a directory tree to demonstrate how to use autoload to implement a tree structure
1. Define the relation
@Model({
entity: EntityCategory,
relations: {
children: $relation.hasMany(() => ModelCategory, 'categoryIdParent', {
autoload: true,
columns: ['id', 'name'],
}),
},
})
class ModelCategory {}| Name | Description |
|---|---|
| relations.children | Relation Name |
| $relation.hasMany | 1:n |
| ModelCategory | Target Model |
| 'categoryIdParent' | Foreign key |
| autoload | Autoload |
| columns | List of fields to query |
2. Using relations
- Because a
hasManyrelation is defined with itself, a tree structure is formed. This tree structure can be used for allCRUDoperations - Because
autoload: trueis defined, the system automatically operates on children while operating on the main data
class ServiceCategory {
async categoryTree() {
// create
const treeCreate = await this.scope.model.category.insert({
name: 'Category-1',
children: [
{
name: 'Category-1-1',
children: [
{ name: 'Category-1-1-1' },
],
},
{
name: 'Category-1-2',
},
],
});
// get
const tree = await this.scope.model.category.get({
id: treeCreate.id,
});
assert.equal(tree?.children.length, 2);
assert.equal(tree?.children[0].children.length, 1);
// update
await this.scope.model.category.update({
id: treeCreate.id,
name: 'Category-1-Update',
children: [
// create
{ name: 'Category-1-3' },
// update
{ id: treeCreate.children?.[0].id, name: 'Category-1-1-Update' },
// delete
{ id: treeCreate.children?.[1].id, deleted: true },
],
});
// delete
await this.scope.model.category.delete({
id: treeCreate.id,
});
}
}Tree structure (reverse query)
The previous demonstration shows how to query a directory tree from the parent to the children. The following demonstration shows how to query a directory tree from the child to the parent
1. Define the relation
@Model({
entity: EntityCategory,
relations: {
parent: $relation.belongsTo(() => ModelCategoryChain, () => ModelCategoryChain, 'categoryIdParent', {
autoload: true,
columns: ['id', 'name', 'categoryIdParent'],
}),
},
})
class ModelCategoryChain {}| Name | Description |
|---|---|
| relations.parent | Relation Name |
| $relation.belongsTo | n:1 |
| ModelCategoryChain | Source Model |
| ModelCategoryChain | Target Model |
| 'categoryIdParent' | Foreign key |
| autoload | Autoload |
| columns | List of fields to query |
2. Using relations
- Due to the
belongsTorelation defined with itself, an inverted tree structure is formed. This tree structure is only used for query operation - Due to
autoload: truebeing defined, the system will automatically query the parent directories when querying subdirectory
class ServiceCategory {
async categoryTreeReverse() {
// create
const treeCreate = await this.scope.model.category.insert({
name: 'Category-1',
children: [
{
name: 'Category-1-1',
children: [
{ name: 'Category-1-1-1' },
],
},
{
name: 'Category-1-2',
},
],
});
// 'Category-1-1-1'
const subCategoryId = treeCreate.children?.[0].children?.[0].id;
// get: reverse
const subCategory = await this.scope.model.categoryChain.get({
id: subCategoryId,
});
assert.equal(subCategory?.parent?.parent?.id, treeCreate.id);
}
}Relation Options
1. $relation.hasOne/$relation.belongsTo
| Name | Description |
|---|---|
| autoload | Autoload |
| columns | List of fields to query |
| meta.client | Define the datasource used by the relation, which can realize cross-datasource relation query |
| meta.table | Define the data table used by the relation |
2. $relation.hasMany/$relation.belongsToMany
| Name | Description |
|---|---|
| autoload | Autoload |
| columns | List of fields to query |
| meta.client | Define the datasource used by the relation, which can realize cross-datasource relation query |
| meta.table | Define the data table used by the relation |
| distinct | Whether to enable distinct |
| where | Conditional statement |
| joins | Related tables |
| orders | Sorting |
| limit | Can be used for paginated queries |
| offset | Can be used for paginated queries |
| aggrs | Aggregate query |
| groups | Group-by query |
Parameter: Model
When defining a relation, you need to provide the following parameters: Source Model, Target Model, and Intermediate Model. The following types are supported:
| Name | Description |
|---|---|
| ModelPost | Model Class |
| () => ModelPost | Use a function to delay loading to avoid circular dependency errors |
| 'test-vona:post' | When using models across modules, typically use the model name directly |