mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
fixed bug when relation was not updating when saving exist object from inverse side
This commit is contained in:
parent
f649ff98f2
commit
60bddd6c58
62
sample/sample25-insert-from-inverse-side/app.ts
Normal file
62
sample/sample25-insert-from-inverse-side/app.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import "reflect-metadata";
|
||||
import {createConnection, CreateConnectionOptions} from "../../src/typeorm";
|
||||
import {Post} from "./entity/Post";
|
||||
import {Author} from "./entity/Author";
|
||||
|
||||
const options: CreateConnectionOptions = {
|
||||
driver: "mysql",
|
||||
connection: {
|
||||
host: "192.168.99.100",
|
||||
port: 3306,
|
||||
username: "root",
|
||||
password: "admin",
|
||||
database: "test",
|
||||
autoSchemaCreate: true,
|
||||
logging: {
|
||||
logOnlyFailedQueries: true,
|
||||
logFailedQueryError: true
|
||||
}
|
||||
},
|
||||
entities: [Post, Author]
|
||||
};
|
||||
|
||||
createConnection(options).then(connection => {
|
||||
|
||||
let postRepository = connection.getRepository(Post);
|
||||
let authorRepository = connection.getRepository(Author);
|
||||
|
||||
const authorPromise = authorRepository.findOneById(1).then(author => {
|
||||
if (!author) {
|
||||
author = new Author();
|
||||
author.name = "Umed";
|
||||
return authorRepository.persist(author).then(savedAuthor => {
|
||||
return authorRepository.findOneById(1);
|
||||
});
|
||||
}
|
||||
return author;
|
||||
});
|
||||
|
||||
const postPromise = postRepository.findOneById(1).then(post => {
|
||||
if (!post) {
|
||||
post = new Post();
|
||||
post.title = "Hello post";
|
||||
post.text = "This is post contents";
|
||||
return postRepository.persist(post).then(savedPost => {
|
||||
return postRepository.findOneById(1);
|
||||
});
|
||||
}
|
||||
return post;
|
||||
});
|
||||
|
||||
return Promise.all<any>([authorPromise, postPromise])
|
||||
.then(results => {
|
||||
const [author, post] = results;
|
||||
author.posts = [post];
|
||||
return authorRepository.persist(author);
|
||||
})
|
||||
.then(savedAuthor => {
|
||||
console.log("Author has been saved: ", savedAuthor);
|
||||
})
|
||||
.catch(error => console.log(error.stack));
|
||||
|
||||
}, error => console.log("Cannot connect: ", error));
|
||||
18
sample/sample25-insert-from-inverse-side/entity/Author.ts
Normal file
18
sample/sample25-insert-from-inverse-side/entity/Author.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import {PrimaryColumn, Column} from "../../../src/columns";
|
||||
import {Table} from "../../../src/tables";
|
||||
import {OneToMany} from "../../../src/decorator/relations/OneToMany";
|
||||
import {Post} from "./Post";
|
||||
|
||||
@Table("sample25_author")
|
||||
export class Author {
|
||||
|
||||
@PrimaryColumn("int", { generated: true })
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
name: string;
|
||||
|
||||
@OneToMany(type => Post, author => author.author)
|
||||
posts: Post[];
|
||||
|
||||
}
|
||||
21
sample/sample25-insert-from-inverse-side/entity/Post.ts
Normal file
21
sample/sample25-insert-from-inverse-side/entity/Post.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import {PrimaryColumn, Column} from "../../../src/columns";
|
||||
import {Table} from "../../../src/tables";
|
||||
import {Author} from "./Author";
|
||||
import {ManyToOne} from "../../../src/decorator/relations/ManyToOne";
|
||||
|
||||
@Table("sample25_post")
|
||||
export class Post {
|
||||
|
||||
@PrimaryColumn("int", { generated: true })
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
title: string;
|
||||
|
||||
@Column()
|
||||
text: string;
|
||||
|
||||
@ManyToOne(type => Author, author => author.posts)
|
||||
author: Author;
|
||||
|
||||
}
|
||||
@ -183,6 +183,7 @@ export class MysqlDriver extends BaseDriver implements Driver {
|
||||
const updateValues = this.escapeObjectMap(valuesMap).join(",");
|
||||
const conditionString = this.escapeObjectMap(conditions).join(" AND ");
|
||||
const query = `UPDATE ${tableName} SET ${updateValues} ${conditionString ? (" WHERE " + conditionString) : ""}`;
|
||||
// console.log("executing update: ", query);
|
||||
return this.query(query).then(() => {});
|
||||
// const qb = this.createQueryBuilder().update(tableName, valuesMap).from(tableName, "t");
|
||||
// Object.keys(conditions).forEach(key => qb.andWhere(key + "=:" + key, { [key]: (<any> conditions)[key] }));
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import {EntityMetadata} from "../metadata/EntityMetadata";
|
||||
import {RelationMetadata} from "../metadata/RelationMetadata";
|
||||
import {Connection} from "../connection/Connection";
|
||||
import {PersistOperation, EntityWithId} from "./operation/PersistOperation";
|
||||
import {InsertOperation} from "./operation/InsertOperation";
|
||||
import {UpdateByRelationOperation} from "./operation/UpdateByRelationOperation";
|
||||
@ -9,6 +8,7 @@ import {UpdateOperation} from "./operation/UpdateOperation";
|
||||
import {CascadesNotAllowedError} from "./error/CascadesNotAllowedError";
|
||||
import {RemoveOperation} from "./operation/RemoveOperation";
|
||||
import {EntityMetadataCollection} from "../metadata/collection/EntityMetadataCollection";
|
||||
import {UpdateByInverseSideOperation} from "./operation/UpdateByInverseSideOperation";
|
||||
|
||||
/**
|
||||
* 1. collect all exist objects from the db entity
|
||||
@ -72,10 +72,12 @@ export class EntityPersistOperationBuilder {
|
||||
persistOperation.allPersistedEntities = allPersistedEntities;
|
||||
persistOperation.inserts = this.findCascadeInsertedEntities(persistedEntity, dbEntities);
|
||||
persistOperation.updatesByRelations = this.updateRelations(persistOperation.inserts, persistedEntity);
|
||||
persistOperation.updatesByInverseRelations = this.updateInverseRelations(metadata, dbEntity, persistedEntity);
|
||||
persistOperation.updates = this.findCascadeUpdateEntities(persistOperation.updatesByRelations, metadata, dbEntity, persistedEntity);
|
||||
persistOperation.junctionInserts = this.findJunctionInsertOperations(metadata, persistedEntity, dbEntities);
|
||||
persistOperation.removes = this.findCascadeRemovedEntities(metadata, dbEntity, allPersistedEntities, undefined, undefined, undefined);
|
||||
persistOperation.junctionRemoves = this.findJunctionRemoveOperations(metadata, dbEntity, allPersistedEntities);
|
||||
|
||||
return persistOperation;
|
||||
}
|
||||
|
||||
@ -221,6 +223,31 @@ export class EntityPersistOperationBuilder {
|
||||
return operations;
|
||||
}
|
||||
|
||||
private updateInverseRelations(metadata: EntityMetadata,
|
||||
dbEntity: any,
|
||||
newEntity: any,
|
||||
operations: UpdateByInverseSideOperation[] = []): UpdateByInverseSideOperation[] {
|
||||
metadata.relations
|
||||
.filter(relation => relation.isOneToMany) // todo: maybe need to check isOneToOne and not owner
|
||||
.filter(relation => newEntity[relation.propertyName] instanceof Array) // todo: what to do with empty relations? need to set to NULL from inverse side?
|
||||
.forEach(relation => {
|
||||
|
||||
// to find new objects in relation go throw all objects in newEntity and check if they don't exist in dbEntity
|
||||
newEntity[relation.propertyName].filter((subEntity: any) => {
|
||||
if (!dbEntity /* are you sure about this? */ || !dbEntity[relation.propertyName]) // if there is no items in dbEntity - then all items in newEntity are new
|
||||
return true;
|
||||
|
||||
return !dbEntity[relation.propertyName].find((dbSubEntity: any) => {
|
||||
return relation.inverseEntityMetadata.getEntityId(subEntity) === relation.inverseEntityMetadata.getEntityId(dbSubEntity);
|
||||
});
|
||||
}).forEach((subEntity: any) => {
|
||||
operations.push(new UpdateByInverseSideOperation(subEntity, newEntity, relation));
|
||||
});
|
||||
});
|
||||
|
||||
return operations;
|
||||
}
|
||||
|
||||
/**
|
||||
* To update relation, you need:
|
||||
* update table where this relation (owner side)
|
||||
@ -351,7 +378,16 @@ export class EntityPersistOperationBuilder {
|
||||
return metadata.relations
|
||||
.filter(relation => relation.isManyToOne || (relation.isOneToOne && relation.isOwning))
|
||||
.filter(relation => !updatesByRelations.find(operation => operation.targetEntity === newEntity && operation.updatedRelation === relation)) // try to find if there is update by relation operation - we dont need to generate update relation operation for this
|
||||
.filter(relation => newEntity[relation.propertyName] !== dbEntity[relation.name]);
|
||||
.filter(relation => {
|
||||
if (!newEntity[relation.propertyName] && !dbEntity[relation.name])
|
||||
return false;
|
||||
if (!newEntity[relation.propertyName] || !dbEntity[relation.name])
|
||||
return true;
|
||||
|
||||
const newEntityRelationMetadata = this.entityMetadatas.findByTarget(newEntity[relation.propertyName].constructor);
|
||||
const dbEntityRelationMetadata = this.entityMetadatas.findByTarget(dbEntity[relation.name].constructor);
|
||||
return newEntityRelationMetadata.getEntityId(newEntity[relation.propertyName]) !== dbEntityRelationMetadata.getEntityId(dbEntity[relation.name]);
|
||||
});
|
||||
}
|
||||
|
||||
private findEntityWithId(entityWithIds: EntityWithId[], entityClass: Function, id: any) {
|
||||
|
||||
@ -8,6 +8,7 @@ import {UpdateByRelationOperation} from "./operation/UpdateByRelationOperation";
|
||||
import {Broadcaster} from "../subscriber/Broadcaster";
|
||||
import {EntityMetadataCollection} from "../metadata/collection/EntityMetadataCollection";
|
||||
import {Driver} from "../driver/Driver";
|
||||
import {UpdateByInverseSideOperation} from "./operation/UpdateByInverseSideOperation";
|
||||
|
||||
/**
|
||||
* Executes PersistOperation in the given connection.
|
||||
@ -43,6 +44,7 @@ export class PersistOperationExecutor {
|
||||
.then(() => this.executeRemoveJunctionsOperations(persistOperation))
|
||||
.then(() => this.executeRemoveRelationOperations(persistOperation))
|
||||
.then(() => this.executeUpdateRelationsOperations(persistOperation))
|
||||
.then(() => this.executeUpdateInverseRelationsOperations(persistOperation))
|
||||
.then(() => this.executeUpdateOperations(persistOperation))
|
||||
.then(() => this.executeRemoveOperations(persistOperation))
|
||||
.then(() => this.driver.endTransaction())
|
||||
@ -169,6 +171,15 @@ export class PersistOperationExecutor {
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes update relations operations.
|
||||
*/
|
||||
private executeUpdateInverseRelationsOperations(persistOperation: PersistOperation) {
|
||||
return Promise.all(persistOperation.updatesByInverseRelations.map(updateInverseOperation => {
|
||||
return this.updateInverseRelation(updateInverseOperation);
|
||||
}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes update operations.
|
||||
*/
|
||||
@ -301,6 +312,36 @@ export class PersistOperationExecutor {
|
||||
return this.driver.update(tableName, { [relationName]: relationId }, { [idColumn]: id });
|
||||
}
|
||||
|
||||
private updateInverseRelation(operation: UpdateByInverseSideOperation) {
|
||||
/*let tableName: string, relationName: string, relationId: any, idColumn: string, id: any;
|
||||
const relatedInsertOperation = insertOperations.find(o => o.entity === operation.targetEntity);
|
||||
const idInInserts = relatedInsertOperation ? relatedInsertOperation.entityId : null;
|
||||
if (operation.updatedRelation.isOneToMany) {
|
||||
const metadata = this.entityMetadatas.findByTarget(operation.insertOperation.entity.constructor);
|
||||
tableName = metadata.table.name;
|
||||
relationName = operation.updatedRelation.inverseRelation.name;
|
||||
relationId = operation.targetEntity[metadata.primaryColumn.propertyName] || idInInserts;
|
||||
idColumn = metadata.primaryColumn.name;
|
||||
id = operation.insertOperation.entityId;
|
||||
|
||||
} else {
|
||||
const metadata = this.entityMetadatas.findByTarget(operation.targetEntity.constructor);
|
||||
tableName = metadata.table.name;
|
||||
relationName = operation.updatedRelation.name;
|
||||
relationId = operation.insertOperation.entityId;
|
||||
idColumn = metadata.primaryColumn.name;
|
||||
id = operation.targetEntity[metadata.primaryColumn.propertyName] || idInInserts;
|
||||
}*/
|
||||
const targetEntityMetadata = this.entityMetadatas.findByTarget(operation.targetEntity.constructor);
|
||||
const tableName = targetEntityMetadata.table.name;
|
||||
const targetRelation = operation.fromRelation.inverseRelation;
|
||||
const targetEntityId = operation.fromEntity[targetRelation.joinColumn.referencedColumn.name];
|
||||
const idColumn = targetEntityMetadata.primaryColumn.name;
|
||||
const id = targetEntityMetadata.getEntityId(operation.targetEntity);
|
||||
|
||||
return this.driver.update(tableName, { [targetRelation.name]: targetEntityId }, { [idColumn]: id });
|
||||
}
|
||||
|
||||
private update(updateOperation: UpdateOperation) {
|
||||
const entity = updateOperation.entity;
|
||||
const metadata = this.entityMetadatas.findByTarget(entity.constructor);
|
||||
|
||||
@ -4,6 +4,7 @@ import {UpdateOperation} from "./UpdateOperation";
|
||||
import {JunctionInsertOperation} from "./JunctionInsertOperation";
|
||||
import {JunctionRemoveOperation} from "./JunctionRemoveOperation";
|
||||
import {UpdateByRelationOperation} from "./UpdateByRelationOperation";
|
||||
import {UpdateByInverseSideOperation} from "./UpdateByInverseSideOperation";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
@ -30,6 +31,7 @@ export class PersistOperation {
|
||||
junctionInserts: JunctionInsertOperation[] = [];
|
||||
junctionRemoves: JunctionRemoveOperation[] = [];
|
||||
updatesByRelations: UpdateByRelationOperation[] = [];
|
||||
updatesByInverseRelations: UpdateByInverseSideOperation[] = [];
|
||||
|
||||
log() {
|
||||
console.log("---------------------------------------------------------");
|
||||
@ -73,6 +75,10 @@ export class PersistOperation {
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log(this.updatesByRelations);
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log("UPDATES BY INVERSE RELATIONS");
|
||||
console.log("---------------------------------------------------------");
|
||||
console.log(this.updatesByInverseRelations);
|
||||
console.log("---------------------------------------------------------");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
11
src/persistment/operation/UpdateByInverseSideOperation.ts
Normal file
11
src/persistment/operation/UpdateByInverseSideOperation.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {RelationMetadata} from "../../metadata/RelationMetadata";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class UpdateByInverseSideOperation {
|
||||
constructor(public targetEntity: any,
|
||||
public fromEntity: any,
|
||||
public fromRelation: RelationMetadata) {
|
||||
}
|
||||
}
|
||||
@ -31,9 +31,12 @@ export class PlainObjectToDatabaseEntityTransformer<Entity> {
|
||||
const needToLoad = this.buildLoadMap(object, metadata, true);
|
||||
|
||||
this.join(queryBuilder, needToLoad, alias);
|
||||
return queryBuilder
|
||||
|
||||
queryBuilder
|
||||
.where(alias + "." + metadata.primaryColumn.name + "=:id")
|
||||
.setParameter("id", object[metadata.primaryColumn.name])
|
||||
.setParameter("id", object[metadata.primaryColumn.name]);
|
||||
|
||||
return queryBuilder
|
||||
.getSingleResult();
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user