mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
fixed issue with persistment when new entitiy is inserted with already exist entities in relations
This commit is contained in:
parent
3cd0f830e9
commit
9ebb128979
@ -31,14 +31,14 @@ createMysqlConnection(options, [__dirname + "/entity"]).then(connection => {
|
||||
post.author = author;
|
||||
post.categories.push(category1, category2);
|
||||
|
||||
category1 = new PostCategory();
|
||||
/*category1 = new PostCategory();
|
||||
category1.name = "post category #1";
|
||||
|
||||
category2 = new PostCategory();
|
||||
category2.name = "post category #2";
|
||||
|
||||
author = new PostAuthor();
|
||||
author.name = "Umed";
|
||||
author.name = "Umed";*/
|
||||
|
||||
let blog = new Blog();
|
||||
blog.text = "Hello how are you?";
|
||||
|
||||
@ -129,7 +129,7 @@ export class Connection {
|
||||
/**
|
||||
* Gets repository for the given entity class.
|
||||
*/
|
||||
getRepository<Entity>(entityClass: ConstructorFunction<Entity>): Repository<Entity> {
|
||||
getRepository<Entity>(entityClass: ConstructorFunction<Entity>|Function): Repository<Entity> {
|
||||
const metadata = this.getMetadata(entityClass);
|
||||
const repoMeta = this.repositoryAndMetadatas.find(repoMeta => repoMeta.metadata === metadata);
|
||||
if (!repoMeta)
|
||||
|
||||
@ -54,9 +54,14 @@ export class EntityPersistOperationBuilder {
|
||||
/**
|
||||
* Finds columns and relations from entity2 which does not exist or does not match in entity1.
|
||||
*/
|
||||
buildFullPersistment(metadata: EntityMetadata, dbEntity: any, persistedEntity: any): PersistOperation {
|
||||
const dbEntities = this.extractObjectsById(dbEntity, metadata);
|
||||
const allPersistedEntities = this.extractObjectsById(persistedEntity, metadata);
|
||||
buildFullPersistment(metadata: EntityMetadata,
|
||||
dbEntity: any,
|
||||
persistedEntity: any,
|
||||
dbEntities: EntityWithId[],
|
||||
allPersistedEntities: EntityWithId[]): PersistOperation {
|
||||
|
||||
//const dbEntities = this.extractObjectsById(dbEntity, metadata);
|
||||
//const allPersistedEntities = this.extractObjectsById(persistedEntity, metadata);
|
||||
|
||||
const persistOperation = new PersistOperation();
|
||||
persistOperation.dbEntity = dbEntity;
|
||||
@ -76,17 +81,21 @@ export class EntityPersistOperationBuilder {
|
||||
/**
|
||||
* Finds columns and relations from entity2 which does not exist or does not match in entity1.
|
||||
*/
|
||||
buildOnlyRemovement(metadata: EntityMetadata, dbEntity: any, newEntity: any): PersistOperation {
|
||||
const dbEntities = this.extractObjectsById(dbEntity, metadata);
|
||||
const allEntities = this.extractObjectsById(newEntity, metadata);
|
||||
buildOnlyRemovement(metadata: EntityMetadata,
|
||||
dbEntity: any,
|
||||
persistedEntity: any,
|
||||
dbEntities: EntityWithId[],
|
||||
allPersistedEntities: EntityWithId[]): PersistOperation {
|
||||
// const dbEntities = this.extractObjectsById(dbEntity, metadata);
|
||||
// const allEntities = this.extractObjectsById(newEntity, metadata);
|
||||
|
||||
const persistOperation = new PersistOperation();
|
||||
persistOperation.dbEntity = dbEntity;
|
||||
persistOperation.persistedEntity = newEntity;
|
||||
persistOperation.persistedEntity = persistedEntity;
|
||||
persistOperation.allDbEntities = dbEntities;
|
||||
persistOperation.allPersistedEntities = allEntities;
|
||||
persistOperation.removes = this.findCascadeRemovedEntities(metadata, dbEntity, allEntities, null, null, null);
|
||||
persistOperation.junctionRemoves = this.findJunctionRemoveOperations(metadata, dbEntity, allEntities);
|
||||
persistOperation.allPersistedEntities = allPersistedEntities;
|
||||
persistOperation.removes = this.findCascadeRemovedEntities(metadata, dbEntity, allPersistedEntities, null, null, null);
|
||||
persistOperation.junctionRemoves = this.findJunctionRemoveOperations(metadata, dbEntity, allPersistedEntities);
|
||||
|
||||
return persistOperation;
|
||||
}
|
||||
@ -317,32 +326,6 @@ export class EntityPersistOperationBuilder {
|
||||
}, <JunctionInsertOperation[]> []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts unique objects from given entity and all its downside relations.
|
||||
*/
|
||||
private extractObjectsById(entity: any, metadata: EntityMetadata): EntityWithId[] {
|
||||
if (!entity)
|
||||
return [];
|
||||
|
||||
return metadata.relations
|
||||
.filter(relation => !!entity[relation.propertyName])
|
||||
.map(relation => {
|
||||
const relMetadata = relation.relatedEntityMetadata;
|
||||
if (!(entity[relation.propertyName] instanceof Array))
|
||||
return this.extractObjectsById(entity[relation.propertyName], relMetadata);
|
||||
|
||||
return entity[relation.propertyName]
|
||||
.map((subEntity: any) => this.extractObjectsById(subEntity, relMetadata))
|
||||
.reduce((col1: any[], col2: any[]) => col1.concat(col2), []); // flatten
|
||||
})
|
||||
.reduce((col1: any[], col2: any[]) => col1.concat(col2), []) // flatten
|
||||
.concat([{
|
||||
id: entity[metadata.primaryColumn.name],
|
||||
entity: entity
|
||||
}])
|
||||
.filter((entity: any, index: number, allEntities: any[]) => allEntities.indexOf(entity) === index); // unique
|
||||
}
|
||||
|
||||
private diffColumns(metadata: EntityMetadata, newEntity: any, dbEntity: any) {
|
||||
return metadata.columns
|
||||
.filter(column => !column.isVirtual)
|
||||
|
||||
@ -5,6 +5,7 @@ import {PlainObjectToNewEntityTransformer} from "../query-builder/transformer/Pl
|
||||
import {PlainObjectToDatabaseEntityTransformer} from "../query-builder/transformer/PlainObjectToDatabaseEntityTransformer";
|
||||
import {EntityPersistOperationBuilder} from "../persistment/EntityPersistOperationsBuilder";
|
||||
import {PersistOperationExecutor} from "../persistment/PersistOperationExecutor";
|
||||
import {EntityWithId} from "../persistment/operation/PersistOperation";
|
||||
|
||||
// todo: think how we can implement queryCount, queryManyAndCount
|
||||
|
||||
@ -83,13 +84,20 @@ export class Repository<Entity> {
|
||||
* Persists (saves) a given entity in the database.
|
||||
*/
|
||||
persist(entity: Entity) {
|
||||
let loadedDbEntity: any;
|
||||
const persister = new PersistOperationExecutor(this.connection);
|
||||
const builder = new EntityPersistOperationBuilder(this.connection);
|
||||
const allPersistedEntities = this.extractObjectsById(entity, this.metadata);
|
||||
const promise = !this.hasId(entity) ? Promise.resolve(null) : this.initialize(entity);
|
||||
return promise.then(dbEntity => {
|
||||
const builder = new EntityPersistOperationBuilder(this.connection);
|
||||
const persistOperation = builder.buildFullPersistment(this.metadata, dbEntity, entity);
|
||||
return persister.executePersistOperation(persistOperation);
|
||||
}).then(() => entity);
|
||||
return promise
|
||||
.then(dbEntity => {
|
||||
loadedDbEntity = dbEntity;
|
||||
return this.findNotLoadedIds(this.extractObjectsById(dbEntity, this.metadata), allPersistedEntities);
|
||||
}) // need to find db entities that were not loaded by initialize method
|
||||
.then(allDbEntities => {
|
||||
const persistOperation = builder.buildFullPersistment(this.metadata, loadedDbEntity, entity, allDbEntities, allPersistedEntities);
|
||||
return persister.executePersistOperation(persistOperation);
|
||||
}).then(() => entity);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -100,7 +108,9 @@ export class Repository<Entity> {
|
||||
return this.initialize(entity).then(dbEntity => {
|
||||
(<any> entity)[this.metadata.primaryColumn.name] = undefined;
|
||||
const builder = new EntityPersistOperationBuilder(this.connection);
|
||||
const persistOperation = builder.buildOnlyRemovement(this.metadata, dbEntity, entity);
|
||||
const dbEntities = this.extractObjectsById(dbEntity, this.metadata);
|
||||
const allPersistedEntities = this.extractObjectsById(entity, this.metadata);
|
||||
const persistOperation = builder.buildOnlyRemovement(this.metadata, dbEntity, entity, dbEntities, allPersistedEntities);
|
||||
return persister.executePersistOperation(persistOperation);
|
||||
}).then(() => entity);
|
||||
}
|
||||
@ -158,4 +168,63 @@ export class Repository<Entity> {
|
||||
.then(() => runInTransactionResult);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Private Methods
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* When ORM loads dbEntity it uses joins to load all entity dependencies. However when dbEntity is newly persisted
|
||||
* to the db, but uses already exist in the db relational entities, those entities cannot be loaded, and will
|
||||
* absent in dbEntities. To fix it, we need to go throw all persistedEntities we have, find out those which have
|
||||
* ids, check if we did not load them yet and try to load them. This algorithm will make sure that all dbEntities
|
||||
* are loaded. Further it will help insert operations to work correctly.
|
||||
*/
|
||||
private findNotLoadedIds(dbEntities: EntityWithId[], persistedEntities: EntityWithId[]): Promise<EntityWithId[]> {
|
||||
const missingDbEntitiesLoad = persistedEntities
|
||||
.filter(entityWithId => entityWithId.id !== null && entityWithId.id !== undefined)
|
||||
.filter(entityWithId => !dbEntities.find(dbEntity => dbEntity.entity.constructor === entityWithId.entity.constructor && dbEntity.id === entityWithId.id))
|
||||
.map(entityWithId => {
|
||||
const metadata = this.connection.getMetadata(entityWithId.entity.constructor);
|
||||
const repository = this.connection.getRepository(entityWithId.entity.constructor);
|
||||
return repository.findById(entityWithId.id).then(loadedEntity => {
|
||||
if (!loadedEntity) return undefined;
|
||||
|
||||
return {
|
||||
id: (<any> loadedEntity)[metadata.primaryColumn.name],
|
||||
entity: loadedEntity
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
return Promise.all(missingDbEntitiesLoad).then(missingDbEntities => {
|
||||
return dbEntities.concat(missingDbEntities.filter(dbEntity => !!dbEntity));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts unique objects from given entity and all its downside relations.
|
||||
*/
|
||||
private extractObjectsById(entity: any, metadata: EntityMetadata): EntityWithId[] {
|
||||
if (!entity)
|
||||
return [];
|
||||
|
||||
return metadata.relations
|
||||
.filter(relation => !!entity[relation.propertyName])
|
||||
.map(relation => {
|
||||
const relMetadata = relation.relatedEntityMetadata;
|
||||
if (!(entity[relation.propertyName] instanceof Array))
|
||||
return this.extractObjectsById(entity[relation.propertyName], relMetadata);
|
||||
|
||||
return entity[relation.propertyName]
|
||||
.map((subEntity: any) => this.extractObjectsById(subEntity, relMetadata))
|
||||
.reduce((col1: any[], col2: any[]) => col1.concat(col2), []); // flatten
|
||||
})
|
||||
.reduce((col1: any[], col2: any[]) => col1.concat(col2), []) // flatten
|
||||
.concat([{
|
||||
id: entity[metadata.primaryColumn.name],
|
||||
entity: entity
|
||||
}])
|
||||
.filter((entity: any, index: number, allEntities: any[]) => allEntities.indexOf(entity) === index); // unique
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user