diff --git a/sample/sample18-lazy-relations/app.ts b/sample/sample18-lazy-relations/app.ts index d3844aa49..1806361a3 100644 --- a/sample/sample18-lazy-relations/app.ts +++ b/sample/sample18-lazy-relations/app.ts @@ -1,6 +1,7 @@ import {createConnection, CreateConnectionOptions} from "../../src/typeorm"; import {Post} from "./entity/Post"; import {Author} from "./entity/Author"; +import {Category} from "./entity/Category"; const options: CreateConnectionOptions = { driver: "mysql", @@ -16,13 +17,14 @@ const options: CreateConnectionOptions = { logFailedQueryError: true } }, - entities: [Post, Author] + entities: [Post, Author, Category] }; createConnection(options).then(connection => { let postRepository = connection.getRepository(Post); let authorRepository = connection.getRepository(Author); + let categoryRepository = connection.getRepository(Category); let author = authorRepository.create(); author.name = "Umed"; @@ -67,6 +69,40 @@ createConnection(options).then(connection => { }) .then(posts => { console.log("Two post's author has been removed."); + console.log("Now lets check many-to-many relations"); + + let category1 = categoryRepository.create(); + category1.name = "Hello category1"; + + let category2 = categoryRepository.create(); + category2.name = "Bye category2"; + + let post = postRepository.create(); + post.title = "Post & Categories"; + post.text = "Post with many categories"; + post.categories = Promise.resolve([ + category1, + category2 + ]); + + return postRepository.persist(post); + }) + .then(posts => { + console.log("Post has been saved with its categories. "); + console.log("Lets find it now. "); + return postRepository.find({ alias: "post", innerJoinAndSelect: { categories: "post.categories" } }); + }) + .then(posts => { + console.log("Post with categories are loaded: ", posts); + console.log("Lets remove one of the categories: "); + return posts[0].categories.then(categories => { + categories.splice(0, 1); + // console.log(posts[0]); + return postRepository.persist(posts[0]); + }); + }) + .then(posts => { + console.log("One of the post category has been removed."); }) .catch(error => console.log(error.stack)); diff --git a/sample/sample18-lazy-relations/entity/Category.ts b/sample/sample18-lazy-relations/entity/Category.ts new file mode 100644 index 000000000..4269e945b --- /dev/null +++ b/sample/sample18-lazy-relations/entity/Category.ts @@ -0,0 +1,18 @@ +import {PrimaryColumn, Column} from "../../../src/columns"; +import {Table} from "../../../src/tables"; +import {ManyToMany} from "../../../src/decorator/relations/ManyToMany"; +import {Post} from "./Post"; + +@Table("sample18_category") +export class Category { + + @PrimaryColumn("int", { generated: true }) + id: number; + + @Column() + name: string; + + @ManyToMany(type => Post, post => post.categories) + posts: Promise; + +} \ No newline at end of file diff --git a/sample/sample18-lazy-relations/entity/Post.ts b/sample/sample18-lazy-relations/entity/Post.ts index 94617b14e..443f9eaaa 100644 --- a/sample/sample18-lazy-relations/entity/Post.ts +++ b/sample/sample18-lazy-relations/entity/Post.ts @@ -2,6 +2,9 @@ import {PrimaryColumn, Column} from "../../../src/columns"; import {Table} from "../../../src/tables"; import {Author} from "./Author"; import {ManyToOne} from "../../../src/decorator/relations/ManyToOne"; +import {Category} from "./Category"; +import {ManyToMany} from "../../../src/decorator/relations/ManyToMany"; +import {JoinTable} from "../../../src/decorator/relations/JoinTable"; @Table("sample18_post") export class Post { @@ -22,4 +25,10 @@ export class Post { }) author: Promise; + @ManyToMany(type => Category, category => category.posts, { + cascadeAll: true + }) + @JoinTable() + categories: Promise; + } \ No newline at end of file diff --git a/src/persistment/EntityPersistOperationsBuilder.ts b/src/persistment/EntityPersistOperationsBuilder.ts index 089217e51..3d7504597 100644 --- a/src/persistment/EntityPersistOperationsBuilder.ts +++ b/src/persistment/EntityPersistOperationsBuilder.ts @@ -273,15 +273,19 @@ export class EntityPersistOperationBuilder { }); return metadata.relations .filter(relation => relation.isManyToMany) - .filter(relation => newEntity[relation.propertyName] instanceof Array) + // .filter(relation => newEntity[relation.propertyName] instanceof Array) .reduce((operations, relation) => { const relationMetadata = relation.relatedEntityMetadata; const relationIdProperty = relationMetadata.primaryColumn.name; - newEntity[relation.propertyName].map((subEntity: any) => { + const value = this.getEntityRelationValue(relation, newEntity); + const dbValue = dbEntity ? this.getEntityRelationValue(relation, dbEntity.entity) : null; + + if (!(value instanceof Array)) + return operations; + + value.forEach((subEntity: any) => { - const has = !dbEntity || - !dbEntity.entity[relation.propertyName] || - !dbEntity.entity[relation.propertyName].find((e: any) => e[relationIdProperty] === subEntity[relationIdProperty]); + const has = !dbValue || !dbValue.find((e: any) => e[relationIdProperty] === subEntity[relationIdProperty]); if (has) { operations.push({ @@ -307,15 +311,19 @@ export class EntityPersistOperationBuilder { }); return metadata.relations .filter(relation => relation.isManyToMany) - .filter(relation => dbEntity[relation.propertyName] instanceof Array) + // .filter(relation => dbEntity[relation.propertyName] instanceof Array) .reduce((operations, relation) => { const relationMetadata = relation.relatedEntityMetadata; const relationIdProperty = relationMetadata.primaryColumn.name; - dbEntity[relation.propertyName].map((subEntity: any) => { + const value = newEntity ? this.getEntityRelationValue(relation, newEntity.entity) : null; + const dbValue = this.getEntityRelationValue(relation, dbEntity); - const has = !newEntity || - !newEntity.entity[relation.propertyName] || - !newEntity.entity[relation.propertyName].find((e: any) => e[relationIdProperty] === subEntity[relationIdProperty]); + if (!(dbValue instanceof Array)) + return operations; + + dbValue.forEach((subEntity: any) => { + + const has = !value || !value.find((e: any) => e[relationIdProperty] === subEntity[relationIdProperty]); if (has) { operations.push({ diff --git a/src/repository/Repository.ts b/src/repository/Repository.ts index fbd622096..4cb4d8936 100644 --- a/src/repository/Repository.ts +++ b/src/repository/Repository.ts @@ -77,42 +77,6 @@ export class Repository { return this.addLazyProperties(this.metadata.create()); } - // todo: duplication - private addLazyProperties(entity: any) { - const metadata = this.entityMetadatas.findByTarget(entity.constructor); - metadata.relations - .filter(relation => relation.isLazy) - .forEach(relation => { - const index = "__" + relation.propertyName + "__"; - - Object.defineProperty(entity, relation.propertyName, { - get: () => { - if (entity[index]) - return Promise.resolve(entity[index]); - // find object metadata and try to load - return new QueryBuilder(this.driver, this.entityMetadatas, this.broadcaster) - .select(relation.propertyName) - .from(relation.target, relation.propertyName) // todo: change `id` after join column implemented - .where(relation.propertyName + ".id=:" + relation.propertyName + "Id") - .setParameter(relation.propertyName + "Id", entity[index]) - .getSingleResult() - .then(result => { - entity[index] = result; - return entity[index]; - }); - }, - set: (promise: Promise) => { - if (promise instanceof Promise) { - promise.then(result => entity[index] = result); - } else { - entity[index] = promise; - } - } - }); - }); - return entity; - } - /** * Creates entities from a given array of plain javascript objects. */ @@ -412,4 +376,40 @@ export class Repository { }); } + // todo: duplication + private addLazyProperties(entity: any) { + const metadata = this.entityMetadatas.findByTarget(entity.constructor); + metadata.relations + .filter(relation => relation.isLazy) + .forEach(relation => { + const index = "__" + relation.propertyName + "__"; + + Object.defineProperty(entity, relation.propertyName, { + get: () => { + if (entity[index]) + return Promise.resolve(entity[index]); + // find object metadata and try to load + return new QueryBuilder(this.driver, this.entityMetadatas, this.broadcaster) + .select(relation.propertyName) + .from(relation.target, relation.propertyName) // todo: change `id` after join column implemented + .where(relation.propertyName + ".id=:" + relation.propertyName + "Id") + .setParameter(relation.propertyName + "Id", entity[index]) + .getSingleResult() + .then(result => { + entity[index] = result; + return entity[index]; + }); + }, + set: (promise: Promise) => { + if (promise instanceof Promise) { + promise.then(result => entity[index] = result); + } else { + entity[index] = promise; + } + } + }); + }); + return entity; + } + } \ No newline at end of file