How to delete entities from one-to-many collection with only composite primary keys in MariaDB - mikro-orm

#Entity()
export class Job {
#PrimaryKey({ type: BigIntType })
id: string;
#OneToMany(() => JobExperienceLevel,
jobExperienceLevel => jobExperienceLevel.job, {cascade: Cascade.ALL], orphanRemoval: true})
experienceLevels = new Collection<JobExperienceLevel>(this);
}
#Entity()
export class JobExperienceLevel {
#PrimaryKey()
#Enum({
items: () => JobExperienceLevelType
})
experienceLevel: JobExperienceLevelType;
#ManyToOne({nullable:false, primary: true, joinColumn: 'job_id'})
job: Job;
}
export enum JobExperienceLevelType {
ENTRY_LEVEL = 'ENTRY_LEVEL',
JUNIOR = 'JUNIOR',
REGULAR = 'REGULAR',
SENIOR = 'SENIOR'
}
After calling experienceLevels.removeAll() on some job entity it is generating the following query:
delete from `job_experience_level` where `experience_level` = 'SENIOR' and `job_id` is null
The database table 'job_experience_level' contains only composite primary keys (experience_level, job_id)
I have checked that before calling removeAll method there is one entity 'SENIOR' in the collection.
I am using entityrepository with persistAndFlush on the job entity.
The problem is that this query is wrong and it should populate the correct job_id.
I also tried to remove the #PrimaryKey() from experienceLevel property, but then there is no delete query in the transaction at all.

As discussed in the comments, there was a bug with orphan removal and composite keys. Upgrade to v3.6.7 to fix it.
https://github.com/mikro-orm/mikro-orm/blob/master/CHANGELOG.md#367-2020-04-16
Here is a testcase to make sure it really works:
https://github.com/mikro-orm/mikro-orm/commit/94c71c89a648e03fad38a93cced7fa92bbfd7ff7#diff-595473b980e4d4384f667f60dbddde2aR1

Related

Mikro-orm order by ST_Distance_Sphere using MySQL driver

With MySQL, I am trying to order by ST_Distance_Sphere using QueryBuilder.
I have a entity:
import { Entity, PrimaryKey, Property } from "mikro-orm";
#Entity({ tableName: "studio" })
export default class StudioEntity {
#PrimaryKey()
public id!: number;
#Property()
public name!: string;
#Property({ columnType: "point srid 4326" })
public geometry!: object;
}
And I am trying:
export default class StudioStore {
private studioRepository: EntityRepository<StudioEntity>;
public constructor(ormClient: OrmClient) {
this.studioRepository = ormClient.em.getRepository(StudioEntity);
}
public async findPage(first: number): Promise<StudioEntity[]> {
const query = this.studioRepository.createQueryBuilder().select("*");
query.addSelect(
"ST_Distance_Sphere(`e0`.`geometry`, ST_GeomFromText('POINT(28.612849 77.229883)', 4326)) as distance",
);
query.orderBy({ distance: "ASC" });
return query.limit(first).getResult();
}
}
But I get a ORM error:
Trying to query by not existing property StudioEntity.distance
So, I try to add a property to the entity:
#Property({ persist: false })
public distance?: number;
But now I get a MySQL error:
Unknown column 'e0.distance' in 'order clause'
This is the generated SQL query:
[query] select `e0`.*, ST_Distance_Sphere(`e0`.`geometry`, ST_GeomFromText('POINT(28.612849 77.229883)', 4326)) as distance from `studio` as `e0` order by `e0`.`distance` asc limit 5 [took 4 ms]
You will need to fallback to knex, as QB currently supports only defined property fields in order by. You will also need to define that virtual distance property as you already did, so the value can be mapped to the entity.
https://mikro-orm.io/docs/query-builder/#using-knexjs
const query = this.studioRepository.createQueryBuilder().select("*");
query.addSelect("ST_Distance_Sphere(`e0`.`geometry`, ST_GeomFromText('POINT(28.612849 77.229883)', 4326)) as distance");
query.limit(first);
const knex = query.getKnexQuery();
knex.orderBy('distance', 'asc');
const res = await this.em.getConnection().execute(knex);
const entities = res.map(a => this.em.map(StudioEntity, a));
Not very nice I must say, totally forgot that it is possible to order by computed fields. Will try to address this in v4. I think it could even work as your second approach, as QB could simply check if the property is virtual (has persist: false), and then it would not prefix it.
edit: as of 3.6.6 the approach with persist: false should work out of box

AppSync #connection not making two-way connection

I'm using graphql through AWS AppSync. Given my models below I would expect when I successfully createClassroom with createClassroomInput that the teacherClassrooms would have a new Classroom associated to it and that the newly created Classroom would have a teacher associated to it.
The outcome, however, is that Classroom is created and the User is correctly associated with the new Classroom but the Classroom is not associated to the existing User.
type User #model {
id: ID!
userType: String!
teacherClassrooms: [Classroom] #connection(name: "TeacherClassrooms")
}
type Classroom #model {
id: ID!
teacher: User #connection(name: "TeacherClassrooms")
linkCode: String!
name: String!
}
export type CreateClassroomInput = {
id?: string | null,
linkCode: string,
name: string,
classroomTeacherId?: string | null,
};
So, if I query for listClassrooms, each classroom comes back with its associated User. But if I query for a User they do not have any classrooms in their teacherClassrooms array.
Do I need to updateUser when I create a new Classroom? My intuition, and my understanding of the docs, lead me to believe that AppSync would handle both updates when #connection is specified on a model property.
Or is this just a way of indicating to the backend that "for each Id in this property array assume it's of type X and when queried go fetch by Id against the corresponding table"?
Check your list query. I had the same issue and then realised that generated list query was missing relation attributes after I updated schema with name connection. In your case something like this
listUsers {
items {
id
teacherClassrooms {
items {
linkCode
id name
}
}
}
}
inside your listUsers query

Addtional parameter with #ngrx/entity

I want to keep employee array and page loading status in store state. So my initial state will look like this
const initialState = {
isLoading: false,
employees: []
};
Now i want to use #ngrx/entity for employee instead of array. The documentation only show the demo for using entity with entire state.
How can i use entity for only one property rather than entire state?
If it's not possible what is the alternative for above scenario?
See the docs for an example:
import { EntityState, EntityAdapter, createEntityAdapter } from '#ngrx/entity';
export interface User {
id: string;
name: string;
}
export interface State extends EntityState<User> {
// additional entities state properties
selectedUserId: number;
}
export const adapter: EntityAdapter<User> = createEntityAdapter<User>();

Sqlite queries inside the constructor are executing twice?

I need to create multiple tables in local DB using the Nativescript-Sqlite plugin. When I tried, the queries written inside the constructor is executing twice. I found that by given alert for each table creation code.
How should I rectify this?
constructor(private page: Page, private userService: UserService) {
this.user = new User();
(new Sqlite("my.db")).then(db => {
database = db;
db.execSQL("CREATE TABLE IF NOT EXISTS people1 (id INTEGER PRIMARY KEY AUTOINCREMENT, firstname TEXT, lastname TEXT)").then(id => {
alert("table1 insertd"); }, error => {
console.log("CREATE TABLES ERROR", error);
});
db.execSQL("CREATE TABLE IF NOT EXISTS people2 (id INTEGER PRIMARY KEY AUTOINCREMENT, firstname TEXT, lastname TEXT)").then(id => {
alert("table2 insertd"); }, error => {
console.log("CREATE TABLE ERROR", error);
});
}, error => {
console.log("OPEN DB ERROR", error);
});}
My development environment-
Nativescript with angular2
IDE - Visual Studio Code
-Thanks in advance.
You could be injecting this component in multiple place -- perhaps early in your app's lifecycle (app.module.ts perhaps?). I would first check to see if the constructor is being called twice. If it is, you're component is being instantiated twice.

Redux - cross-entity state management

I'm using Redux and ImmutableJS to manage the state of my app. I've created the following two Records:
export const OrderRecord = Record({
id: null,
productId: null,
amount: 1,
});
export const ProductRecord = Record({
id: null,
name: '',
price: 0,
});
My global state is normalized based on the normalizr approach like this:
const state = {
entities: {
orders: new OrderedMap(new Map({
1: new OrderRecord(createOrderItem(1, 1)),
})),
products: new OrderedMap(new Map({
1: new ProductRecord(createProductItem(1)),
})),
},
};
I'm using this specification for testing purposes.
Now I'm trying to make some selects with computed fields using Reselect.
export const getVisibleOrders = createSelector(
[getProducts, getOrders],
(products, orders) => {
orders.map(order => {
const product = products.get(order.productId.toString());
if (!product) {
return order;
}
const totalPrice = order.amount * product.price;
order.set('productName', product.name);
order.set('totalPrice', totalPrice);
return order;
});
}
);
, but I get the following error message:
Error: Cannot set unknown key "productName" on Record
I know the reason - Record cannot contain any undefined keys, but my question is: Is there any suggested approach how gracefully solved this problem?
I don't want to extend my Records to support this kind of computed parameters (product.name and totalPrice).
I don't want to keep the static and computed parameters in one place, because for example the 'productName' parametr is from "Product" entity and not from "Order" entity.
Thank you.
The whole point of using Immutable.Record is to not let you add new keys to your record, hence the error message you get. And the whole point of selectors is to expose such "computed" property if you want to consume them outside. In your case, you can simply return a new Map() object or a new record type if you need to use the dotted syntax :
return Map({
productName: 'foobar'
}).merge(order)

Resources