View Entities

What is View Entity?

View entity is a class that maps to a database view.You can create a view entity by defining a new class and mark it with @ViewEntity():

@ViewEntity() accepts following options:

  • name - view name. If not specified, then view name is generated from entity class name.
  • database - database name in selected DB server.
  • schema - schema name.
  • expression - view definition. Required parameter.

expression can be string with properly escaped columns and tables, depend on database used (postgres in example):

  1. @ViewEntity({
  2. expression: `
  3. SELECT "post"."id" "id", "post"."name" AS "name", "category"."name" AS "categoryName"
  4. FROM "post" "post"
  5. LEFT JOIN "category" "category" ON "post"."categoryId" = "category"."id"
  6. `
  7. })

or an instance of QueryBuilder

  1. @ViewEntity({
  2. expression: (connection: Connection) => connection.createQueryBuilder()
  3. .select("post.id", "id")
  4. .addSelect("post.name", "name")
  5. .addSelect("category.name", "categoryName")
  6. .from(Post, "post")
  7. .leftJoin(Category, "category", "category.id = post.categoryId")
  8. })

Note: parameter binding is not supported due to drivers limitations. Use the literal parameters instead.

  1. @ViewEntity({
  2. expression: (connection: Connection) => connection.createQueryBuilder()
  3. .select("post.id", "id")
  4. .addSelect("post.name", "name")
  5. .addSelect("category.name", "categoryName")
  6. .from(Post, "post")
  7. .leftJoin(Category, "category", "category.id = post.categoryId")
  8. .where("category.name = :name", { name: "Cars" }) // <-- this is wrong
  9. .where("category.name = 'Cars'") // <-- and this is right
  10. })

Each view entity must be registered in your connection options:

  1. import {createConnection, Connection} from "typeorm";
  2. import {UserView} from "./entity/UserView";
  3. const connection: Connection = await createConnection({
  4. type: "mysql",
  5. host: "localhost",
  6. port: 3306,
  7. username: "test",
  8. password: "test",
  9. database: "test",
  10. entities: [UserView]
  11. });

Or you can specify the whole directory with all entities inside - and all of them will be loaded:

  1. import {createConnection, Connection} from "typeorm";
  2. const connection: Connection = await createConnection({
  3. type: "mysql",
  4. host: "localhost",
  5. port: 3306,
  6. username: "test",
  7. password: "test",
  8. database: "test",
  9. entities: ["entity/*.js"]
  10. });

View Entity columns

To map data from view into the correct entity columns you must mark entity columns with @ViewColumn()decorator and specify these columns as select statement aliases.

example with string expression definition:

  1. import {ViewEntity, ViewColumn} from "typeorm";
  2. @ViewEntity({
  3. expression: `
  4. SELECT "post"."id" AS "id", "post"."name" AS "name", "category"."name" AS "categoryName"
  5. FROM "post" "post"
  6. LEFT JOIN "category" "category" ON "post"."categoryId" = "category"."id"
  7. `
  8. })
  9. export class PostCategory {
  10. @ViewColumn()
  11. id: number;
  12. @ViewColumn()
  13. name: string;
  14. @ViewColumn()
  15. categoryName: string;
  16. }

example using QueryBuilder:

  1. import {ViewEntity, ViewColumn} from "typeorm";
  2. @ViewEntity({
  3. expression: (connection: Connection) => connection.createQueryBuilder()
  4. .select("post.id", "id")
  5. .addSelect("post.name", "name")
  6. .addSelect("category.name", "categoryName")
  7. .from(Post, "post")
  8. .leftJoin(Category, "category", "category.id = post.categoryId")
  9. })
  10. export class PostCategory {
  11. @ViewColumn()
  12. id: number;
  13. @ViewColumn()
  14. name: string;
  15. @ViewColumn()
  16. categoryName: string;
  17. }

Complete example

Let create two entities and a view containing aggregated data from these entities:

  1. import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
  2. @Entity()
  3. export class Category {
  4. @PrimaryGeneratedColumn()
  5. id: number;
  6. @Column()
  7. name: string;
  8. }
  1. import {Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn} from "typeorm";
  2. import {Category} from "./Category";
  3. @Entity()
  4. export class Post {
  5. @PrimaryGeneratedColumn()
  6. id: number;
  7. @Column()
  8. name: string;
  9. @Column()
  10. categoryId: number;
  11. @ManyToOne(() => Category)
  12. @JoinColumn({ name: "categoryId" })
  13. category: Category;
  14. }
  1. import {ViewEntity, ViewColumn} from "typeorm";
  2. @ViewEntity({
  3. expression: (connection: Connection) => connection.createQueryBuilder()
  4. .select("post.id", "id")
  5. .addSelect("post.name", "name")
  6. .addSelect("category.name", "categoryName")
  7. .from(Post, "post")
  8. .leftJoin(Category, "category", "category.id = post.categoryId")
  9. })
  10. export class PostCategory {
  11. @ViewColumn()
  12. id: number;
  13. @ViewColumn()
  14. name: string;
  15. @ViewColumn()
  16. categoryName: string;
  17. }

then fill these tables with data and request all data from PostCategory view:

  1. import {getManager} from "typeorm";
  2. import {Category} from "./entity/Category";
  3. import {Post} from "./entity/Post";
  4. import {PostCategory} from "./entity/PostCategory";
  5. const entityManager = getManager();
  6. const category1 = new Category();
  7. category1.name = "Cars";
  8. await entityManager.save(category1);
  9. const category2 = new Category();
  10. category2.name = "Airplanes";
  11. await entityManager.save(category2);
  12. const post1 = new Post();
  13. post1.name = "About BMW";
  14. post1.categoryId = category1.id;
  15. await entityManager.save(post1);
  16. const post2 = new Post();
  17. post2.name = "About Boeing";
  18. post2.categoryId = category2.id;
  19. await entityManager.save(post2);
  20. const postCategories = await entityManager.find(PostCategory);
  21. const postCategory = await entityManager.findOne(PostCategory, { id: 1 });

the result in postCategories will be:

  1. [ PostCategory { id: 1, name: 'About BMW', categoryName: 'Cars' },
  2. PostCategory { id: 2, name: 'About Boeing', categoryName: 'Airplanes' } ]

and in postCategory:

  1. PostCategory { id: 1, name: 'About BMW', categoryName: 'Cars' }