TypeORM does not make further query for nested object - postgresql

I'm currently using PostgresQL with typeORM, as well as Typegraphql. With the ManyToOne (User has many orderItems) relationship, somehow I cannot query for the nested object relation. I set the logging: true and saw that there is no SELECT query for the User entity. However, I think the query should be automatically generated giving the relation I defined in the Entity according to TypeORM.
In CartItem.ts Entity
#ObjectType()
#Entity()
export class CartItem extends BaseEntity {
#PrimaryGeneratedColumn()
#Field()
id!: number;
#Column()
#Field()
userId: string;
#Field(() => User, { nullable: true })
#ManyToOne((type) => User, (user) => user.cartItems)
user: User;
In User.ts Entity
export class User extends BaseEntity {
#PrimaryGeneratedColumn("uuid")
#Field()
id!: string;
#OneToMany((type) => CartItem, (cartItem) => cartItem.user)
cartItems: CartItem[];
In cartItem.ts Resolver
#Mutation(() => CartItem)
#UseMiddleware(isAuth)
async createCartItem(
#Arg("input") input: CartItemInput,
#Ctx() { req }: MyContext
): Promise<CartItem> {
const newCart = await CartItem.create({
quantity: input.quantity,
userId: req.session.userId,
mealkitId: input.mealkitId,
}).save();
return newCart;
With the following graphql Query, user would return null even though I'm supposed to get the username of the user
query cartItems{
cartItems {
id
quantity
userId
user{
username
}
}
}
Here is the response I received
{
"data": {
"cartItems": [
{
"id": 2,
"quantity": 2,
"userId": "5619ffb2-6ce2-42cf-bd5c-042f2685a045",
"user": null
},
{
"id": 1,
"quantity": 10,
"userId": "5619ffb2-6ce2-42cf-bd5c-042f2685a045",
"user": null
}
]
}
}```

I just ran into this myself and in my query resolver I had to leftJoinAndSelect all of the sub-objects to get it to work. You aren't showing your query resolver, but something like
async cartItems(): Promise<CartItem[]> {
return await getConnection()
.createQueryBuilder(CartItem, 'cartItem')
.leftJoinAndSelect('cartItem.user', 'user', 'cartItem.userId = user.id')
.getMany()
}

Related

TypeORM OneToMany and ManyToOne relations cases 500 error on GET request

After adding OneToMany and manyToOne relations in the entities and doing GET request it shows in postman 500 error.
// Car entity
#Entity({ name: 'car' })
export class Car {
#PrimaryColumn()
car_id: number;
#Column()
name: string;
#Column()
enabled: boolean;
#ManyToOne(() => Person, (person) => person.cars)
person: Person;
}
// Person entity
#Entity({ name: 'person' })
export class Person {
#PrimaryColumn()
person_id: number;
#Column()
name: string;
#Column()
enabled: boolean;
#OneToMany(() => Car, (car) => car.person)
cars: Car[];
}
// Postman
{
"statusCode": 500,
"error": "Internal Server Error",
"message": "",
"path": "/api/v2/cars",
"method": "GET",
}
UPDATE
In try catch it shows this error
ERROR QueryFailedError: column Car.personPersonId does not exist
//Service
GET request to fetch cars by person id
async getByPersonId(
personId: string | number,
relations: string[] = [],
throwsException = false,
): Promise<Car[] | []> {
return await this._carRepository.getByPersonId(
personId,
relations,
throwsException,
);
}
// Repository
async getByPersonId(
personId: string | number,
relations: string[] = [],
throwsException = false,
enabled?: boolean,
): Promise<Car[]> {
const where: IPersonById = {
person_id: personId,
};
if (!isNil(enabled)) {
where.enabled = enabled;
}
return await this.find({
where: {
...where,
},
relations,
})
.then((entities) => {
if (!entities && throwsException) {
return Promise.reject(
new NotFoundException(
'Car is not found by provided person id.',
),
);
}
return Promise.resolve(entities ? this.transformMany(entities) : null);
})
.catch((error) => {
console.log('ERROR', error);
return Promise.reject(error);
});
}
Try this:
on the Car entity:
#ManyToOne(type => Person, person => person.cars, {eager: false})
person: Person
on the Person Entity
#OneToMany(type => Car, car => car.person, {eager: true})
cars: Car[];

Error: Cannot query across many-to-many for property on updating

I am try to update many to may relation.
export class CreateProductDto {
#ApiProperty()
#IsString()
description: string;
#ApiProperty()
#IsString()
name: string;
#ApiProperty({ isArray: true })
#IsNumber({}, { each: true })
#IsArray()
categoryIds: number[];
}
export class UpdateProductDto extends PartialType(CreateProductDto) {}
export class ProductsService {
constructor(
#InjectRepository(Product)
private productRepository: Repository<Product>,
private categoriesService: CategoriesService,
) {}
async update(id: number, updateProductDto: UpdateProductDto) {
let categories: Category[] = undefined;
if (updateProductDto.categoryIds) {
categories = await Promise.all(
updateProductDto.categoryIds.map(
async (id) => await this.categoriesService.findOneOrFail(id),
),
);
delete updateProductDto.categoryIds;
}
await this.productRepository.update(
{ id },
{ ...updateProductDto, categories },
);
return await this.findOneOrFail(id);
}
async findOneOrFail(id: number) {
const product = await this.productRepository.findOne({ id });
if (product) {
return product;
}
throw new BadRequestException(`Product is not present`);
}
}
#Entity()
export class Product extends BaseEntity {
#Column()
description: string;
#Column()
name: string;
#ManyToMany(() => Category, (object) => object.products, {
cascade: true,
eager: true,
})
#JoinTable()
categories: Category[];
}
#Entity()
export class Category extends BaseEntity {
#Column()
name: string;
#ManyToMany(() => Product, (object) => object.categories)
products: Product[];
}
Finally when i try to call ProductsService.update with this payload it
"categoryIds": [ 2 ]
i got an error like this
Error: Cannot query across many-to-many for property categories
Can some please help me to update many to many
In your Category Entity add the relation id of Product and use save method instead of update when you update your entity.
#Entity()
export class Category extends BaseEntity {
#Column()
name: string;
#ManyToMany(() => Product, (object) => object.categories)
products: Product[];
// Add this
#Column()
productId: string;
}
To solve problemes like i use.
async dummyUpdate(objetUpdateDto: ObjectUpdateDto): Promise<TypeReturn> {
const { idObjectToUpdate, colum1ToUpate, colum2ToUpate } = objetUpdateDto;
try {
const objectToUpdate = await this.repositoryOfEntity.findOne(idObjectToUpdate);
idObjectToUpdate.colum1ToUpate = colum1ToUpate;
idObjectToUpdate.colum2ToUpate = colum2ToUpate;
await this.repositoryOfEntity.save(objectToUpdate);
return objectToUpdate,
} catch(err) {
throw new ConflictException("your message" + err);
}
}

Insert data in database depending by relation

I am using typeorm, Nest Js and postgresql database.
I have the next entities:
import {Entity, Column, PrimaryGeneratedColumn, OneToMany, ManyToOne, JoinColumn, OneToOne} from 'typeorm';
import {User} from "./user.entity";
import {MetaImages} from "./meta-images.entity";
#Entity()
export class Cars {
#PrimaryGeneratedColumn({name: "carId"})
carId: number;
#OneToMany(() => CarsColors, c => c.carId, { cascade: true })
carsColors: CarsColors[];
}
/// Colors Entity
#Entity()
export class CarsColors {
#PrimaryGeneratedColumn()
id: number;
#Column({ nullable: true})
color: string;
#ManyToOne(() => Cars, cars => cars.carId)
#JoinColumn({ name: 'carId' })
carId: Cars;
}
The idea of these entities is that i should get something like this:
{
id: 1,
carColors: [
{
id: 1,
carId: 1,
color: "red",
},
{
id: 2,
carId: 1,
color: "blue",
},
...
]
}
So, each car can have multiple colors. I want, depending by carId to add a new color in CarsColors entity.
For this i do:
await getConnection()
.createQueryBuilder()
.where("carId = :carId", {
carId: 1
})
.insert()
.into(MetaImages)
.values([{
color: 'new color',
}])
.execute();
Doing this, the new color is inserted in the db, but without carId, which is null, so the:
.where("carId = :carId", { carId: 1 })
does not work. Question: How to add new color depending by carId?
If you're already using query builder because of efficiency and you know carId, you should insert object directly into CarsColors:
await getConnection()
.createQueryBuilder()
.insert()
.into(CarsColors)
.values([{
carId: 1,
color: 'new color',
}])
.execute();

findUnique query returns null for array fields

I read the Prisma Relations documentation and it fixed my findMany query which is able to return valid data but I'm getting inconsistent results with findUnique.
Schema
model User {
id Int #id #default(autoincrement())
fname String
lname String
email String
password String
vehicles Vehicle[]
}
model Vehicle {
id Int #id #default(autoincrement())
vin String #unique
model String
make String
drivers User[]
}
Typedefs
const typeDefs = gql'
type User {
id: ID!
fname: String
lname: String
email: String
password: String
vehicles: [Vehicle]
}
type Vehicle {
id: ID!
vin: String
model: String
make: String
drivers: [User]
}
type Mutation {
post(id: ID!, fname: String!, lname: String!): User
}
type Query {
users: [User]
user(id: ID!): User
vehicles: [Vehicle]
vehicle(vin: String): Vehicle
}
'
This one works
users: async (_, __, context) => {
return context.prisma.user.findMany({
include: { vehicles: true}
})
},
However, for some reason the findUnique version will not resolve the array field for "vehicles"
This one doesn't work
user: async (_, args, context) => {
const id = +args.id
return context.prisma.user.findUnique({ where: {id} },
include: { vehicles: true}
)
},
This is what it returns
{
"data": {
"user": {
"id": "1",
"fname": "Jess",
"lname": "Potato",
"vehicles": null
}
}
}
I was reading about fragments and trying to find documentation on graphql resolvers but I haven't found anything relevant that can solve this issue.
Any insight would be appreciated! Thanks!
You need to fix the arguments passed to findUnique. Notice the arrangement of the { and }.
Change
return context.prisma.user.findUnique({ where: { id } },
// ^
include: { vehicles: true}
)
to
return context.prisma.user.findUnique({
where: { id },
include: { vehicles: true }
})

sequelize-typescript many-to-many relationship model data with

I am using sequelize with sequelize-typescript library, and am trying to achieve the following relationship:
Team.ts
#Scopes({
withPlayers: {
include: [{model: () => User}]
}
})
#Table
export default class Team extends Model<Team> {
#AllowNull(false)
#Column
name: string;
#BelongsToMany(() => User, () => TeamPlayer)
players: User[];
}
User.ts
#Scopes({
withTeams: {
include: [{model: () => Team, include: [ () => User ]}]
}
})
#Table
export default class User extends Model<User> {
#AllowNull(false)
#Column
firstName: string;
#AllowNull(false)
#Column
lastName: string;
#BelongsToMany(() => Team, () => TeamPlayer)
teams: Team[];
}
TeamPlayer.ts
#DefaultScope({
include: [() => Team, () => User],
attributes: ['number']
})
#Table
export default class TeamPlayer extends Model<TeamPlayer> {
#ForeignKey(() => User)
#Column
userId: number;
#ForeignKey(() => Team)
#Column
teamId: number;
#Unique
#Column
number: number;
}
Now when querying for player, you get the object with the following data:
{
"id": 1,
"name": "Doe's Team",
"players": [
{
"id": 1,
"firstName": "John",
"lastName": "Doe",
"TeamPlayer": {
"userId": 1,
"teamId": 1,
"number": 32
}
}]
}
Now there are couple of things that I cannot get done..
1) I want to rename the TeamPlayer to something like "membership"; but not by changing the name of the class
2) the content of TeamPlayer should not have the id`s, but I want it to contain the data of the team, for example:
{
"firstName": "John",
"lastName": "Doe"
"membership": {
"number": 32
}
In the above classes, I tried to set a scope to the TeamPlayer class to only include number inside the TeamMember inclusion, but no effect.
I used to have the TeamPlayer class have direct memberships to team and player, but that solution added redundant id to the TeamPlayer class, and also did not prevent duplicate memberships in the team. I could indeed manually (= in code) prevent duplicates in these situations, but that does not feel elegant.
I ended up solving this by adding one-to-many relationships from TeamPlayer to User and Team, and also figured out the way to make the teamId + userId pair unique by adding two more fields with #ForeignKey like this:
TeamPlayer.ts
#Table
export default class TeamPlayer extends Model<TeamPlayer> {
#BelongsTo(() => Team)
team: Team;
#ForeignKey(() => Team)
#PrimaryKey
#Column
teamId: number;
#BelongsTo(() => User)
user: User;
#ForeignKey(() => User)
#PrimaryKey
#Column
userId: number;
#Column
number: number;
}
User.ts
#Table
export default class User extends Model<User> {
#AllowNull(false)
#Column
firstName: string;
#AllowNull(false)
#Column
lastName: string;
#HasMany(() => TeamPlayer)
teams: TeamPlayer[];
}
Team.ts
export default class Team extends Model<Team> {
#AllowNull(false)
#Column
name: string;
#HasMany(() => TeamPlayer)
players: TeamPlayer[];
}
This solution allows me to control the included query attributes via scopes, and gives me proper object output.