fixed issue with persistment when new entitiy is inserted with already exist entities in relations

This commit is contained in:
Umed Khudoiberdiev 2016-03-21 18:31:30 +05:00
parent 3cd0f830e9
commit 9ebb128979
4 changed files with 97 additions and 45 deletions

View File

@ -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?";

View File

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

View File

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

View File

@ -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
}
}