0

我有一个使用nestjswith的项目typeorm

有一个database.config.ts

const entitiesPath = join(__dirname, '..', 'entities', '*.entity.{ts,js}');
const migrationsPath = join(__dirname, '..', 'migrations', '*.*');
const subscribersPath = join(__dirname, '..', 'subscribers', '*.subscriber.{ts,js}');

export const connectionConfig = {
    name: 'default',
    type: 'postgres',
    host: process.env.DB_HOST,
    port: process.env.DB_PORT,
    database: process.env.DB_DATABASE,
    schema: process.env.DB_SCHEMA,
    username: process.env.DB_USERNAME,
    password: process.env.DB_PASSWORD,
};

export const databaseConfig = registerAs('database', () => ({
    entities: [entitiesPath],
    migrations: [migrationsPath],
    subscribers: [subscribersPath],
    ...connectionConfig,
}));

database.module.ts

const connectionProvider = {
    provide: DatabaseProvider.Connection,
    useFactory: async (configService: ConfigService): Promise<Connection> => {
        const databaseConfig = configService.get('database');

        return await createConnection({
            ...databaseConfig,
        });
    },
    inject: [ConfigService],
};

@Module({
    imports: [
        ConfigModule.forRoot({
            load: [databaseConfig],
        }),
    ],
    providers: [connectionProvider, TestRepository],
    exports: [connectionProvider, TestRepository],
})
export class DatabaseModule {}

TestRepository是一个类,它扩展了BaseRepository一个工作单元,几乎就像这里描述的那样。

连接是这样注入的:

    constructor(@Inject(DatabaseProvider.Connection) private readonly conn: Connection) {
        super(conn);
    }

在基础存储库中,我QueryRunner在构造函数中创建:

    constructor(connection: Connection) {
        super();

        this.connection = connection;
        this.unitOfWorkQueryRunner = this.connection.createQueryRunner();
    }

现在,我想为工作单元编写一些集成测试,我得到了连接,TestRepository就像这样:

describe('test.repository.ts', () => {
    let app: INestApplication;
    let connection: Connection;
    let testRepository: TestRepository;

    beforeAll(async () => {
        app = await NestFactory.create(DatabaseModule);
        connection = app.get<Connection>(DatabaseProvider.Connection);
    });

    beforeEach(async () => {
        await connection.runMigrations();

        testRepository = connection.getCustomRepository(TestRepository);
    });

    [...]

似乎testRepositoryandconnection被正确初始化了,this.unitOfWorkQueryRunner = this.connection.createQueryRunner()我得到错误createQueryRunner不是函数。

我究竟做错了什么?

编辑 connection::

<ref *1> Connection {
      migrations: [
        CreateBrandTable1628717011030 {
          name: 'CreateBrandTable1628717011030'
        }
      ],
      subscribers: [ GlobalSubscriber {} ],
      entityMetadatas: [
        EntityMetadata {
          childEntityMetadatas: [],
          inheritanceTree: [Array],
          tableType: 'regular',
          withoutRowid: false,
          synchronize: true,
          hasNonNullableRelations: false,
          isJunction: false,
          isAlwaysUsingConstructor: true,
          isClosureJunction: false,
          hasMultiplePrimaryKeys: false,
          hasUUIDGeneratedColumns: true,
          ownColumns: [Array],
          columns: [Array],
          ancestorColumns: [],
          descendantColumns: [],
          nonVirtualColumns: [Array],
          ownerColumns: [],
          inverseColumns: [],
          generatedColumns: [Array],
          primaryColumns: [Array],
          ownRelations: [],
          relations: [],
          eagerRelations: [],
          lazyRelations: [],
          oneToOneRelations: [],
          ownerOneToOneRelations: [],
          oneToManyRelations: [],
          manyToOneRelations: [],
          manyToManyRelations: [],
          ownerManyToManyRelations: [],
          relationsWithJoinColumns: [],
          relationIds: [],
          relationCounts: [],
          foreignKeys: [],
          embeddeds: [],
          allEmbeddeds: [],
          ownIndices: [],
          indices: [],
          uniques: [],
          ownUniques: [],
          checks: [],
          exclusions: [],
          ownListeners: [],
          listeners: [],
          afterLoadListeners: [],
          beforeInsertListeners: [],
          afterInsertListeners: [],
          beforeUpdateListeners: [],
          afterUpdateListeners: [],
          beforeRemoveListeners: [],
          afterRemoveListeners: [],
          connection: [Circular *1],
          inheritancePattern: undefined,
          treeType: undefined,
          treeOptions: undefined,
          parentClosureEntityMetadata: undefined,
          tableMetadataArgs: [Object],
          target: [class Brand extends CustomBaseEntity],
          expression: undefined,
          engine: undefined,
          database: undefined,
          schema: 'sh',
          givenTableName: undefined,
          targetName: 'Brand',
          tableNameWithoutPrefix: 'brand',
          tableName: 'brand',
          name: 'Brand',
          tablePath: 'sh.brand',
          orderBy: undefined,
          discriminatorValue: 'Brand',
          treeParentRelation: undefined,
          treeChildrenRelation: undefined,
          createDateColumn: [ColumnMetadata],
          updateDateColumn: undefined,
          deleteDateColumn: undefined,
          versionColumn: undefined,
          discriminatorColumn: undefined,
          treeLevelColumn: undefined,
          nestedSetLeftColumn: undefined,
          nestedSetRightColumn: undefined,
          materializedPathColumn: undefined,
          objectIdColumn: undefined,
          propertiesMap: [Object]
        }
      ],
      name: 'default',
      options: {
        entities: [
          ...
        ],
        migrations: [
          ...
        ],
        subscribers: [
          ...
        ],
        name: 'default',
        type: 'postgres',
        host: 'localhost',
        port: '5432',
        database: 'database_name',
        schema: 'sh',
        username: 'sh_user',
        password: 'password'
      },
      logger: AdvancedConsoleLogger { options: undefined },
      driver: PostgresDriver {
        slaves: [],
        connectedQueryRunners: [],
        isReplicated: false,
        treeSupport: true,
        supportedDataTypes: [
          'int',
          'int2',
          'int4',
          'int8',
          'smallint',
          'integer',
          'bigint',
          'decimal',
          'numeric',
          'real',
          'float',
          'float4',
          'float8',
          'double precision',
          'money',
          'character varying',
          'varchar',
          'character',
          'char',
          'text',
          'citext',
          'hstore',
          'bytea',
          'bit',
          'varbit',
          'bit varying',
          'timetz',
          'timestamptz',
          'timestamp',
          'timestamp without time zone',
          'timestamp with time zone',
          'date',
          'time',
          'time without time zone',
          'time with time zone',
          'interval',
          'bool',
          'boolean',
          'enum',
          'point',
          'line',
          'lseg',
          'box',
          'path',
          'polygon',
          'circle',
          'cidr',
          'inet',
          'macaddr',
          'tsvector',
          'tsquery',
          'uuid',
          'xml',
          'json',
          'jsonb',
          'int4range',
          'int8range',
          'numrange',
          'tsrange',
          'tstzrange',
          'daterange',
          'geometry',
          'geography',
          'cube',
          'ltree'
        ],
        spatialTypes: [ 'geometry', 'geography' ],
        withLengthColumnTypes: [
          'character varying',
          'varchar',
          'character',
          'char',
          'bit',
          'varbit',
          'bit varying'
        ],
        withPrecisionColumnTypes: [
          'numeric',
          'decimal',
          'interval',
          'time without time zone',
          'time with time zone',
          'timestamp without time zone',
          'timestamp with time zone'
        ],
        withScaleColumnTypes: [ 'numeric', 'decimal' ],
        mappedDataTypes: {
          createDate: 'timestamp',
          createDateDefault: 'now()',
          updateDate: 'timestamp',
          updateDateDefault: 'now()',
          deleteDate: 'timestamp',
          deleteDateNullable: true,
          version: 'int4',
          treeLevel: 'int4',
          migrationId: 'int4',
          migrationName: 'varchar',
          migrationTimestamp: 'int8',
          cacheId: 'int4',
          cacheIdentifier: 'varchar',
          cacheTime: 'int8',
          cacheDuration: 'int4',
          cacheQuery: 'text',
          cacheResult: 'text',
          metadataType: 'varchar',
          metadataDatabase: 'varchar',
          metadataSchema: 'varchar',
          metadataTable: 'varchar',
          metadataName: 'varchar',
          metadataValue: 'text'
        },
        dataTypeDefaults: {
          character: [Object],
          bit: [Object],
          interval: [Object],
          'time without time zone': [Object],
          'time with time zone': [Object],
          'timestamp without time zone': [Object],
          'timestamp with time zone': [Object]
        },
        maxAliasLength: 63,
        connection: [Circular *1],
        options: {
          entities: [Array],
          migrations: [Array],
          subscribers: [Array],
          name: 'default',
          type: 'postgres',
          host: 'localhost',
          port: '5432',
          database: 'database_name',
          schema: 'sh',
          username: 'sh_user',
          password: 'password'
        },
        postgres: PG {
          defaults: [Object],
          Client: [Function],
          Query: [class Query extends EventEmitter],
          Pool: [class BoundPool extends Pool],
          _pools: [],
          Connection: [class Connection extends EventEmitter],
          types: [Object],
          DatabaseError: [class DatabaseError extends Error]
        },
        database: 'competitor_monitoring_tool_test',
        master: BoundPool {
          _events: [Object: null prototype],
          _eventsCount: 1,
          _maxListeners: undefined,
          options: [Object],
          log: [Function (anonymous)],
          Client: [Function],
          Promise: [Function: Promise],
          _clients: [Array],
          _idle: [Array],
          _pendingQueue: [],
          _endCallback: undefined,
          ending: false,
          ended: false,
          [Symbol(kCapture)]: false
        }
      },
      manager: EntityManager {
        repositories: [],
        plainObjectToEntityTransformer: PlainObjectToNewEntityTransformer {},
        connection: [Circular *1]
      },
      namingStrategy: DefaultNamingStrategy {
        nestedSetColumnNames: { left: 'nsleft', right: 'nsright' },
        materializedPathColumnName: 'mpath'
      },
      queryResultCache: undefined,
      relationLoader: RelationLoader { connection: [Circular *1] },
      isConnected: true
    }

回购以重现类似问题(可能是相同的配置问题):https ://github.com/y-chen/nestjs-typeorm-undefined-issue

4

1 回答 1

0

没有必要将连接注入到从 TypeORM扩展Repository/的存储库,因为您可以使用以下方式访问它:AbstractRepositorythis.manager.connection

this.manager.connection.createQueryRunner();

但是,您无法访问构造函数中的连接,因为管理器(以及连接)在它被实例化后被注入到存储库中。在您的情况下,您只需要QueryRunnerafter call BaseRepository#begin(),因此您可以将存储库更新为以下内容:

export default abstract class BaseRepository<T extends CustomBaseEntity> extends Repository<T> {
    private unitOfWorkQueryRunner?: QueryRunner;

    setTransactionManager(): void {
        this.unitOfWorkQueryRunner = this.manager.connection.createQueryRunner();
    }

    async begin(): Promise<void> {
        this.setTransactionManager();
        await this.unitOfWorkQueryRunner.startTransaction();
    }

    // ...


    async write(...entities: T[]): Promise<T | T[]> {
        return await this.unitOfWorkQueryRunner.manager.save(entities);
    }

    // ...
}

编辑:查看您链接的存储库后,您似乎没有使用@nestjs/typeorm包。您应该尝试一下,因为它大大简化了设置。有关更多信息,请查看NestJS 文档中的此页面。但作为总结:

将此添加到@Module()装饰器中AppModuleTypeOrmModule.forRootAsync()用于访问ConfigService您的仓库中的类似内容):

@Module({
    imports: [
        TypeOrmModule.forRootAsync({
            imports: [ConfigModule],
            useFactory: (configService: ConfigService) => ({
                type: 'sqlite',
                database: './data/test.db',
                entities: [__dirname + '/**/*.entity{.ts,.js}'],
            }),
            inject: [ConfigService],
        }),
        BrandModule, // see below
    ],
    // providers, exports, etc
})
export class AppModule {}

将此添加到@Module()要使用存储库的装饰器中(您可以将所有存储库添加到AppModule,但最好将其拆分为多个Fe​​ature Modules):

@Module({
    imports: [
        TypeOrmModule.forFeature([Brand]), // Brand is an entity
    ],
    providers: [BrandService],
    exports: [BrandService],
})
export class BrandModule {}

在这样的服务中注入存储库:

@Injectable()
export class BrandService {
    constructor(
        @InjectRepository(Brand)
        private brandRepository: Repository<Brand>,
    ) {}
}
于 2021-08-15T03:51:03.143 回答