I have the following Schema:
#Schema()
export class Category extends Document {
#Prop({ required: 'Category name is required' })
name: string;
#Prop({ default: true })
active: boolean;
}
export const CategorySchema = SchemaFactory.createForClass(Category);
And another one that imports this one:
#Schema()
export class Skill extends Document {
#Prop({ required: 'Skill name is required' })
name: string;
#Prop({ default: true })
active: boolean;
#Prop()
category: typeof CategorySchema;
}
export const SkillSchema = SchemaFactory.createForClass(Skill);
And the following query to find the data:
let objects = await this.model
.find(options)
.collation({ locale: "pt" })
.exec();
Everything seems to work, but when I call passing the options to search for the nested object id it doesn't find anything:
const options = {
'category._id': id,
active: true,
};
When I query other property than the nested id, it seems to work fine:
const options = {
'category.name': 'test',
active: true,
};
So, maybe my schema is wrong?
Any clues here?
Related
I have a typeorm entity that uses single table inheritance:
#Entity()
#TableInheritance({ column: { type: "varchar", name: "type" } })
#ObjectType({ isAbstract: false })
export class OrganisationEntity extends BaseEntity {
#Field()
#Column()
name: string;
#Field(() => [UserEntity])
#OneToMany(() => UserEntity, (user) => user.organisation)
users: UserEntity[];
}
and some child entities:
#ChildEntity()
#ObjectType()
class MedicalOrganisation {
}
#ChildEntity()
#ObjectType()
class SoftwareOrganisation {
}
#ChildEntity()
#ObjectType()
class MedicalOrganisation {
}
I'm wondering how I can get the type and the child properties from the parent organisation so that I can do something like:
const organisation = await OrganisationEntity.findOne()
if(organisation.type === "medicalOrganisation"){
...
}
But it seems I'm not allowed to access the type property through the parent. Does anyone know how this can be done?
I'd prefer not to use instanceof because it requires the child entities and is causing circular dependencies.
You've got two options. Either use .createQueryBuilder and get the item using getRawOne and the returned object would contain a field named OrganisationEntity_type which can be used to do the checks. It's value would either be 'MedicalOrganisation', or 'SoftwareOrganisation' and so on.
const orgRepository: Repository<OrganisationEntity> = getRepository(OrganisationEntity);
let organisation = await orgRepository.createQueryBuilder().getRawOne();
// organisation = {
// OrganisationEntity_name: 'name',
// OrganisationEntity_users: [
// {},
// {}
// ],
// OrganisationEntity_type: 'MedicalOrganisation'
// }
Or, you could add the field type in the OrganisationEntity itself like this:
#Entity()
#TableInheritance({ column: { type: "varchar", name: "type" } })
#ObjectType({ isAbstract: false })
export class OrganisationEntity extends BaseEntity {
#Field()
#Column()
name: string;
#Field(() => [UserEntity])
#OneToMany(() => UserEntity, (user) => user.organisation)
users: UserEntity[];
#Column
type: string;
}
and do a straightforward:
let organisation = await OrganisationEntity.findOne();
// organisation =
// MedicalOrganisation {
// name: 'name',
// users: [{}, {}],
// type: 'MedicalOrganisation'
// }
and you get the type field in the object itself.
I've been trying to recover data querying with an include, my first model is "Block" and this has a "hasMany" relation to the destiny model called "Site". Well, usually this works as intended but this time it has a catch, the data is stored in mongodb with ObjectID(), so if I try to query it on robo3t or whereever, I have to put the ObjectId and inside the ID itself of the document.
I found on the loopback 4 documentation that if I'm saving or relating data which is store with objectId, I have to set the property dataType:ObjectId, but still dosnt work.
My Block model:
import {Entity, model, property, hasMany} from '#loopback/repository';
import {Site} from './site.model';
#model({
settings: {
strict: false,
mongodb: {
collection: 'bloques',
},
},
})
export class Block extends Entity {
#property({
type: 'string',
id: true,
defaultFn: 'uuidv4',
mongodb: {dataType: 'ObjectId'},
})
id: string;
#property({
type: 'date',
required: true,
})
dateBlock: string;
#property({
type: 'object',
required: true,
})
statuses: object;
#hasMany(() => Site)
minedIds: Site[];
/* #hasMany(() => Site)
minedIds: Site[];*/
[prop: string]: any;
constructor(data?: Partial<Block>) {
super(data);
}
}
export interface BlockRelations {
// describe navigational properties here
}
export type BlockWithRelations = Block & BlockRelations;
My Site Model:
import {Entity, hasMany, model, property} from '#loopback/repository';
import {CachimbaModel} from './cachimba-model.model';
#model({
settings: {
// model definition goes in here
mongodb: {collection: 'minadas'},
},
})
export class Site extends Entity {
#property({
type: 'string',
id: true,
generated: true,
mongodb: {dataType: 'ObjectId'},
})
id?: string;
#property({
type: 'date',
required: true,
})
lastUpdate: string;
#property({
type: 'string',
required: true,
})
name: string;
#property({
type: 'string',
required: true,
})
logo: string;
#hasMany(() => CachimbaModel)
data: CachimbaModel[];
#property({
type: 'string',
dataType: 'ObjectId',
})
blockId?: string;
constructor(data?: Partial<Site>) {
super(data);
}
}
export interface SiteRelations {
// describe navigational properties here
}
export type SiteWithRelations = Site & SiteRelations;
And their respective repos are as the following:
block-repo
import {Getter, inject} from '#loopback/core';
import {DefaultCrudRepository, repository, HasManyRepositoryFactory} from '#loopback/repository';
import {DbDataSource} from '../datasources';
import {Block, BlockRelations, Site} from '../models';
import {SiteRepository} from './site.repository';
export class BlockRepository extends DefaultCrudRepository<
Block,
typeof Block.prototype.id,
BlockRelations
> {
public readonly minedIds: HasManyRepositoryFactory<Site, typeof Block.prototype.id>;
constructor(
#inject('datasources.DbDataSource') dataSource: DbDataSource,
#repository.getter('SiteRepository')
protected siteRepositoryGetter: Getter<SiteRepository>,
) {
super(Block, dataSource);
this.minedIds = this.createHasManyRepositoryFactoryFor('minedIds', siteRepositoryGetter,);
this.registerInclusionResolver('minedIds', this.minedIds.inclusionResolver);
}
}
site-repo
import {Getter, inject} from '#loopback/core';
import {
DefaultCrudRepository,
HasManyRepositoryFactory,
repository,
} from '#loopback/repository';
import {DbDataSource} from '../datasources';
import {CachimbaModel, Site, SiteRelations} from '../models';
import {CachimbaModelRepository} from './cachimba-model.repository';
export class SiteRepository extends DefaultCrudRepository<
Site,
typeof Site.prototype.id,
SiteRelations
> {
public readonly data: HasManyRepositoryFactory<
CachimbaModel,
typeof Site.prototype.id
>;
constructor(
#inject('datasources.DbDataSource') dataSource: DbDataSource,
#repository.getter('CachimbaModelRepository')
protected cachimbaModelRepositoryGetter: Getter<CachimbaModelRepository>,
) {
super(Site, dataSource);
this.data = this.createHasManyRepositoryFactoryFor(
'data',
cachimbaModelRepositoryGetter,
);
this.registerInclusionResolver('data', this.data.inclusionResolver);
}
}
I try to query and obtain the nested data from the block controller using an include as the following:
And my database has the following values:
I will apreaciate your help for real! Thanks in advance!
My bad, I didnt have to set the ids on minedIds property, I had to set it on the destinatary model property opposite to the hasMany
I'm trying to use automapper-ts to map from dto to entity and vice versa.
The creation of the mappings seems fine to me but when I try to map I always get an object without values.
The config:
automapper
.createMap(CityDTO, City)
.forMember('createdBy', (opts: AutoMapperJs.IMemberConfigurationOptions) => opts.ignore())
.forMember('createdAt', (opts: AutoMapperJs.IMemberConfigurationOptions) => opts.ignore())
.forMember('updatedBy', (opts: AutoMapperJs.IMemberConfigurationOptions) => opts.ignore())
.forMember('updatedAt', (opts: AutoMapperJs.IMemberConfigurationOptions) => opts.ignore());
// These are properties present in the entity but not in the DTO
And this is the mapping method:
const fromCityToCityDto = (source: CityDTO, destination?: City): City => {
if (!destination) {
destination = new City();
}
// This produces 'source name' and 'source population'
Object.keys(source).forEach((key) => console.log('source', key));
// This never runs, it seems it doesn't have any key
Object.keys(destination).forEach((key) => console.log('destination', key));
destination = automapper.map(CityDTO, City, source);
return destination;
};
As ORM I'm using typeorm. This is the entity class:
export class EntityBase extends BaseEntity {
#PrimaryGeneratedColumn({ name: 'Id' })
id: number;
#Column({ name: 'CreatedBy', length: 50 })
createdBy: string;
#CreateDateColumn({ name: 'CreatedAt' })
createdAt: Date;
#Column({ name: 'UpdatedBy', nullable: true, length: 50 })
updatedBy: string;
#CreateDateColumn({ name: 'UpdatedAt', nullable: true })
updatedAt: Date;
}
#Entity('Cities')
export class City extends EntityBase {
#Column({ name: 'Name', length: 50 })
name: string;
#Column({ name: 'Population' })
population: number;
}
EDIT: This is the result of console.log:
source id
source name
source population
source createdBy
source createdAt
source updatedBy
source updatedAt
Looks like the destination doesn't have any property.
https://loopback.io/doc/en/lb4/HasMany-relation.html
I followed this steps and then tried to get data with include but I get 500.
500 Error: Invalid "filter.include" entries: {"relation":"ranks"}
What I want is to get games object with its related ranks.
Rank Model
import { Entity, model, property, belongsTo } from '#loopback/repository';
import { Game, GameWithRelations } from './game.model';
#model({ settings: { strict: 'filter' } })
export class Rank extends Entity {
#property({
type: 'string',
id: true,
})
id?: string;
#property({
type: 'string',
})
name?: string;
#property({
type: 'string',
})
shortName?: string;
#property({
type: 'string',
})
avatar?: string;
#belongsTo(() => Game)
gameId: string;
constructor(data?: Partial<Rank>) {
super(data);
}
}
export interface RankRelations {
game?: GameWithRelations;
}
export type RankWithRelations = Rank & RankRelations;
Game Model
import { Entity, model, property, embedsMany, hasMany } from '#loopback/repository';
import { Rank, RankWithRelations } from './rank.model';
import { HasMany } from 'loopback-datasource-juggler';
#model({ settings: { strict: 'filter' } })
export class Game extends Entity {
#property({
type: 'string',
id: true,
})
id?: string;
#property({
type: 'string',
required: true,
})
name?: string;
#property({
type: 'string',
})
shortName?: string;
#property({
type: 'string',
})
avatar?: string;
#hasMany<Rank>(() => Rank, { keyTo: 'gameId' })
ranks?: Rank[];
constructor(data?: Partial<Game>) {
super(data);
}
}
export interface GameRelations {
}
export type GameWithRelations = Game & GameRelations;
Game Controller
// in this method
// 500 Error: Invalid "filter.include" entries: {"relation":"ranks"}
#get('/games/{id}')
async findById(#param.path.string('id') id: string): Promise<Game> {
return await this.gameRepository.findById(id, { include: [{ relation: 'ranks' }] });
}
Please run your application with DEBUG=loopback:repository:relation-helpers, that way you will get a debug message explaining why filter.include entry was rejected.
You can find the code building the error message here:
https://github.com/strongloop/loopback-next/blob/97ba7893e253bfc2967ac08e408b211c9b9b7f40/packages/repository/src/relations/relation.helpers.ts#L96-L100
The most likely cause: your GameRepository does not have any InclusionResolver registered for ranks relation.
Please refer to our todo-list example to see how to register inclusion resolver. Cross-posting from https://github.com/strongloop/loopback-next/blob/97ba7893e253bfc2967ac08e408b211c9b9b7f40/examples/todo-list/src/repositories/todo-list.repository.ts#L41-L46:
this.todos = this.createHasManyRepositoryFactoryFor(
'todos',
todoRepositoryGetter,
);
this.registerInclusionResolver('todos', this.todos.inclusionResolver);
In my nectjs project I'm using TypeORM and I have 2 entities user and post,
and I'm tying to make a relation between them
user.entity.ts
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column({ length: 50, unique: true })
name: string;
#OneToMany(type => Post, post => post.user)
posts: Post[];
}
post.entity.ts
#Entity()
export class Post {
#PrimaryGeneratedColumn()
id: number;
#Column({ length: 30 })
title: string;
#ManyToOne(type => User, user => user.posts)
user: User;
}
So I want to join these tables and get post by it's title for specific user
const PostObject = await createQueryBuilder("post")
.leftJoinAndSelect(
"post.user",
"user",
"post.title = :title",
{ title: "title1" }
)
.where("user.id = :id", { id: id })
.getOne();
but when I run the project and execute this function I get this error:
Error: "post" alias was not found. Maybe you forget to join it?
You have to inject the Repository first:
constructor(
#InjectRepository(Post) private readonly postRepository: Repository<Post>
) {
}
Then you can create the QueryBuilder for the Post entity by passing in the alias:
this.postRepository.createQueryBuilder('post')
// ^^^^^^^^^^^^^^^^
.leftJoinAndSelect(
'post.user',
'user',
'post.title = :title',
{ title: 'title1' },
)
.where('user.id = :id', { id })
.getOne();
Can also use getRepository like below.
const PostObject = await getRepository("post").createQueryBuilder("post")
.leftJoinAndSelect(
"post.user",
"user",
"post.title = :title",
{ title: "title1" }
)
.where("user.id = :id", { id: id })
.getOne();