-
-
Notifications
You must be signed in to change notification settings - Fork 505
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Feature] Polymorphic relationships #706
Comments
Not supported currently, sure we can have it too. I dont think there is a workaround. |
We would love this! Do you already have ideas on how to implement it? Could we possibly help? |
Originally I was thinking only about M:N, do you guys think we need also polymorphic 1:1 and m:1? It looks a bit weird to me, like upside down composite keys mixed with STI. How can FKs work this way? Or they simply don't? I don't really have a clear idea of how it could be implemented, if you wanna help, feel free to clone the repository and start trying :] I'd say the first thing that is needed (and basically anyone can help) is to specify test cases that will define how the feature should actually work. |
Guess that 1:1 and 1:m are needed as well, but m:n defintely. We got across this in our project while connecting measures to causes and effects. Where we called the connection between them Foreign key constraints would not work anymore in that case. Since a linkable will either pick a cause or effect from the respective table. The m:n table holds an id referencing the id from the causes or effects table and it also holds a string with the type of the id (either cause or effect). |
Any updates on this? I'm currently in progress to upgrading to v.4 and a change to the 'mappedBy' property on relationships broke a workaround I had in place for this. |
I just finished hacking the polymorphic embeddables support (#2426), and turns out it wasn't that difficult as I initially though (took less than 2 focused days). I don't want to pospone v5 release too much, but I am now more confident polymorphic relations will make it to some of the 5.x feature releases. If there would be some brave contributor that would like to give this a try, be sure to check out the PR, as the implementation could be very similar (but definitely more complex, as we will need to handle collections, propagations, autojoining for m:1/1:1 to know the type, etc). It would greatly help if someone could prepare comprehensive test suite, so we know how things should behave in edge cases (never used this in the wild myself so not sure how it really works). |
I tried to dig into the code, but no accurate results for now. Basically, I'm trying to store users in 1 global table then some specific fields to Students and Teacher to 2 different tables, owned by User. So I need a 1:1 polymorphism. This is my scenario: // base.entity.ts
import { BaseEntity as BaseEntityOrig } from '@mikro-orm/core';
import { DbDate } from 'app/db/constants';
export class BaseEntity<T extends { id: id }> extends BaseEntityOrig<T, 'id'> {
constructor(input = {}) {
super();
this.assign(input);
}
@PrimaryKey({ type: t.integer })
id: id;
@Property({ columnType: DbDate })
createdAt: Date = new Date();
@Property({ columnType: DbDate, onUpdate: () => new Date() })
updatedAt: Date = new Date();
} // user.entity.ts
import { BaseEntity } from 'app/db/entities/base.entity';
import { AbstractUser } from 'user/entities/abstract.user.entity';
import { Student } from 'user/entities/student.entity';
import { Teacher } from 'user/entities/teacher.entity';
export enum USER_TYPES {
STUDENT = 1,
TEACHER = 2,
}
@Entity()
@Unique({ properties: ['type', 'user'] })
export class User extends BaseEntity<User> {
@Enum(() => USER_TYPES)
type: USER_TYPES;
@Property()
@Unique()
@IsEmail()
email: string;
// This doesn't work
@OneToOne({ entity: () => AbstractUser })
user: AbstractUser<Student | Teacher>;
} // abstract.user.entity.ts
import { BaseEntity } from 'app/db/entities/base.entity';
// Here I also tried removing the decorator @Entity and / or the keyword `abstract`
@Entity()
export abstract class AbstractUser<T extends { id: id }> extends BaseEntity<T> {} // student.entity.ts
import { AbstractUser } from 'user/entities/abstract.user.entity';
@Entity()
export class Student extends AbstractUser<Student> {
@Property()
firstname: string;
@Property()
lastname: string;
// Some other student fields
} // teacher.entity.ts
// Same as Student but with different fields |
@B4nan maybe the "easiest" way could be to keep the syntax of STI, but adding a property |
This is much more complicated, if you think it's easy, send a PR 😉 |
I worked with polymorphic relationships a lot in Laravel. I can explain my vision of a possible implementation in MikroORM. Let's start with a simple entities model with integer PKs. A user can like both posts and comments: @Entity()
export class User {
@PrimaryKey()
id: number;
} @Entity()
export class UserLike {
@PrimaryKey()
id: number;
@ManyToOne(() => User)
user: User;
@MorphTo(() => [Post, Comment])
entity: Post | Comment; // this will create `entity_type` and `entity_id` DB columns by default; column names should be configurable in @MorphTo()
} @Entity()
export class Post {
@PrimaryKey()
id: number;
@MorphMany(() => UserLike, (userLike) => userLike.entity) // @MorphOne() decorator should also be implemented
likes = new Collection<UserLike>(this);
} @Entity()
export class Comment {
@PrimaryKey()
id: number;
@MorphMany(() => UserLike, (userLike) => userLike.entity)
likes = new Collection<UserLike>(this);
}
It should create a
So, we introduce 3 new decorators:
It might make sense to replace @MorphTo() with @MorphToOne()/@MorphToMany() for readability/validation. Let's see what {
id: 1,
user: { id: 54 },
entity: { id: 2, type: 'Post' }
} There shouldn't be any problems with It's just a basic implementation proposal. Many edge cases still have to be covered yet. |
Thanks for the write up. I guess the biggest question for me was how to handle FK constraints, but I already understood they are simply omitted, as its not possible to have FK targetting different tables. Then another question is how cascading works, given we won't have FK constraints, we won't have referential intgrity either (e.g. I don't think we need more decorators (and definitely not with a naming that does not match what we already have). This can all work on the existing relation decorators just fine, the only difference is that instead of |
Yep, there's no way for DB-level cascading to work without FKs. It all lies on ORM or a user (handling it manually, maybe using lifecycle hooks). |
Thanks for the sumup @andrew-sol. I completely agree with your proposal (except the new decorators as explained above). The proposal given above being a common practice in many sgbd (nosql, sql..), it could be really amazing to have it. |
Any updates on this ? |
I'd like add my 5 cents. To enable FK constraints, instead of using So, from the example above, instead of getting this:
we'll have this
These then mapped or unmapped in runtime, with runtime or db level validations (it depends) that at least one field is set. Otherwise it is corrupted data (on write or read). I'm using this approach with MikroORM in my app for a one-to-one polymorphic relationship. But I have to maintain the fields and runtime checks when mapping manually of course. |
I use polumorphic in db level, I want to know is this anti-pattern? should I use mikroorm like this ? I found mikroorm will invote the id setter(accountId,contactId), I don't find any doc/faq about the how @ManyToOne(() => AccountEntity, { nullable: true, persist: false })
account?: AccountEntity;
@ManyToOne(() => ContactEntity, { nullable: true, persist: false })
contact?: ContactEntity;
@Property({ type: types.string, nullable: true })
customerId?: string;
@Property({ type: types.string, nullable: true })
customerType?: string;
@Property({ type: types.string, nullable: true, persist: false })
get accountId() {
return this.customerType === 'Account' ? this.customerId : undefined;
}
@Property({ type: types.string, nullable: true, persist: false })
get contactId() {
return this.customerType === 'Contact' ? this.customerId : undefined;
}
set accountId(value: string | undefined) {
setCustomer(value, this);
}
set contactId(value: string | undefined) {
setCustomer(value, this);
} customer_id text,
customer_type text,
account_id text generated always as ( case customer_type when 'Account' then customer_id end ) stored,
contact_id text generated always as ( case customer_type when 'Contact' then customer_id end ) stored, |
In our project we are using a polymorphic many-to-many relationship. I have looked through the documentation and the issues and could not find anything on it.
It is a concept we have used before with the Laravel framework, https://laravel.com/docs/7.x/eloquent-relationships#many-to-many-polymorphic-relations
I was wondering if you have thought about something like this already and if it is something that could be added.
The text was updated successfully, but these errors were encountered: