createQueryBuilder with getOne doesn't return #JoinColumns inside table Typeorm - orm

I have a project written by nestjs and typeorm. In my project I have tables chat and user so I want to get ManyToOne relationships. Here is the chat table
export class Chat extends BaseEntity {
#PrimaryGeneratedColumn()
id: number;
#ManyToOne(() => User, (user) => user.chatsCreater, { nullable: false })
#JoinColumn({ name: 'creatorId' })
creator: User;
#ManyToOne(() => User, (user) => user.chatsCompanion, { nullable: false })
#JoinColumn({ name: 'companionId' })
companion: User;
}
and chunk from user table
...
#OneToMany(() => Chat, (chat) => chat.creator)
chatsCreater: Chat[];
#OneToMany(() => Chat, (chat) => chat.companion)
chatsCompanion: Chat[];
...
When I query data from chat I expect to get the hole table {id, companionId, creatorI} not only value of chat.id. Here is my query
.createQueryBuilder('chat')
.where('chat.creatorId = :creatorId AND chat.companionId = :companionId', { creatorId, companionId })
.getOne()
and the result {id: 1}
So what I want is to get values of companionId and creatorId too when I query from chat.
If I change getOne() to getRaw() I get the desired output. But in case of more complex queries (with multiple joins) it becomes a mess with getRaw so is there a way to get all columns using getOne ?

I was able to do it by using leftJoinAndSelect
.createQueryBuilder('chat')
.leftJoinAndSelect('chat.creator', 'creator')
.leftJoinAndSelect('chat.companion', 'companion')
.where('chat.creatorId = :creatorId AND chat.companionId = :companionId', { creatorId, companionId })
.getOne();
In case if hole table is not needed it's also possible to use leftJoin without select and later add to query addSelect(['creator.id','companion.id'])

Related

Mikro-Orm - ManyToMany Relationship how can I delete reference in PivotTable?

I'm using NestJS with mikro-Orm and have a weird behaviour on all of my manyToMany relations.
#ObjectType()
#Entity()
export class Realty {
#Field(() => ID)
#PrimaryKey({ columnType: "uuid" })
id: string = v4();
#Field(() => [Contact])
#ManyToMany(() => Contact, (contact) => contact.realties)
contacts: Collection<Contact>;
}
#ObjectType()
#Entity()
export class Contact {
#Field(() => ID)
#PrimaryKey({ columnType: "uuid" })
id: string = v4();
#Field(() => [Realty])
#ManyToMany(() => Realty, (realty) => realty.contacts, { owner: true })
realties: Collection<Realty>;
}
When I want to delete a realtyReference from a contact, that works fine and the row from the Contact_Realty PivotTable gets removed. But when I try to delete a contactReference from a realty, nothing happens. Does that only work on the owning side?
ContactsService (works):
async update(updateContactInput: UpdateContactInput) {
const { id, realtyIds } = updateContactInput;
const contact = await this.findOneOrFail(id);
const updated = this.contactsRepository.assign(contact, {
realties: await this.realtiesService.find(realtyIds),
});
await this.contactsRepository.persistAndFlush(updated);
return updated;
}
RealtiesService (returns correct updated entity but doesnt remove row in PivotTable):
async update(updateRealtyGeneralInput: UpdateRealtyGeneralInput) {
const { id, contactIds } = updateRealtyGeneralInput;
const realty = await this.realtiesService.findOneOrFail(id);
const updated = this.realtiesRepository.assign(realty, {
contacts: await this.contactsService.find(contactIds),
});
await this.realtiesRepository.persistAndFlush(updated);
return updated;
}
Both return the correct updated entity but only the ContactsService actually removes the row in the pivotTable.
Would really appreciate some help, thanks alot!
I want to remove one or more contacts from a realty and cannot get it to work. Am I doing something wrong?
You always need to have the owning side of your M:N collection initialized/populated, which you apparently don't in the second example. In your case it is Contact.realties, so if you want to manipulate this collection from the inverse side, all the entities you add/remove from the inverse need to have the owning side populated. Only owning side is what is taken into account when computing changesets. I will need to revisit this a bit, we might be able to improve on this thanks to the recent changes like the reference updates added in v5.5.
Also, there is some misunderstanding in your code. assign mutates the parameter, it does not return "modified entity", it mutates the one you pass in the first argument. If that entity is already managed (as in your case), there is no point in re-persisting it again, just flush.
async update(updateRealtyGeneralInput: UpdateRealtyGeneralInput) {
const { id, contactIds } = updateRealtyGeneralInput;
const realty = await this.em.findOneOrFail(Realty, id);
this.realtiesRepository.assign(realty, {
contacts: await this.em.find(Contact, contactIds, { populate: ['realties'] }),
});
await this.em.flush(updated);
return realty;
}

About how to use many to one in TypeORM

For the table which has Foreign key, I want to assign ManyToOne's decorator.
I know #ManyToOne(() => User, user => user.photos) is just table relation,
What its argument () => User, user => user.photos means?
And please tell me user: User's property and value mean.
import {Entity, PrimaryGeneratedColumn, Column, ManyToOne} from "typeorm";
import {User} from "./User";
#Entity()
export class Photo {
#PrimaryGeneratedColumn()
id: number;
#Column()
url: string;
#ManyToOne(() => User, user => user.photos)
user: User;
}
import {Entity, PrimaryGeneratedColumn, Column, OneToMany} from "typeorm";
import {Photo} from "./Photo";
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#OneToMany(() => Photo, photo => photo.user)
photos: Photo[];
}
it just sets the inverse relationship so if you want you could query the other way back. For example:
await this.photoRepository.find({
loadEagerRelations: true,
relations: ['user'],
})
and you would have something like :
[
{
"id": 1,
"url": "https://twetew",
"user": {
"id": 1,
...
...
}
}
]
TypeORM needs this to understand the relation and create the correct reference. In your photos database table it will create a user_id column. User won't have a photo_id.
So:
#ManyToOne(() => User, user => user.photos)
user: User;
creates a user_id column on photos database table and lets you query the other way back.

TypeORM - Getting objects of provided id, which is one relation away

I want to get objects from table providing id, which is in relation with table, which is in another relation. It looks like this:
Hand is in relation manyToOne with Action (hand can have only one action),
Action is in relation manyToOne with Situation (action can have only one situation)
I'm trying to make GET request for hands in which I'm providing situationId.
Simplified entities:
#Entity()
export class Hand {
#PrimaryGeneratedColumn()
hand_id: number;
#Column()
hand: string;
#ManyToOne(type => Action, action => action.simplifiedhands, { eager: true, onDelete: 'CASCADE', onUpdate: 'CASCADE' })
action: Action;
}
#Entity()
export class Action {
#PrimaryColumn()
action_id: number;
#ManyToOne(type => Situation, situation => situation.actions, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
#JoinColumn({name: 'situation'})
situation: Situation;
#OneToMany(type => Hand, hand => hand.action)
hands: Hand[];
#OneToMany(type => Hand, hand => hand.action)
hands: Hand[];
}
#Entity()
export class Situation {
#PrimaryColumn()
situation_id: number;
#ManyToOne(type => Strategy, strategy => strategy.situations, { onDelete: 'CASCADE', onUpdate: 'CASCADE' })
strategy: Strategy;
#OneToMany(type => Action, action => action.situation)
actions: Action[];
}
What approaches didn't work for me so far (just example variants):
return await this.handsRepository.find({
relations: ["action", "action.situation"],
where: {
"situation": id
}
});
and
return await this.handsRepository.find({
join: {
alias: "hands",
leftJoinAndSelect: {
"action": "hand.action",
"situation": "action.situation"
}
},
where: {
"situation": id
}
});
Generally both 'works' but provide all the records, like there were no where condition.
The keys in the object you assign to where should be members of the entity of the repository, in your case Hand, since situation is a member of action it's not working. I'm surprised you didn't mention any errors.
You can do one of the following (example for postgres)
Using query builder:
return await this.handsRepository.createQueryBuilder(Hand, 'hand')
.leftJoin('hand.action', 'action')
.leftJoin('action.situation', 'situation')
.where('situation.id = :id', { id })
.getMany();
Or, you can try the following (success is not guaranteed):
return await this.handsRepository.find({
relations: ["action", "action.situation"],
where: {
action: {
situation: { id }
}
}
});

many-to-many relationships in sequelize can't use include?

So I have a project in which a User can join a Weekly Ladder so I made a belongsToMany relationship between both fields through a third model called UserLadder where I also added an extra field that keeps track of the user points at this ladder.
Basically now in my express app I need a route to shop users and their points by tournament so I wrote this:
const leaderboard = await UserLadder.findAll({
where: {
weeklyLadderId: req.params.id,
},
limit: req.params.playersNumber,
order: [['userPoints', 'DESC']],
});
res.json(leaderboard);
and it worked in the sense of it did show me everyone in this tournament and ordered them according to their points but problem was that the user showed just as a userId field and I wanted to eager load that using Include but it says that there's no relation between the user model and the userladder model.
the temporary solution I did was:
const ladder = await WeeklyLadder.findByPk(req.params.id);
const users = await ladder.getUsers({
attributes: ['displayName'],
});
res.json(users);
which I got the tournament by Id and then got there users of the tournament but this is a 2 step process and I know there's a way to do it through the third UserLadder model I just cant figure it out
any ideas?
accosiations:
const User = require('./models/User');
const WeeklyLadder = require('./models/WeeklyLadder');
const Game = require('./models/Game');
const UserLadder = require('./models/UserLadder');
User.belongsToMany(WeeklyLadder, {
through: UserLadder,
});
WeeklyLadder.belongsToMany(User, {
through: UserLadder,
});
Game.hasMany(WeeklyLadder);
WeeklyLadder.belongsTo(Game);
You can execute with just one request :
db.User.findOne({
include: [
{
model: db.WeeklyLadder,
as: "WeeklyLadder",
where: {
title: "tournament 1", // where on WeeklyLadder
},
},
],
})
On your models :
User.associate = (models) => {
models.User.belongsToMany(models.WeeklyLadder, {
as: "WeeklyLadder",
through: "UserWeeklyLadder",
foreignKey: "fk_user_id",
otherKey: "fk_weekly_ladder_id",
})
}
...
WeeklyLadder.associate = (models) => {
models.WeeklyLadder.belongsToMany(models.User, {
as: "User",
through: "UserWeeklyLadder",
foreignKey: "fk_weekly_ladder_id",
otherKey: "fk_user_id",
})
}

GraphQL & Sequelize: Users and followers/following

I'm attempting to set up my User GraphQL model to have followers and following attributes to query on. However I'm having trouble setting up the relationship in Sequelize. I'm trying to use a Follower model as a Join Table and setup a BelongsToMany association, but haven't been able to get it working. Can anyone suggest what to do or point out what I'm doing wrong?
I've come up with a temporary solution by manually querying, which you can see in my User.model.ts, but I believe there is a better way to do it using proper configuration.
I'm using typescript wrappers around GraphQL and Sequelize, TypeGraphQL and sequelize-typescript respectively, as well as PostgreSQL.
User.model.ts
// VENDOR
import { ObjectType, Field, ID } from 'type-graphql';
import { Model, Table, Column, PrimaryKey, Unique, IsUUID, HasMany, DefaultScope, AllowNull, DataType, BelongsToMany } from 'sequelize-typescript';
// APP
import Post from '../post/post.types';
import Follower from '../follower/follower.types';
/** User model for GraphQL & Database */
#Table({ timestamps: false, tableName: 'users' }) // tell sequelize to treat class as table model
#DefaultScope(() => ({ include: [{ model: Post.scope(), as: 'posts' }] })) // tell sequelize to include posts in its default queries
#ObjectType() // tell GraphQL to treat class as GraphQL model
export default class User extends Model<User>{
#PrimaryKey
#Unique
#AllowNull(false)
#IsUUID(4)
#Column(DataType.UUID)
#Field(() => ID)
id: string;
#Unique
#AllowNull(false)
#Column
#Field()
ci_username: string;
#AllowNull(false)
#Column
#Field()
username: string;
#AllowNull(false)
#Column
#Field()
first_name: string;
#Column
#Field()
last_name: string;
#Column
#Field({ nullable: true })
profile_picture?: string;
// #BelongsToMany(() => User, { otherKey: 'user_id', as: 'followers', through: () => Follower })
// #Field(() => [User])
// followers: User[];
// MY TEMPORARY SOLUTION USING MANUAL QUERYING
#Field(() => [User])
get followers(): Promise<User[]> {
return Follower.findAll({ where: { user_id: this.id } })
.then(records => records.map(record => record.follower_id))
.then((follower_ids: string[]) => {
return User.findAll({ where: { id: follower_ids }});
})
}
// DOES NOT WORK, BUT I BELIEVE COULD POTENTIALLY LEAD TO BETTER SOLUTION
#BelongsToMany(() => User, { otherKey: 'follower_id', as: 'following', through: () => Follower })
#Field(() => [User])
following: User[];
#HasMany(() => Post)
#Field(() => [Post])
posts: Post[];
}
Follower.model.ts
// VENDOR
import { Model, Table, Column, PrimaryKey, Unique, IsUUID, AllowNull, DataType, Index, ForeignKey, AutoIncrement } from 'sequelize-typescript';
// APP
import User from '../user/user.types';
/** Follower model for Database */
#Table({ timestamps: false, tableName: 'followers' }) // tell sequelize to treat class as table model
export default class Follower extends Model<Follower>{
#PrimaryKey
#AutoIncrement
#Unique
#AllowNull(false)
#Column
id: number;
#AllowNull(false)
#IsUUID(4)
#Index
#ForeignKey(() => User)
#Column(DataType.UUID)
user_id: string;
#AllowNull(false)
#IsUUID(4)
#Index
#ForeignKey(() => User)
#Column(DataType.UUID)
follower_id: string;
}
GraphQL Query
{
users: allUsers {
id
username
first_name
last_name
following {
username
id
}
}
}
GraphQL Response / Error
{
"errors": [
{
"message": "Cannot return null for non-nullable field User.following.",
"locations": [
{
"line": 7,
"column": 5
}
],
"path": [
"users",
0,
"following"
],
"extensions": {
"code": "INTERNAL_SERVER_ERROR",
"exception": {
"stacktrace": [
"Error: Cannot return null for non-nullable field User.following.",
" at completeValue (/Users/jsainz237/Projects/trueview/trueview-api/node_modules/graphql/execution/execute.js:560:13)",
" at /Users/jsainz237/Projects/trueview/trueview-api/node_modules/graphql/execution/execute.js:492:16"
]
}
}
}
],
"data": null
}
Any help is appreciated.
You need to write a #FieldResolver manually that will resolve the relation and return proper data.
Another solution is to rely on ORM capabilities and lazy relations - when the returned base entity contains a promise as a field, so when .then() is called, it automatically fetches the relation for the database.