mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
fixed issue when lazy relations are not loaded and issue when lot of not needed data is being loaded
This commit is contained in:
parent
4453f53393
commit
fee6cfd1f5
@ -27,13 +27,15 @@ export class LazyRelationsWrapper {
|
||||
if (relation.hasInverseSide) { // if we don't have inverse side then we can't select and join by relation from inverse side
|
||||
qb.select(relation.propertyName)
|
||||
.from(relation.inverseRelation.entityMetadata.target, relation.propertyName)
|
||||
.innerJoin(`${relation.propertyName}.${relation.inverseRelation.propertyName}`, relation.entityMetadata.targetName);
|
||||
.innerJoin(`${relation.propertyName}.${relation.inverseRelation.propertyName}`, relation.entityMetadata.targetName)
|
||||
.andWhereInIds([relation.entityMetadata.getEntityIdMixedMap(this)]);
|
||||
} else {
|
||||
qb.select(relation.propertyName)
|
||||
.from(relation.type, relation.propertyName)
|
||||
.innerJoin(relation.junctionEntityMetadata.table.name, relation.junctionEntityMetadata.name,
|
||||
`${relation.junctionEntityMetadata.name}.${relation.name}=:${relation.propertyName}Id`)
|
||||
.setParameter(relation.propertyName + "Id", this[relation.referencedColumnName]);
|
||||
.setParameter(relation.propertyName + "Id", this[relation.referencedColumnName])
|
||||
.andWhereInIds([relation.entityMetadata.getEntityIdMixedMap(this)]);
|
||||
}
|
||||
|
||||
this[loadIndex] = qb.getMany().then(results => {
|
||||
@ -51,18 +53,21 @@ export class LazyRelationsWrapper {
|
||||
if (relation.hasInverseSide) {
|
||||
qb.select(relation.propertyName)
|
||||
.from(relation.inverseRelation.entityMetadata.target, relation.propertyName)
|
||||
.innerJoin(`${relation.propertyName}.${relation.inverseRelation.propertyName}`, relation.entityMetadata.targetName);
|
||||
.innerJoin(`${relation.propertyName}.${relation.inverseRelation.propertyName}`, relation.entityMetadata.targetName)
|
||||
.andWhereInIds([relation.entityMetadata.getEntityIdMixedMap(this)]);
|
||||
|
||||
} else {
|
||||
// (ow) post.category<=>category.post
|
||||
// loaded: category from post
|
||||
// example: SELECT category.id AS category_id, category.name AS category_name FROM category category
|
||||
// INNER JOIN post Post ON Post.category=category.id WHERE Post.id=1
|
||||
qb.select(relation.propertyName) // category
|
||||
.from(relation.type, relation.propertyName) // Category, category
|
||||
.innerJoin(relation.entityMetadata.target as Function, relation.entityMetadata.name,
|
||||
`${relation.entityMetadata.name}.${relation.propertyName}=:${relation.propertyName}Id`) // Post, post, post.category = categoryId
|
||||
.setParameter(relation.propertyName + "Id", this[relation.referencedColumnName]);
|
||||
`${relation.entityMetadata.name}.${relation.propertyName}=${relation.propertyName}.${relation.referencedColumn.propertyName}`)
|
||||
.andWhereInIds([relation.entityMetadata.getEntityIdMixedMap(this)]);
|
||||
}
|
||||
// console.log(qb.getSql());
|
||||
|
||||
this[loadIndex] = qb.getOne().then(result => {
|
||||
this[index] = result;
|
||||
this[resolveIndex] = true;
|
||||
|
||||
@ -417,6 +417,13 @@ export class RelationMetadata {
|
||||
return this.isLazy ? entity["__" + this.propertyName + "__"] : entity[this.propertyName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if given entity has a value in a relation.
|
||||
*/
|
||||
hasEntityValue(entity: ObjectLiteral): boolean {
|
||||
return this.isLazy ? entity["__" + this.propertyName + "__"] : entity[this.propertyName];
|
||||
}
|
||||
|
||||
/**
|
||||
* todo: lazy relations are not supported here? implement logic?
|
||||
*
|
||||
|
||||
@ -83,6 +83,9 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
|
||||
// Public Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Builds operations for entity that is being inserted/updated.
|
||||
*/
|
||||
async persist(entity: Entity, metadata: EntityMetadata): Promise<void> {
|
||||
|
||||
// create subject for currently persisted entity and mark that it can be inserted and updated
|
||||
@ -111,6 +114,9 @@ export class SubjectBuilder<Entity extends ObjectLiteral> {
|
||||
await this.buildJunctionOperations({ insert: true, remove: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds only remove operations for entity that is being removed.
|
||||
*/
|
||||
async remove(entity: Entity, metadata: EntityMetadata): Promise<void> {
|
||||
|
||||
// create subject for currently removed entity and mark that it must be removed
|
||||
|
||||
@ -180,7 +180,7 @@ export class SubjectOperationExecutor {
|
||||
const updateOptions: ObjectLiteral = {};
|
||||
subject.metadata.relationsWithJoinColumns.forEach(relation => {
|
||||
const referencedColumn = relation.joinColumn.referencedColumn;
|
||||
const relatedEntity = subject.entity[relation.propertyName];
|
||||
const relatedEntity = relation.getEntityValue(subject.entity);
|
||||
|
||||
// if relation value is not set then nothing to do here
|
||||
if (!relatedEntity)
|
||||
@ -373,7 +373,7 @@ export class SubjectOperationExecutor {
|
||||
/**
|
||||
* Collects columns and values for the insert operation.
|
||||
*/
|
||||
private collectColumnsAndValues(metadata: EntityMetadata, entity: any, date: Date, parentIdColumnValue: any, discriminatorValue: any, alreadyInsertedSubjects: Subject[]): ObjectLiteral {
|
||||
private collectColumnsAndValues(metadata: EntityMetadata, entity: ObjectLiteral, date: Date, parentIdColumnValue: any, discriminatorValue: any, alreadyInsertedSubjects: Subject[]): ObjectLiteral {
|
||||
|
||||
// extract all columns
|
||||
const columns = metadata.columns.filter(column => {
|
||||
@ -381,7 +381,7 @@ export class SubjectOperationExecutor {
|
||||
});
|
||||
|
||||
const relationColumns = metadata.relationsWithJoinColumns
|
||||
.filter(relation => entity.hasOwnProperty(relation.propertyName));
|
||||
.filter(relation => relation.hasEntityValue(entity));
|
||||
|
||||
const columnNames = columns.map(column => column.name);
|
||||
const relationColumnNames = relationColumns.map(relation => relation.name);
|
||||
@ -394,8 +394,6 @@ export class SubjectOperationExecutor {
|
||||
// extract all values
|
||||
const relationValues = relationColumns.map(relation => {
|
||||
const value = relation.getEntityValue(entity);
|
||||
if (value === null || value === undefined)
|
||||
return value;
|
||||
|
||||
// if relation value is stored in the entity itself then use it from there
|
||||
const relationId = relation.getInverseEntityRelationId(value); // todo: check it
|
||||
|
||||
19
test/issues/47/entity/Category.ts
Normal file
19
test/issues/47/entity/Category.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import {Table} from "../../../../src/decorator/tables/Table";
|
||||
import {PrimaryGeneratedColumn} from "../../../../src/decorator/columns/PrimaryGeneratedColumn";
|
||||
import {Column} from "../../../../src/decorator/columns/Column";
|
||||
import {Post} from "./Post";
|
||||
import {OneToMany} from "../../../../src/decorator/relations/OneToMany";
|
||||
|
||||
@Table()
|
||||
export class Category {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@OneToMany(() => Post, post => post.category)
|
||||
posts: Promise<Post[]>;
|
||||
|
||||
}
|
||||
21
test/issues/47/entity/Post.ts
Normal file
21
test/issues/47/entity/Post.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import {Table} from "../../../../src/decorator/tables/Table";
|
||||
import {PrimaryGeneratedColumn} from "../../../../src/decorator/columns/PrimaryGeneratedColumn";
|
||||
import {Column} from "../../../../src/decorator/columns/Column";
|
||||
import {Category} from "./Category";
|
||||
import {ManyToOne} from "../../../../src/decorator/relations/ManyToOne";
|
||||
|
||||
@Table()
|
||||
export class Post {
|
||||
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
title: string;
|
||||
|
||||
@ManyToOne(() => Category, category => category.posts, {
|
||||
cascadeInsert: true
|
||||
})
|
||||
category: Promise<Category>;
|
||||
|
||||
}
|
||||
79
test/issues/47/issue-47.ts
Normal file
79
test/issues/47/issue-47.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import "reflect-metadata";
|
||||
import {setupTestingConnections, closeConnections, reloadDatabases} from "../../utils/test-utils";
|
||||
import {Connection} from "../../../src/connection/Connection";
|
||||
import {Post} from "./entity/Post";
|
||||
import {expect} from "chai";
|
||||
import {Category} from "./entity/Category";
|
||||
|
||||
describe("github issues > #47 wrong sql syntax when loading lazy relation", () => {
|
||||
|
||||
let connections: Connection[];
|
||||
before(async () => connections = await setupTestingConnections({
|
||||
entities: [__dirname + "/entity/*{.js,.ts}"],
|
||||
schemaCreate: true,
|
||||
dropSchemaOnConnection: true,
|
||||
}));
|
||||
beforeEach(() => reloadDatabases(connections));
|
||||
after(() => closeConnections(connections));
|
||||
|
||||
it("should persist successfully and return persisted entity", () => Promise.all(connections.map(async connection => {
|
||||
|
||||
// create objects to save
|
||||
const category1 = new Category();
|
||||
category1.name = "category #1";
|
||||
|
||||
const post1 = new Post();
|
||||
post1.title = "Hello Post #1";
|
||||
post1.category = Promise.resolve(category1);
|
||||
|
||||
const category2 = new Category();
|
||||
category2.name = "category #2";
|
||||
|
||||
const post2 = new Post();
|
||||
post2.title = "Hello Post #2";
|
||||
post2.category = Promise.resolve(category2);
|
||||
|
||||
// persist
|
||||
await connection.entityManager.persist(category1);
|
||||
await connection.entityManager.persist(post1);
|
||||
await connection.entityManager.persist(category2);
|
||||
await connection.entityManager.persist(post2);
|
||||
|
||||
// check that all persisted objects exist
|
||||
const loadedPost = await connection.entityManager
|
||||
.createQueryBuilder(Post, "post")
|
||||
.getMany();
|
||||
|
||||
const loadedCategory1 = await loadedPost[0].category;
|
||||
expect(loadedCategory1!).not.to.be.empty;
|
||||
loadedCategory1!.should.be.eql({
|
||||
id: 1,
|
||||
name: "category #1"
|
||||
});
|
||||
|
||||
const loadedCategory2 = await loadedPost[1].category;
|
||||
expect(loadedCategory2!).not.to.be.empty;
|
||||
loadedCategory2!.should.be.eql({
|
||||
id: 2,
|
||||
name: "category #2"
|
||||
});
|
||||
|
||||
const loadedPosts1 = await loadedCategory1.posts;
|
||||
expect(loadedPosts1!).not.to.be.empty;
|
||||
loadedPosts1!.should.be.eql([{
|
||||
id: 1,
|
||||
title: "Hello Post #1"
|
||||
}]);
|
||||
|
||||
const loadedPosts2 = await loadedCategory2.posts;
|
||||
expect(loadedPosts2!).not.to.be.empty;
|
||||
loadedPosts2!.should.be.eql([{
|
||||
id: 2,
|
||||
title: "Hello Post #2"
|
||||
}]);
|
||||
|
||||
// todo: need to test somehow how query is being generated, or how many raw data is returned
|
||||
|
||||
})));
|
||||
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user