虚拟实体
虚拟实体不代表任何数据库表。相反,它们动态解析为 SQL 查询(或 MongoDB 中的聚合),允许将任何类型的结果映射到实体上。此类实体用于读取目的,它们没有主键,因此无法跟踪更改。在某种程度上,它们类似于(当前不支持的)数据库视图,你可以使用它们来代理你的原生视图。
¥Virtual entities don't represent any database table. Instead, they dynamically resolve to an SQL query (or an aggregation in MongoDB), allowing to map any kind of results onto an entity. Such entities are meant for read purposes, they don't have a primary key and therefore cannot be tracked for changes. In a way they are similar to (currently unsupported) database views, and you can use them to proxy your native views already.
虚拟实体可以包含标量属性以及一对一关系(M:1 和 1:1 所有者)。此类关系始终通过
select-in
策略填充。¥Virtual entities can contain scalar properties as well as to-one relations (M:1 and 1:1 owners). Such relations are always populated via
select-in
strategy.
要定义虚拟实体,请提供 expression
,可以是字符串(SQL 查询):
¥To define a virtual entity, provide an expression
, either as a string (SQL query):
你需要根据当前命名策略使用虚拟列名。请注意,
authorName
属性表示为author_name
列。¥You need to use the virtual column names based on current naming strategy. Note the
authorName
property being represented asauthor_name
column.
- reflect-metadata
- ts-morph
- EntitySchema
@Entity({
expression: `
select b.title, a.name as author_name,
(
select group_concat(distinct t.name)
from book b
join tags_ordered bt on bt.book_id = b.id
join book_tag t on t.id = bt.book_tag_id
where b.author_id = a.id
group by b.author_id
) as tags
from author a
group by a.id
`
})
export class BookWithAuthor {
@Property()
title!: string;
@Property()
authorName!: string;
@Property()
tags!: string[];
}
@Entity({
expression: `
select b.title, a.name as author_name,
(
select group_concat(distinct t.name)
from book b
join tags_ordered bt on bt.book_id = b.id
join book_tag t on t.id = bt.book_tag_id
where b.author_id = a.id
group by b.author_id
) as tags
from author a
group by a.id
`
})
export class BookWithAuthor {
@Property()
title!: string;
@Property()
authorName!: string;
@Property()
tags!: string[];
}
export interface IBookWithAuthor{
title: string;
authorName: string;
tags: string[];
}
export const BookWithAuthor = new EntitySchema<IBookWithAuthor>({
name: 'BookWithAuthor',
expression: `
select b.title, a.name as author_name,
(
select group_concat(distinct t.name)
from book b
join tags_ordered bt on bt.book_id = b.id
join book_tag t on t.id = bt.book_tag_id
where b.author_id = a.id
group by b.author_id
) as tags
from author a
group by a.id
`,
properties: {
title: { type: 'string' },
authorName: { type: 'string' },
tags: { type: 'string[]' },
},
});
或作为回调:
¥Or as a callback:
- reflect-metadata
- ts-morph
- EntitySchema
@Entity({
expression: (em: EntityManager) => {
return em.createQueryBuilder(Book, 'b')
.select(['b.title', 'a.name as author_name', 'group_concat(t.name) as tags'])
.join('b.author', 'a')
.join('b.tags', 't')
.groupBy('b.id');
},
})
export class BookWithAuthor {
@Property()
title!: string;
@Property()
authorName!: string;
@Property()
tags!: string[];
}
@Entity({
expression: (em: EntityManager) => {
return em.createQueryBuilder(Book, 'b')
.select(['b.title', 'a.name as author_name', 'group_concat(t.name) as tags'])
.join('b.author', 'a')
.join('b.tags', 't')
.groupBy('b.id');
},
})
export class BookWithAuthor {
@Property()
title!: string;
@Property()
authorName!: string;
@Property()
tags!: string[];
}
export interface IBookWithAuthor{
title: string;
authorName: string;
tags: string[];
}
export const BookWithAuthor = new EntitySchema<IBookWithAuthor>({
name: 'BookWithAuthor',
expression: (em: EntityManager) => {
return em.createQueryBuilder(Book, 'b')
.select(['b.title', 'a.name as author_name', 'group_concat(t.name) as tags'])
.join('b.author', 'a')
.join('b.tags', 't')
.groupBy('b.id');
},
properties: {
title: { type: 'string' },
authorName: { type: 'string' },
tags: { type: 'string[]' },
},
});
在 MongoDB 中,你可以使用聚合,尽管由于其性质,它不太符合人机工程学。以下示例与前面的 SQL 示例大致相同。
¥In MongoDB, you can use aggregations, although it is not very ergonomic due to their nature. Following example is a rough equivalent of the previous SQL ones.
where
查询以及orderBy
、limit
和offset
等选项需要在管道中明确处理。¥The
where
query as well as the options likeorderBy
,limit
andoffset
needs to be explicitly handled in your pipeline.
@Entity({
expression: (em: EntityManager, where, options) => {
const $sort = { ...options.orderBy } as Dictionary;
$sort._id = 1;
const pipeline: Dictionary[] = [
{ $project: { _id: 0, title: 1, author: 1 } },
{ $sort },
{ $match: where ?? {} },
{ $lookup: { from: 'author', localField: 'author', foreignField: '_id', as: 'author', pipeline: [{ $project: { name: 1 } }] } },
{ $unwind: '$author' },
{ $set: { authorName: '$author.name' } },
{ $unset: ['author'] },
];
if (options.offset != null) {
pipeline.push({ $skip: options.offset });
}
if (options.limit != null) {
pipeline.push({ $limit: options.limit });
}
return em.aggregate(Book, pipeline);
},
})
export class BookWithAuthor {
@Property()
title!: string;
@Property()
authorName!: string;
}