typeorm: beforeUpdate subscriber entity argument is undefined

Issue type:

[ ] question [X] bug report [ ] feature request [ ] documentation issue

Database system/driver:

[ ] cordova [ ] mongodb [ ] mssql [ ] mysql / mariadb [ ] oracle [ ] postgres [ ] cockroachdb [X] sqlite [ ] sqljs [ ] react-native [ ] expo

TypeORM version:

[ ] latest [ ] @next [X] typeorm@0.2.19 (or put your version here)

Steps to reproduce or a small repository showing the problem:

@EventSubscriber()
export class UpdateModifiedSubscriber implements EntitySubscriberInterface {
    beforeUpdate ( event: UpdateEvent<any> ) {
        if ( ( event.entity as IEntityBase ) != null )
            ( event.entity as IEntityBase ).dateModified = new Date();
    }

    beforeInsert ( event: InsertEvent<any> ) {
        if ( ( event.entity as IEntityBase ) != null )
            ( event.entity as IEntityBase ).dateModified = new Date();
    }
}

export interface IEntityBase {
    id?: string;
    dateModified?: Date;
}

export abstract class EntityBase<T> implements IEntityBase {
    constructor ();
    constructor ( entity: Partial<T> );
    constructor ( entity?: any ) {
        if ( !entity ) return;

        for ( const key in entity )
            this[ key ] = entity[ key ];
    }

    @PrimaryGeneratedColumn( 'uuid' )
    id: string;

    @Column( { type: dateTimeType(), nullable: true } )
    dateModified: Date;
}

@Entity( 'Player' )
export class Player extends EntityBase<IPlayer> implements IPlayer {
    @Column( { type: 'nvarchar', length: 20, nullable: false } )
    firstName: string;

    @Column( { type: 'nvarchar', length: 20, nullable: false } )
    lastName: string;

    @Column( { type: 'integer', nullable: false } )
    jerseyNumber: number;

    @Column( { type: 'integer', nullable: false } )
    weight: number;

    @Column( { type: 'text', nullable: true } )
    stats?: string;

    @Column( { nullable: false } )
    teamId: string;

    /**
     * The players complete name
     *
     * @readonly
     * @memberof Player
     */
    get fullName () { return `${ this.firstName } ${ this.lastName }`; }
}

    async update ( playerId: string, player: Partial<Player> ): Promise<boolean> {
        // test that the a record to update exists in the database
        let existingPlayer: Player;
        try {
            existingPlayer = await this
                .connection
                .getRepository( Player )
                .findOneOrFail( playerId );
        } catch ( error ) {
            throw new BadRequest( error );
        }

        try {
            if ( process.env.DATABASE_TYPE && process.env.DATABASE_TYPE == 'sqlite' ) {
                Object.keys( player ).forEach( prop => {
                    if ( existingPlayer.hasOwnProperty( prop ) )
                        existingPlayer[ prop ] = player[ prop ];
                } );
                // I absolutely do NOT like this. It smells to me. However, I wonder if sql server driver eventually does
                // the same thing here with output clause by select query after the update.
                let updateResult = await this.connection
                    .getRepository( Player )
                    .update( playerId, existingPlayer );
                // await this.connection
                //     .createQueryBuilder()
                //     .update( Player )
                //     .set( player )
                //     .where( "id = :id", { id: playerId } )
                //     .execute();

                let updatedPlayer = await this
                    .connection
                    .getRepository( Player )
                    .findOne( playerId );

                return isMatch( updatedPlayer, player );
            } else {
                // so we are going to use mssql output clause to return updated cols and values then test that instead of 
                // running seperate additional select query to see if all updated cols were success
                let result = await this.connection
                    .createQueryBuilder()
                    .update( Player )
                    .set( player )
                    .where( "id = :id", { id: playerId } )
                    .output( Object.keys( player ) )
                    .execute();

                if ( result.raw && result.raw.length > 0 )
                    return isEqual( result.raw[ 0 ], player );
            }
        } catch ( error ) {
            throw new BadRequest( error );
        }

        return false;
    }

The update method shows a few different things I have tried in order to perform the partial update. I would expect the commented out querybuilder code to work. I understand I can result to using the save repo method but I would like the partial support and optimized primitive query. I tried mssql driver and it works as expected. It is also much more convenient as you can see the code simply need just test the update query results to see if update was a success. However, I must use sqlite and I understand there is no output clause. To test if success update I am selecting the record after the update then testing that. All the beforeInsert subscribers work as expected however the beforeUpdate doesn’t pass the event.entity and therefore I cannot update the dateModified.

2019-10-21 (1)

Is there a different or better way to do this. I absolutely love typeorm and will work around this issue, however, it would be nice to have more concise partial update code to implement expressjs server patch routes. I have not had the time to scour the queryRunner manager or metadata and it is not clear to me what if any I can update in order to pass the new date to the query in the subscriber method.

I apologize for the example code not being complete and I hope there is enough there to see the issue. I tried the beforeUpdate subscriber in a older project where I also used typeorm and the same issue exists. I wondered if the metadata of the inheritance and stuff might of been a issue.

I will try to write some tests for the listeners soon and will update here on that or offer a PR.

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Comments: 22 (7 by maintainers)

Commits related to this issue

Most upvoted comments

So I finally tracked down the lines I needed to change and was able to locally fix the issue 😃

NOTE: I’m NOT sure this will work universally for everyone, but it MAY work as a temporary workaround. I do plan to attempt to do a proper PR today if I get the chance.

NOTE: This is for version 0.2.22 of TypeORM, I’m not sure if this will work or break with other versions!

For those that want to try using patch-package (how I was able to fix it for now), follow these steps:

  • Install patch-package globally (or locally in your project, not sure if it matters but I’ve installed it globally and locally)
  • Create patches/typeorm+0.2.22.patch (in your root project directory. I,e [rootDir]/patches/typeorm+0.2.22.patch`) with the following contents:
diff --git a/node_modules/typeorm/query-builder/UpdateQueryBuilder.js b/node_modules/typeorm/query-builder/UpdateQueryBuilder.js
index 01e7760..9926e1f 100644
--- a/node_modules/typeorm/query-builder/UpdateQueryBuilder.js
+++ b/node_modules/typeorm/query-builder/UpdateQueryBuilder.js
@@ -67,7 +67,8 @@ var UpdateQueryBuilder = /** @class */ (function (_super) {
                     case 3:
                         if (!(this.expressionMap.callListeners === true && this.expressionMap.mainAlias.hasMetadata)) return [3 /*break*/, 5];
                         broadcastResult = new BroadcasterResult_1.BroadcasterResult();
-                        queryRunner.broadcaster.broadcastBeforeUpdateEvent(broadcastResult, this.expressionMap.mainAlias.metadata);
+                        // Fix beforeUpdate subscriber
+                        queryRunner.broadcaster.broadcastBeforeUpdateEvent(broadcastResult, this.expressionMap.mainAlias.metadata, this.expressionMap.valuesSet);
                         if (!(broadcastResult.promises.length > 0)) return [3 /*break*/, 5];
                         return [4 /*yield*/, Promise.all(broadcastResult.promises)];
                     case 4:
@@ -103,7 +104,8 @@ var UpdateQueryBuilder = /** @class */ (function (_super) {
                     case 8:
                         if (!(this.expressionMap.callListeners === true && this.expressionMap.mainAlias.hasMetadata)) return [3 /*break*/, 10];
                         broadcastResult = new BroadcasterResult_1.BroadcasterResult();
-                        queryRunner.broadcaster.broadcastAfterUpdateEvent(broadcastResult, this.expressionMap.mainAlias.metadata);
+                        // Fix afterUpdate subscriber
+                        queryRunner.broadcaster.broadcastAfterUpdateEvent(broadcastResult, this.expressionMap.mainAlias.metadata, this.expressionMap.valuesSet);
                         if (!(broadcastResult.promises.length > 0)) return [3 /*break*/, 10];
                         return [4 /*yield*/, Promise.all(broadcastResult.promises)];
                     case 9:
  • Add the following to the “scripts” section of your package.json:
"postinstall": "patch-package"
  • Run yarn/yarn install or npm i
    • In Yarn, you should see something like the following:
yarn install v1.21.1
[1/4] Resolving packages...
success Already up-to-date.
$ patch-package
patch-package 6.2.0
Applying patches...
typeorm@0.2.22 ✔
  • Run things how you normally do, if you log the entity value from the beforeUpdate or afterUpdate you should see the changes you made in the returned object
    • NOTE: It does not seem to populate the databaseEntity, not sure if there’s a way to do that or not