I have a rather simple entity model where a user has basic information. If a user also has a provider function a OneToOne relation will be created to the provider table.
My issue is that if I update a user without any provider function it works as expected. The fields which changed get updated but no new entry gets created. If the user has a provider function, all fields of the user get updated and no new entry gets created. In the table of the provider information each updated creates a new entry and the new ID gets set in the user table.
#Entity()
export class Users {
#Column('text', {primary: true})
uid: string;
#Column('text', {nullable: true})
firstName: string;
#Column('text', {nullable: true})
lastName: string;
#Column('text')
email: string;
#Column('text')
password: string;
#Column('text', {nullable: true})
role: string;
#OneToOne(type => Providers, providerData => providerData.user, {cascade: true})
#JoinColumn()
providerData: Providers;
#OneToOne(type => Clients, clientData => clientData.user, {cascade: true})
#JoinColumn()
clientData: Clients;
#Column('bytea', {nullable: true})
photo: Uint8Array;
}
Update function:
async update(uid: string, dto: UpdateUserDto): Promise<Users> {
const userToUpdate = await this.usersRepository.findOne(uid);
try {
const user = new Users();
const provider = new Providers();
const client = new Clients();
user.email = dto.email;
user.firstName = dto.firstName;
user.lastName = dto.lastName;
user.photo = dto.photo;
user.role = dto.role;
Logger.log(dto.email);
provider.licensed = dto.licensed;
provider.notes = dto.notes;
provider.paymentOptions = dto.paymentOptions;
provider.speciality = dto.speciality;
user.providerData = provider;
user.clientData = client;
const updatedUser: Users = Object.assign(user, dto);
updatedUser.uid = uid;
Logger.log('Updated User with UID: ' + userToUpdate.uid);
return await this.usersRepository.save(updatedUser);
} catch (error) {
Logger.log('Error updating user: ' + error);
}
}
What am I doing wrong or what is a better solution?
You are creating a new user instead of updating the existing one. try to add this line
const editedUser = this.usersRepository.merge(userToUpdate ,updatedUser);
and then save it
return this.usersRepository.save(editedUser);
Related
i am using mongoose module to get a pretty id but when i assign the prettyId value and use
await student.setNext("studentId");
i can't do student.save()
and i can't access student.prettyId
but it's stored in the database and it's acessable anywhere but the scope where i declare student
const { password, name, classId } = req.body;
const myClass = await Class.findOne({ prettyId: classId });
const student = new Student({
name: name,
password: await bcrypt.hash(password, 10),
class: {
id: myClass._id,
prettyId: myClass.prettyId,
name: myClass.name,
},
});
await student.setNext("studentId");
console.log(student.prettyId) // not working
await student.save() // not working
I got two entities with some relationships going on. The problem is that the EntityManager only saves the main entity and sets the foreign keys to NULL. I have read a similar question that OP seems to have solved himself, but his answer leaves me clueless.
I'm used to ORMs being able to insert nested entities in one call. Does have TypeORM have such functionality?
The entities are at the bottom of the question to save space here. The objects are built up like this:
const whiteFlourEntity = new Ingredient();
whiteFlourEntity.name = "white flour";
whiteFlourEntity.percentage = 100;
const flourEntities: Ingredient[] = [whiteFlourEntity];
const waterEntity = new Ingredient();
waterEntity.name = "water";
waterEntity.percentage = 68;
waterEntity.waterPercentage = 100;
const yeastEntity = new Ingredient();
yeastEntity.name = "yeast";
yeastEntity.percentage = 1;
const saltEntity = new Ingredient();
saltEntity.name = "salt";
saltEntity.percentage = 2;
const formulaEntity = new Formula();
formulaEntity.name = "test formula";
formulaEntity.flours = flourEntities;
formulaEntity.water = waterEntity;
formulaEntity.yeast = yeastEntity;
formulaEntity.salt = saltEntity;
When I save all nested entities first and after that the main entity all relationships are set up right. But when I only save the main entity, the sub-entities are not saved.
createConnection({
/** */
"entities": [Ingredient, Formula]
}).then(async connection => {
// WITH THIS LINES ACTIVE ALL RELATIONSHIPS ARE SAVED CORRECTLY
// flourEntities.forEach((flour: Ingredient) => {
// connection.manager.save(flour);
// });
// await connection.manager.save(waterEntity );
// await connection.manager.save(yeastEntity );
// await connection.manager.save(saltEntity );
await connection.manager.save(formulaEntity);
}).catch(error => console.log(error));
#Entity()
export default class Ingredient {
#PrimaryGeneratedColumn()
id?: number;
#Column()
name!: string;
#Column()
percentage!: number;
#Column()
waterPercentage!: number = 0;
}
#Entity()
export class Formula {
#PrimaryGeneratedColumn()
id?: number;
#Column()
name!: string;
#ManyToMany(() => Formula)
#JoinTable()
flours!: Ingredient[];
#OneToOne(() => Ingredient)
#JoinColumn()
water!: Ingredient;
#OneToOne(() => Ingredient)
#JoinColumn()
yeast!: Ingredient;
#OneToOne(() => Ingredient)
#JoinColumn()
salt!: Ingredient;
#ManyToMany(() => Ingredient)
#JoinTable()
extras?: Ingredient[];
#ManyToMany(() => Formula)
#JoinTable()
subFormulas?: Formula[];
}
I have a couple of questions about NestJS and TypeOrm.
First, how to pass an array of strings to DTO? I tried just to use :string[] type, but the compiler gives an error.
This is my Post entity:
#Entity('posts')
export class Post {
#PrimaryGeneratedColumn()
id: number;
#ManyToOne(() => User, user => user.posts, { cascade: true })
author: number;
#Column({ type: 'timestamp' })
date: Date;
#Column()
text: string;
#Column({ default: 0 })
likes: number;
#OneToMany(() => Photo, photo => photo.post, { cascade: true })
photos: Photo[];
}
And CreatePostDto:
export class CreatePostDto {
authorId: number;
date: Date;
text?: string;
// photos?: string[];
}
And the second question: How can i save to the repository every photo (keeping the connection with post), posts to the posts repo and update user by adding new post binded to him.
I tried something like this, but it won't work obviously.
async create(createPostDto: CreatePostDto) {
const post = this.postsRepository.create(createPostDto);
const user = await this.usersRepository.findOne(createPostDto.authorId);
return this.postsRepository.save({author: user, date: createPostDto.date, text: createPostDto.text});
}
What you missed here is saving photos before bind them with the post, here's an example:
async create(createPostDto: CreatePostDto) {
let photos:Array<Photo> = [] ; array of type photo entity
for(let urlPhoto of createPostDto.photos)
{
let photo = await this.imageRepository.save({url : urlPhoto }); you must save the photos first
photos.push(photo);
}
const user = await this.usersRepository.findOne(createPostDto.authorId);
return this.postsRepository.save({author: user, date: createPostDto.date, text:
createPostDto.text,photos:photos});
}
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number
#Column("simple-array")
names: string[]
}
I'm using NestJS with TypeORM
I got two entities with relation:
export class Device {
#PrimaryGeneratedColumn()
id: number;
#Column("macaddr")
mac: string;
#OneToMany(type => DeviceStatus, deviceStatus => deviceStatus.mac)
#JoinColumn()
status: DeviceStatus[]
#Column()
created: Date;
}
export class DeviceStatus {
#PrimaryGeneratedColumn()
id: number;
#ManyToOne(type => Device, device => device.mac)
#JoinColumn({ name: "mac", referencedColumnName: "mac" })
mac: Device;
#Column()
deviceState: number;
#Column()
created: Date;
}
I want to get Device but only it's latest DeviceStatus on its status property.
Now I am doing it like this:
const deviceQuery: Device = await this.deviceRepository
.createQueryBuilder("device")
.where('device.id = :id AND "device"."userId" = :user', {id: id, user: user.id})
.getOne();
const statusQuery: DeviceStatus = await this.deviceStatusRepository.createQueryBuilder("status")
.where('status.mac = :mac', { mac: deviceQuery.mac })
.orderBy('created', "DESC")
.limit(1)
.getOne();
deviceQuery.status = [statusQuery];
How can I do this using just one typeorm queryBuilder query?
I already tried this, but it doesn't map status property to Device, .execute() gets all DeviceStatus properties but it wont map the entities.
const deviceQuery: Device = await this.deviceRepository
.createQueryBuilder("device")
.leftJoinAndSelect(subquery => {
return subquery
.from(DeviceStatus, "status")
.innerJoin(subquery => {
return subquery
.select(["status2.mac"])
.addSelect("MAX(created)", "lastdate")
.from(DeviceStatus, "status2")
.groupBy("status2.mac")
}, "status3", 'status.created = "status3"."lastdate"')
}, "status", 'device.mac = "status"."mac"')
.where('device.id = :id AND "device"."userId" = :user', {id: id, user: user.id})
.getOne();
You can achieve this by joining status 2 times.
The second status join (next_status) condition should compare created date and be after the first joined status.
Then just check if next_status is null, that means that there is no status younger than the first join
Code is probably a better explanation:
const device: Device = await this.deviceRepository
.createQueryBuilder("device")
.leftJoinAndSelect("device.status", "status")
.leftJoin("device.status", "next_status", "status.created < next_status.created")
.where("next_status.id IS NULL")
// device.status is the latest status
I'm trying to share some base interfaces between the client code and the server code. I'm having problems when using the interfaces to create data models in mongoose.
The problem I have is how to access the document._id property in the client. I can't add _id to the User interface without causing compilation errors and I can't access _id without declaring it.
My project layout:
/src
-/client
--/user.service.ts
-/server
--/models
---/user.model.ts
-/common
--/common.d.ts
user.service.ts
import { User } from 'common'
deleteUser(user: User): Promise<User> {
return this.http.delete( 'http://someurl/api/users' + user._id )
.toPromise()
.then( resp => resp.json().data as User )
.catch( err => this.errorHandler(err) );
}
user.model.ts
import { model, Schema, Document } from 'mongoose';
import { User } from 'common';
let UserSchema = new Schema {
firstName: String,
lastName: String,
email: String
}
export interface UserDocument extends User, Document { }
export var UserModel:Model<UserDocument> = model<UserDocument>('Users', UserSchema);
common.d.ts
declare module 'common' {
export interface User {
firstName: string;
lastName: string;
email: string;
}
}
Thanks for the help
You can declare _id as optional:
export interface User {
_id?: string;
firstName: string;
lastName: string;
email: string;
}
Or you can have another interface for a user with id:
export interface PersistedUser extends User {
_id: string;
}
And cast to it whenever needed.