fixed bug when relation was not updating when saving exist object from inverse side

This commit is contained in:
Umed Khudoiberdiev 2016-05-17 22:24:32 +05:00
parent f649ff98f2
commit 60bddd6c58
9 changed files with 203 additions and 4 deletions

View 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));

View 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[];
}

View 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;
}

View File

@ -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] }));

View File

@ -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) {

View File

@ -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);

View File

@ -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("---------------------------------------------------------");
}
}

View File

@ -0,0 +1,11 @@
import {RelationMetadata} from "../../metadata/RelationMetadata";
/**
* @internal
*/
export class UpdateByInverseSideOperation {
constructor(public targetEntity: any,
public fromEntity: any,
public fromRelation: RelationMetadata) {
}
}

View File

@ -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();
}