Allister Moon
Allister Moon

Reputation: 121

Querying associated record fails "Cannot read properties of undefined (reading 'joinColumns')", not loading inverse relation in TypeORM in NestJS app

Well I see a very similar problem here, sadly there was no definitive answer: TypeORM & NestJS: TypeError: Cannot read properties of undefined (reading 'joinColumns')

Summary

I have an existing DB and I wanted to try making this app as a test to interact with it. I went with NestJS, and the docs mention TypeORM being the tightest integration so I'm trying that. I can query the DB just fine with entities, but trying to include an association is failing. I'm stuck on pretty much the first hurdle, alas. I'll switch if I have to, but I don't want to just give up on fixing this. There must be one little thing missing or incorrect I'm sure.

The exact error:

TypeError: Cannot read properties of undefined (reading 'joinColumns')
    at <anonymous> (C:\...REDACTED!\node_modules\typeorm\query-builder\src\query-builder\SelectQueryBuilder.ts:2319:39)
    at Array.map (<anonymous>)
    at SelectQueryBuilder.createJoinExpression (C:\...REDACTED!\node_modules\typeorm\query-builder\src\query-builder\SelectQueryBuilder.ts:2257:57)
    at SelectQueryBuilder.getQuery (C:\...REDACTED!\node_modules\typeorm\query-builder\src\query-builder\SelectQueryBuilder.ts:87:21)
    at SelectQueryBuilder.executeEntitiesAndRawResults (C:\...REDACTED!\node_modules\typeorm\query-builder\src\query-builder\SelectQueryBuilder.ts:3477:26)
    at SelectQueryBuilder.getRawAndEntities (C:\...REDACTED!\node_modules\typeorm\query-builder\src\query-builder\SelectQueryBuilder.ts:1670:40)
    at SelectQueryBuilder.getOne (C:\...REDACTED!\node_modules\typeorm\query-builder\src\query-builder\SelectQueryBuilder.ts:1697:36)
    at EntityManager.findOne (C:\...REDACTED!\node_modules\typeorm\entity-manager\src\entity-manager\EntityManager.ts:1215:14)
    at Repository.findOne (C:\...REDACTED!\node_modules\typeorm\repository\src\repository\Repository.ts:597:29)
    at AppController.getClients (C:\...REDACTED!\src\app.controller.ts:24:46)

It seems the inverse relation isn't loading (last code block in the code detail section).

Code Details

Here are my two classes with unrelated properties stripped out:

@Entity({ name: 'Agent', schema: 'dbo' })
export class EntityAgent {
  @PrimaryGeneratedColumn({ name: 'Id' })
  id: number

  @Column({ name: 'ClientId' })
  id_client: number

  ...

  @ManyToOne(() => EntityClient, (relation) => relation.id)
  client: Relation<EntityClient>
}

@Entity({ name: 'Client', schema: 'dbo' })
export class EntityClient {
  @PrimaryGeneratedColumn({ name: 'Id' })
  id: number

  ...

  @OneToMany(() => EntityAgent, (relation) => relation.id_client)
  agents: Relation<EntityAgent[]>
}

I'm attempting to get the clients from the DB like this:

// tried with repository pattern
await this.repositoryClient.findOne({
  where: { id: 4 },
  relations: { agents: true },
})
// and using the datasource manager
await this.dataSource.manager.findOne(EntityClient, {
  where: { id: 4 },
  relations: { agents: true },
})

And here's how I'm initializing the app

@Module({
  imports: [
    ...
    TypeOrmModule.forRootAsync({
      // name: 'agent',
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (config: ConfigService) => ({
        type: 'mssql',
        host: config.get('database.host', { infer: true }),
        port: config.get('database.port', { infer: true }),
        database: config.get('database.db_agent_name', { infer: true }),
        username: config.get('database.db_agent_user', { infer: true }),
        password: config.get('database.db_agent_pass', { infer: true }),
        logging: config.get('server.environment', { infer: true }) === 'local',
        entities: [
          EntityAgent, EntityClient,
        ],
        options: {
          encrypt: true,
          trustServerCertificate: config.get('server.environment', { infer: true }) === 'local',
        },
      }),
    }),
    TypeOrmModule.forFeature([
      EntityClient,
    ]),
    AdminModule,
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

And with debugger, I've determined it's failing at this code in the typeorm library

createJoinExpression() {
  const joins = this.expressionMap.joinAttributes.map((joinAttr) => {
    const relation = joinAttr.relation;
    const destinationTableName = joinAttr.tablePath;
    const destinationTableAlias = joinAttr.alias.name;
    ...
    else if (relation.isOneToMany || relation.isOneToOneNotOwner) {
      // JOIN `post` `post` ON `post`.`categoryId` = `category`.`id`
      const condition = relation.inverseRelation.joinColumns.map((joinColumn) => { // <-- RIGHT HERE, relation.inverseRelation is undefined
      ...

Troubleshooting Details

Annoyingly, I don't really know where to troubleshoot from here. Here's a sample of what I've tried:

Upvotes: 0

Views: 201

Answers (1)

Allister Moon
Allister Moon

Reputation: 121

Not the most satisfying answer, but my solution was to stop trying to use TypeORM. Apparently plenty of people have had issues with this.

I used Sequelize instead, worked instantly.

I had fun digging into TypeORM, I was following the initialization of everything line by line with the debugger, and there's a part where it compares the property paths of the models on both sides. It would not respect the relation column name when matching the model properties unless it was the associated property in the model (EntityAgent.client or EntityClient.agents), but that would just make it fail to query the DB because it would use that name to try querying the DB. I dunno, but I had to stop trying so I could actually get stuff done.

Upvotes: -1

Related Questions