Open gcherem opened 3 years ago
Thanks!
I have not tested this can of setup, however, I believe you need to define the connectionName within the decorator.
You can use function that returns the connectionName, but it is a must, if it is not default
See https://github.com/odavid/typeorm-transactional-cls-hooked#using-transactional-decorator
Hope it helps...
Thanks @odavid for your reply. Unfortunately I have no idea how to get the connection name within the decorator, since I have no access to the request in that context (I think), The connection name is the tenant name that I retrieve from the JWT in the Authorization header. Any idea? Could you please provide an example or direct me to a sample? Thanks in advance.
Hi @gcherem, I think that once this PR will be merged, you will be able to use the
runInTransaction
function instead of the decorator
I don't think this is related to multi tenant. I am using nestjs and it doesn't work as well.
Gracias @odavid por tu respuesta. Desafortunadamente, no tengo idea de cómo obtener el nombre de la conexión dentro del decorador, ya que no tengo acceso a la solicitud en ese contexto (creo). El nombre de la conexión es el nombre del inquilino que recupero del JWT en el encabezado de Autorización. ¿Alguna idea? ¿Podría darme un ejemplo o dirigirme a una muestra? Gracias por adelantado.
Hello, I am stuck in this same situation. Some help?
I'm using multi tenant too just as op said, I need some help to resolve dynamic connections?
I can't get everything to run within the same transaction.
I have the following code:
export class TestService {
repository: any;
constructor(
@Inject(TENANT_CONNECTION) private connection)
{
this.repository = connection.getRepository(Test)
}`
@Transactional()
async agregar(tableNew: Test): Promise<Number> {
const tableSave = this.repository.create(tableNew)
await this.repository.save(tableSave)
if(tableSave.default){
await this.repository.update(
{default:true,id:Not(tableSave.id)},
{default:false}
)
}
return tableSave.id
}
The following is executed in the database:
query: START TRANSACTION
query: SELECT `Test`.`id` AS `Test_id`,`Test`.`default` AS `Test_default` FROM `test` `Test` WHERE `Test`.`id` IN (?) -- PARAMETERS: [0]
query: START TRANSACTION
query: INSERT INTO `test`(`id`,`default`) VALUES (?,?) -- PARAMETERS: [0,1]
query: COMMIT
query: UPDATE `test` SET `default` = ? WHERE (`default` = ? AND `id` != ?) -- PARAMETERS: [0,true,41]
query: COMMIT
As you can see, 2 transactions are executed, I need it to be only one
it's working for me now here is the code snippet for multi tenant
return runInTransaction(
async () => {
// async operations
},
{ connectionName: this.connection.name },
);
@mohsinamjad
This is not working for me.
I tried to put logic inside runInTransaction
but still, the transaction is happening for each case. I want to have one transaction for all DB transactions.
@pavel-xendit
weird, why the heck it's working for me then, I've tested on simple use-case though
Are you giving connectionName in the second param of runInTransacion?
my package info
"typeorm": "0.2.34", "typeorm-transactional-cls-hooked": "0.1.21",
@mohsinamjad
How can I get connectionName? I passed default
as connectionName.
@odavid @mohsinamjad
Any thoughts on how to make runIntransactions
work?
it is possible to use async-hooks or cls-hooked or some other similar package for creating request scoped context. after that it is quite easy to use it in Transactional callback to get necessary connection
For multi tenant your connection name will be dynamic like in my case I set tenant ID as connection name, and you can't use same connection name is e.g default. You have to use this.connectio.name for it.
I believe that there is no definitive solution to this problem.
In my case, I used middleware to initialize the database connection and store it in a global variable so the user can re-use it, but that caused the transactional issue since addTransactionalDataSource
should be only called when init the app .
So I came out with a solution to sync all tenant data to a JSON file (only the scheme name) and read the file data on app init so I can register all schemes with addTransactionalDataSource
.
Some sample
import { TypeOrmModule } from "@nestjs/typeorm";
import { DataSource } from "typeorm";
import { addTransactionalDataSource } from "typeorm-transactional";
import * as con from "./connections";
export const DatabaseConfig = TypeOrmModule.forRootAsync({
useFactory() {
return {
type: 'postgres',
host: process.env.DB_HOST,
port: +process.env.DB_PORT,
username: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: ["dist/**/*.entity{.ts,.js}"],
migrations: ["dist/src/database/migrations/*{.ts,.js}"],
logging: true,
synchronize: false,
migrationsRun: true,
migrationsTransactionMode: 'all',
useUTC: true
};
},
async dataSourceFactory(options) {
if (!options) {
throw new Error('Invalid options passed');
}
let mainDs: DataSource;
const tenants = ['public'];
for (const schema of tenants) {
const source = new DataSource({
...options,
schema: schema,
} as any);
const ds = addTransactionalDataSource({name: schema, dataSource: source});
con.connectionMap.set(schema, ds);
if(schema == "public") {
mainDs = ds;
}
}
return mainDs;
},
});
Just imagine this part const tenants = ['public'];
are a list you get from a JSON file, con.connectionMap
is a const connectionMap = new Map<string, DataSource>();
.
The downside is that I have to restart the app every time a new tenant arrives, and hope someone jumps in and gives a pro solution.
First of all, thank you for this library. It's a must-have.
I am using nest.js to create a multi tenant application, so the module creates the connection dynamically and injects into the service, and then I get the repository like this:
The
main.ts
is:Although my application works fine, transactions just don't work. Am I missing something?