mirror of
https://github.com/typeorm/typeorm.git
synced 2025-12-08 21:26:23 +00:00
more experiments over new persistent mechanizm
This commit is contained in:
parent
aa8ac65f58
commit
dfecec0fc9
@ -77,5 +77,5 @@ export class JoinColumnMetadata {
|
||||
|
||||
return this.relation.inverseEntityMetadata.firstPrimaryColumn;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -5,6 +5,7 @@ import {Subject} from "./subject/Subject";
|
||||
import {SubjectCollection} from "./subject/SubjectCollection";
|
||||
import {NewJunctionRemoveOperation} from "./operation/NewJunctionRemoveOperation";
|
||||
import {NewJunctionInsertOperation} from "./operation/NewJunctionInsertOperation";
|
||||
const DepGraph = require("dependency-graph").DepGraph;
|
||||
|
||||
|
||||
// at the end, subjects which does not have database entities are newly persisted entities
|
||||
@ -111,6 +112,7 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
|
||||
// persistedEntity.mustBeRemoved = true;
|
||||
// todo: execute operations
|
||||
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
@ -135,6 +137,10 @@ export class DatabaseEntityLoader<Entity extends ObjectLiteral> {
|
||||
// if there is a value in the relation and insert or update cascades are set - it means we must load entity
|
||||
if (relation.isEntityDefined(value) && (relation.isCascadeInsert || relation.isCascadeUpdate)) {
|
||||
|
||||
// if we already has this entity in list of loaded subjects then skip it to avoid recursion
|
||||
if (this.loadedSubjects.hasWithEntity(value))
|
||||
return;
|
||||
|
||||
// add to the array of subjects to load only if there is no same entity there already
|
||||
const subject = new Subject(valueMetadata, value); // todo: store relations inside to create correct order then? // todo: try to find by likeDatabaseEntity and replace its persistment entity?
|
||||
subject.canBeInserted = relation.isCascadeInsert === true;
|
||||
|
||||
@ -13,6 +13,7 @@ import {CascadesNotAllowedError} from "./error/CascadesNotAllowedError";
|
||||
import {NewUpdateOperation} from "./operation/NewUpdateOperation";
|
||||
import {NewRemoveOperation} from "./operation/NewRemoveOperation";
|
||||
import {DatabaseEntityLoader} from "./DatabaseEntityLoader";
|
||||
import {PersistSubjectExecutor} from "./PersistSubjectExecutor";
|
||||
|
||||
/**
|
||||
* Manages entity persistence - insert, update and remove of entity.
|
||||
@ -122,6 +123,11 @@ export class EntityPersister<Entity extends ObjectLiteral> {
|
||||
const databaseEntityLoader = new DatabaseEntityLoader(this.connection);
|
||||
await databaseEntityLoader.load(entity, this.metadata);
|
||||
console.log("all persistence subjects: ", databaseEntityLoader.loadedSubjects);
|
||||
|
||||
|
||||
const executor = new PersistSubjectExecutor(this.connection, this.queryRunner);
|
||||
await executor.execute(databaseEntityLoader.loadedSubjects, databaseEntityLoader.junctionInsertOperations, databaseEntityLoader.junctionRemoveOperations);
|
||||
|
||||
return entity;
|
||||
|
||||
// const allNewEntities = await this.flattenEntityRelationTree(entity, this.metadata);
|
||||
|
||||
@ -43,7 +43,7 @@ export class PersistSubjectExecutor {
|
||||
// check if remove subject also must be inserted or updated - then we throw an exception
|
||||
const removeInInserts = removeSubjects.find(removeSubject => insertSubjects.indexOf(removeSubject) !== -1);
|
||||
if (removeInInserts)
|
||||
throw new Error(`Removed entity ${removeInInserts.entityTarget} is also scheduled for insert operation.`);
|
||||
throw new Error(`Removed entity ${removeInInserts.entityTarget} is also scheduled for insert operation. This looks like ORM problem. Please report a github issue.`);
|
||||
|
||||
const removeInUpdates = removeSubjects.find(removeSubject => updateSubjects.indexOf(removeSubject) !== -1);
|
||||
if (removeInUpdates)
|
||||
@ -62,22 +62,22 @@ export class PersistSubjectExecutor {
|
||||
}
|
||||
|
||||
await this.executeInsertOperations(insertSubjects);
|
||||
await this.executeInsertClosureTableOperations(insertSubjects);
|
||||
await this.executeUpdateTreeLevelOperations(insertSubjects);
|
||||
await this.executeInsertJunctionsOperations(junctionInsertOperations, insertSubjects);
|
||||
await this.executeRemoveJunctionsOperations(junctionRemoveOperations);
|
||||
await this.executeRemoveRelationOperations(persistOperation); // todo: can we add these operations into list of updated?
|
||||
await this.executeUpdateRelationsOperations(persistOperation); // todo: merge these operations with update operations?
|
||||
await this.executeUpdateInverseRelationsOperations(persistOperation); // todo: merge these operations with update operations?
|
||||
await this.executeUpdateOperations(updateSubjects);
|
||||
await this.executeRemoveOperations(removeSubjects);
|
||||
// await this.executeInsertClosureTableOperations(insertSubjects);
|
||||
// await this.executeUpdateTreeLevelOperations(insertSubjects);
|
||||
// await this.executeInsertJunctionsOperations(junctionInsertOperations, insertSubjects);
|
||||
// await this.executeRemoveJunctionsOperations(junctionRemoveOperations);
|
||||
// await this.executeRemoveRelationOperations(persistOperation); // todo: can we add these operations into list of updated?
|
||||
// await this.executeUpdateRelationsOperations(persistOperation); // todo: merge these operations with update operations?
|
||||
// await this.executeUpdateInverseRelationsOperations(persistOperation); // todo: merge these operations with update operations?
|
||||
// await this.executeUpdateOperations(updateSubjects);
|
||||
// await this.executeRemoveOperations(removeSubjects);
|
||||
|
||||
// commit transaction if it was started by us
|
||||
if (isTransactionStartedByItself === true)
|
||||
await this.queryRunner.commitTransaction();
|
||||
|
||||
// update all special columns in persisted entities, like inserted id or remove ids from the removed entities
|
||||
await this.updateSpecialColumnsInPersistedEntities(insertSubjects, updateSubjects);
|
||||
// await this.updateSpecialColumnsInPersistedEntities(insertSubjects, updateSubjects, removeSubjects);
|
||||
|
||||
// finally broadcast events
|
||||
this.connection.broadcaster.broadcastAfterEventsForAll(insertSubjects, updateSubjects, removeSubjects);
|
||||
@ -103,7 +103,35 @@ export class PersistSubjectExecutor {
|
||||
* Executes insert operations.
|
||||
*/
|
||||
private async executeInsertOperations(insertSubjects: Subject[]): Promise<void> {
|
||||
await Promise.all(insertSubjects.map(subject => this.insert(subject)));
|
||||
|
||||
// for insertion we separate two groups of entities:
|
||||
// - first group of entities are entities which does not have any relations
|
||||
// or entities which does not have any non-nullable relation
|
||||
// - second group of entities are entities which does have non-nullable relations
|
||||
// note: these two groups should be inserted in sequence, not in parallel, because second group is depend on first
|
||||
|
||||
// insert process of the entities from the first group which can only have nullable relations are actually a two-step process:
|
||||
// - first we insert entities without their relations, explicitly left them NULL
|
||||
// - later we update inserted entity once again with id of the object inserted with it
|
||||
// yes, two queries are being executed, but this is by design.
|
||||
// there is no better way to solve this problem and others at the same time.
|
||||
|
||||
// insert process of the entities from the second group which can have only non nullable relations is a single-step process:
|
||||
// - we simply insert all entities and get into attention all its dependencies which were inserted in the first group
|
||||
|
||||
const firstInsertSubjects = insertSubjects.filter(subject => {
|
||||
const relationsWithJoinColumns = subject.metadata.relationsWithJoinColumns;
|
||||
return relationsWithJoinColumns.length === 0 || !!relationsWithJoinColumns.find(relation => relation.isNullable);
|
||||
});
|
||||
const secondInsertSubjects = insertSubjects.filter(subject => {
|
||||
return !firstInsertSubjects.find(subjectFromFirstGroup => subjectFromFirstGroup === subject);
|
||||
});
|
||||
|
||||
// console.log("firstInsertSubjects: ", firstInsertSubjects);
|
||||
// console.log("secondInsertSubjects: ", secondInsertSubjects);
|
||||
|
||||
await Promise.all(firstInsertSubjects.map(subject => this.insert(subject, [])));
|
||||
await Promise.all(secondInsertSubjects.map(subject => this.insert(subject, firstInsertSubjects)));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -459,94 +487,134 @@ export class PersistSubjectExecutor {
|
||||
* If entity has an generated column, then after saving new generated value will be stored to the InsertOperation.
|
||||
* If entity uses class-table-inheritance, then multiple inserts may by performed to save all entities.
|
||||
*/
|
||||
private async insert(subject: Subject): Promise<any> {
|
||||
private async insert(subject: Subject, alreadyInsertedSubjects: Subject[]): Promise<any> {
|
||||
|
||||
let generatedId: any;
|
||||
const parentEntityMetadata = subject.metadata.parentEntityMetadata;
|
||||
const metadata = subject.metadata;
|
||||
const entity = subject.entity;
|
||||
|
||||
if (metadata.table.isClassTableChild) {
|
||||
const parentValuesMap = this.collectColumnsAndValues(parentEntityMetadata, entity, subject.date, undefined, metadata.discriminatorValue);
|
||||
generatedId = await this.queryRunner.insert(parentEntityMetadata.table.name, parentValuesMap, parentEntityMetadata.generatedColumnIfExist);
|
||||
const childValuesMap = this.collectColumnsAndValues(metadata, entity, subject.date, generatedId);
|
||||
// if entity uses class table inheritance then we need to separate entity into sub values that will be inserted into multiple tables
|
||||
if (metadata.table.isClassTableChild) { // todo: with current implementation inheritance of multiple class table children will not work
|
||||
|
||||
// first insert entity values into parent class table
|
||||
const parentValuesMap = this.collectColumnsAndValues(parentEntityMetadata, entity, subject.date, undefined, metadata.discriminatorValue, alreadyInsertedSubjects);
|
||||
subject.newlyGeneratedId = await this.queryRunner.insert(parentEntityMetadata.table.name, parentValuesMap, parentEntityMetadata.generatedColumnIfExist);
|
||||
|
||||
// second insert entity values into child class table
|
||||
const childValuesMap = this.collectColumnsAndValues(metadata, entity, subject.date, subject.newlyGeneratedId, undefined, alreadyInsertedSubjects);
|
||||
const secondGeneratedId = await this.queryRunner.insert(metadata.table.name, childValuesMap, metadata.generatedColumnIfExist);
|
||||
if (!generatedId && secondGeneratedId) generatedId = secondGeneratedId;
|
||||
} else {
|
||||
const valuesMap = this.collectColumnsAndValues(metadata, entity, subject.date);
|
||||
generatedId = await this.queryRunner.insert(metadata.table.name, valuesMap, metadata.generatedColumnIfExist);
|
||||
if (!subject.newlyGeneratedId && secondGeneratedId) subject.newlyGeneratedId = secondGeneratedId;
|
||||
|
||||
} else { // in the case when class table inheritance is not used
|
||||
const valuesMap = this.collectColumnsAndValues(metadata, entity, subject.date, undefined, undefined, alreadyInsertedSubjects);
|
||||
subject.newlyGeneratedId = await this.queryRunner.insert(metadata.table.name, valuesMap, metadata.generatedColumnIfExist);
|
||||
}
|
||||
|
||||
// todo: remove this block once usage of subject.entityId are removed
|
||||
// if there is a generated column and we have a generated id then store it in the insert operation for further use
|
||||
if (parentEntityMetadata && parentEntityMetadata.hasGeneratedColumn && generatedId) {
|
||||
subject.entityId = { [parentEntityMetadata.generatedColumn.propertyName]: generatedId };
|
||||
if (parentEntityMetadata && parentEntityMetadata.hasGeneratedColumn && subject.newlyGeneratedId) {
|
||||
subject.entityId = { [parentEntityMetadata.generatedColumn.propertyName]: subject.newlyGeneratedId };
|
||||
|
||||
} else if (metadata.hasGeneratedColumn && generatedId) {
|
||||
subject.entityId = { [metadata.generatedColumn.propertyName]: generatedId };
|
||||
} else if (metadata.hasGeneratedColumn && subject.newlyGeneratedId) {
|
||||
subject.entityId = { [metadata.generatedColumn.propertyName]: subject.newlyGeneratedId };
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private collectColumnsAndValues(metadata: EntityMetadata, entity: any, date: Date, parentIdColumnValue?: any, discriminatorValue?: any): ObjectLiteral {
|
||||
private collectColumnsAndValues(metadata: EntityMetadata, entity: any, date: Date, parentIdColumnValue: any, discriminatorValue: any, alreadyInsertedSubjects: Subject[]): ObjectLiteral {
|
||||
|
||||
const columns = metadata.columns
|
||||
.filter(column => !column.isVirtual && !column.isParentId && !column.isDiscriminator && column.hasEntityValue(entity));
|
||||
// extract all columns
|
||||
const columns = metadata.columns.filter(column => {
|
||||
return !column.isVirtual && !column.isParentId && !column.isDiscriminator && column.hasEntityValue(entity);
|
||||
});
|
||||
|
||||
const values = columns.map(column => this.connection.driver.preparePersistentValue(column.getEntityValue(entity), column));
|
||||
const relationColumns = metadata.relationsWithJoinColumns
|
||||
.filter(relation => entity.hasOwnProperty(relation.propertyName));
|
||||
|
||||
const relationColumns = metadata.relations
|
||||
.filter(relation => !relation.isManyToMany && relation.isOwning && !!relation.inverseEntityMetadata)
|
||||
.filter(relation => entity.hasOwnProperty(relation.propertyName))
|
||||
.map(relation => relation.name);
|
||||
const columnNames = columns.map(column => column.name);
|
||||
const relationColumnNames = relationColumns.map(relation => relation.name);
|
||||
const allColumnNames = columnNames.concat(relationColumnNames);
|
||||
|
||||
const relationValues = metadata.relations
|
||||
.filter(relation => !relation.isManyToMany && relation.isOwning && !!relation.inverseEntityMetadata)
|
||||
.filter(relation => entity.hasOwnProperty(relation.propertyName))
|
||||
.map(relation => {
|
||||
const value = this.getEntityRelationValue(relation, entity);
|
||||
if (value !== null && value !== undefined) // in the case if relation has null, which can be saved
|
||||
return value[relation.inverseEntityMetadata.firstPrimaryColumn.propertyName]; // todo: it should be get by field set in join column in the relation metadata
|
||||
const columnValues = columns.map(column => {
|
||||
return this.connection.driver.preparePersistentValue(column.getEntityValue(entity), column);
|
||||
});
|
||||
|
||||
// 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
|
||||
if (relationId)
|
||||
return relationId;
|
||||
|
||||
// otherwise try to find relational value from just inserted subjects
|
||||
const alreadyInsertedSubject = alreadyInsertedSubjects.find(insertedSubject => {
|
||||
return insertedSubject.entity === value;
|
||||
});
|
||||
if (alreadyInsertedSubject) {
|
||||
const referencedColumn = relation.joinColumn.referencedColumn;
|
||||
|
||||
const allColumns = columns.map(column => column.name).concat(relationColumns);
|
||||
const allValues = values.concat(relationValues);
|
||||
// if join column references to the primary generated column then seek in the newEntityId of the insertedSubject
|
||||
if (referencedColumn.isGenerated)
|
||||
return alreadyInsertedSubject.newlyGeneratedId;
|
||||
|
||||
// if it references to create or update date columns
|
||||
if (referencedColumn.isCreateDate || referencedColumn.isUpdateDate)
|
||||
return this.connection.driver.preparePersistentValue(alreadyInsertedSubject.date, referencedColumn);
|
||||
|
||||
// if it references to version column
|
||||
if (referencedColumn.isVersion)
|
||||
return this.connection.driver.preparePersistentValue(1, referencedColumn);
|
||||
|
||||
// todo: implement other referenced column types
|
||||
}
|
||||
});
|
||||
|
||||
const allValues = columnValues.concat(relationValues);
|
||||
|
||||
// add special column and value - date of creation
|
||||
if (metadata.hasCreateDateColumn) {
|
||||
allColumns.push(metadata.createDateColumn.name);
|
||||
allColumnNames.push(metadata.createDateColumn.name);
|
||||
allValues.push(this.connection.driver.preparePersistentValue(date, metadata.createDateColumn));
|
||||
}
|
||||
|
||||
// add special column and value - date of updating
|
||||
if (metadata.hasUpdateDateColumn) {
|
||||
allColumns.push(metadata.updateDateColumn.name);
|
||||
allColumnNames.push(metadata.updateDateColumn.name);
|
||||
allValues.push(this.connection.driver.preparePersistentValue(date, metadata.updateDateColumn));
|
||||
}
|
||||
|
||||
// add special column and value - version column
|
||||
if (metadata.hasVersionColumn) {
|
||||
allColumns.push(metadata.versionColumn.name);
|
||||
allColumnNames.push(metadata.versionColumn.name);
|
||||
allValues.push(this.connection.driver.preparePersistentValue(1, metadata.versionColumn));
|
||||
}
|
||||
|
||||
// add special column and value - discriminator value (for tables using table inheritance)
|
||||
if (metadata.hasDiscriminatorColumn) {
|
||||
allColumns.push(metadata.discriminatorColumn.name);
|
||||
allColumnNames.push(metadata.discriminatorColumn.name);
|
||||
allValues.push(this.connection.driver.preparePersistentValue(discriminatorValue || metadata.discriminatorValue, metadata.discriminatorColumn));
|
||||
}
|
||||
|
||||
// add special column and value - tree level and tree parents (for tree-type tables)
|
||||
if (metadata.hasTreeLevelColumn && metadata.hasTreeParentRelation) {
|
||||
const parentEntity = entity[metadata.treeParentRelation.propertyName];
|
||||
const parentLevel = parentEntity ? (parentEntity[metadata.treeLevelColumn.propertyName] || 0) : 0;
|
||||
|
||||
allColumns.push(metadata.treeLevelColumn.name);
|
||||
allColumnNames.push(metadata.treeLevelColumn.name);
|
||||
allValues.push(parentLevel + 1);
|
||||
}
|
||||
|
||||
// add special column and value - parent id column (for tables using table inheritance)
|
||||
if (metadata.parentEntityMetadata && metadata.hasParentIdColumn) {
|
||||
allColumns.push(metadata.parentIdColumn.name); // todo: should be array of primary keys
|
||||
allColumnNames.push(metadata.parentIdColumn.name); // todo: should be array of primary keys
|
||||
allValues.push(parentIdColumnValue || entity[metadata.parentEntityMetadata.firstPrimaryColumn.propertyName]); // todo: should be array of primary keys
|
||||
}
|
||||
|
||||
return this.zipObject(allColumns, allValues);
|
||||
return this.zipObject(allColumnNames, allValues);
|
||||
}
|
||||
|
||||
private insertIntoClosureTable(subject: Subject, updateMap: ObjectLiteral) {
|
||||
|
||||
@ -25,9 +25,20 @@ export class Subject { // todo: move entity with id creation into metadata? // t
|
||||
/**
|
||||
* When subject is newly persisted it may have a generated entity id.
|
||||
* In this case it should be written here.
|
||||
*
|
||||
* @deprecated use newlyGeneratedId instead. Difference between this and newly generated id
|
||||
* is that newly generated id hold value itself, without being in object.
|
||||
* When we have generated value we always have only one primary key thous we dont need object
|
||||
*/
|
||||
entityId: any; // todo: rename to newEntityId
|
||||
|
||||
/**
|
||||
* When subject is newly persisted it may have a generated entity id.
|
||||
* In this case it should be written here.
|
||||
*
|
||||
*/
|
||||
newlyGeneratedId: any;
|
||||
|
||||
/**
|
||||
* Used in newly persisted entities which are tree tables.
|
||||
*/
|
||||
@ -65,7 +76,7 @@ export class Subject { // todo: move entity with id creation into metadata? // t
|
||||
}
|
||||
|
||||
get mustBeInserted() {
|
||||
return !!this.databaseEntity;
|
||||
return !this.databaseEntity;
|
||||
}
|
||||
|
||||
get mustBeUpdated() {
|
||||
@ -78,8 +89,10 @@ export class Subject { // todo: move entity with id creation into metadata? // t
|
||||
|
||||
set databaseEntity(databaseEntity: ObjectLiteral|undefined) {
|
||||
this._databaseEntity = databaseEntity;
|
||||
this.buildDiffColumns();
|
||||
this.buildDiffRelationalColumns();
|
||||
if (this.entity && databaseEntity) {
|
||||
this.buildDiffColumns();
|
||||
this.buildDiffRelationalColumns();
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
@ -86,4 +86,8 @@ export class SubjectCollection extends Array<Subject> {
|
||||
});
|
||||
}
|
||||
|
||||
hasWithEntity(entity: ObjectLiteral): boolean {
|
||||
return !!this.findByEntity(entity);
|
||||
}
|
||||
|
||||
}
|
||||
@ -24,22 +24,26 @@ describe("persistence > cascade operations", () => {
|
||||
const category1 = new Category();
|
||||
category1.name = "Category saved by cascades #1";
|
||||
|
||||
// create photos
|
||||
const photo1 = new Photo();
|
||||
photo1.url = "http://me.com/photo";
|
||||
const photo2 = new Photo();
|
||||
photo2.url = "http://me.com/photo";
|
||||
|
||||
// create post
|
||||
const post1 = new Post();
|
||||
post1.title = "Hello Post #1";
|
||||
|
||||
|
||||
// create photos
|
||||
const photo1 = new Photo();
|
||||
photo1.url = "http://me.com/photo";
|
||||
photo1.post = post1;
|
||||
const photo2 = new Photo();
|
||||
photo2.url = "http://me.com/photo";
|
||||
photo2.post = post1;
|
||||
|
||||
post1.category = category1;
|
||||
post1.category.photos = [photo1, photo2];
|
||||
await connection.entityManager.persist(post1);
|
||||
|
||||
console.log("********************************************************");
|
||||
|
||||
const posts = await connection.entityManager
|
||||
/*const posts = await connection.entityManager
|
||||
.createQueryBuilder(Post, "post")
|
||||
.leftJoinAndSelect("post.category", "category")
|
||||
// .innerJoinAndSelect("post.photos", "photos")
|
||||
@ -52,7 +56,7 @@ describe("persistence > cascade operations", () => {
|
||||
|
||||
// posts[0].category = null; // todo: uncomment to check remove
|
||||
console.log("removing post's category: ", posts[0]);
|
||||
await connection.entityManager.persist(posts[0]);
|
||||
await connection.entityManager.persist(posts[0]);*/
|
||||
|
||||
/* await connection.entityManager.persist([photo1, photo2]);
|
||||
|
||||
|
||||
@ -6,6 +6,7 @@ import {OneToMany} from "../../../../../src/decorator/relations/OneToMany";
|
||||
import {Photo} from "./Photo";
|
||||
import {ManyToMany} from "../../../../../src/decorator/relations/ManyToMany";
|
||||
import {JoinTable} from "../../../../../src/decorator/relations/JoinTable";
|
||||
import {ManyToOne} from "../../../../../src/decorator/relations/ManyToOne";
|
||||
|
||||
@Table()
|
||||
export class Category {
|
||||
@ -31,4 +32,11 @@ export class Category {
|
||||
@JoinTable()
|
||||
photos: Photo[];
|
||||
|
||||
@ManyToOne(type => Photo, {
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true
|
||||
})
|
||||
photo: Photo|null;
|
||||
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
import {Table} from "../../../../../src/decorator/tables/Table";
|
||||
import {PrimaryGeneratedColumn} from "../../../../../src/decorator/columns/PrimaryGeneratedColumn";
|
||||
import {Column} from "../../../../../src/decorator/columns/Column";
|
||||
import {ManyToOne} from "../../../../../src/decorator/relations/ManyToOne";
|
||||
import {Post} from "../entity/Post";
|
||||
|
||||
@Table()
|
||||
export class Photo {
|
||||
@ -11,4 +13,12 @@ export class Photo {
|
||||
@Column()
|
||||
url: string;
|
||||
|
||||
@ManyToOne(type => Post, {
|
||||
cascadeInsert: true,
|
||||
cascadeUpdate: true,
|
||||
cascadeRemove: true,
|
||||
nullable: false
|
||||
})
|
||||
post: Post|null;
|
||||
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user