Skip to content
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

Lazy relation promise persistence #2902

Conversation

yazshel
Copy link

@yazshel yazshel commented Oct 8, 2018

await and persist resolved value of Promise instances on lazy relation properties (closes #2729)

@yazshel
Copy link
Author

yazshel commented Oct 8, 2018

I've created this PR to prompt some discussion about how lazy loading might be improved, by enabling the user to provide lazy relation values using Promise.resolve({ <values> }), or an unresolved database query Promise (both referred hereafter as a "dirty" Promise - in contrast to Promise instances created by DatabaseLoader to lazy-load "clean" relation data records).

One of the main advantages to these changes is that they remove the need for nasty TypeScript type casts when assigning object literals to lazy-load properties (which require Promise<{entity}>). Similarly, this circumvents the need to manually await on any Promise.resolve(<entity>) assignments to resolve the Promise before persisting a parent entity.

In addition to the tests for #2729, there are 2 main changes in this PR:

  1. Changes to PlainObjectToNewEntityTransformer to check for a Promise instance on lazy relation properties, and transform this to a Promise which recursively applies the transformer to the Promise's resolved value.
  2. Changes to the EntityPersistExecutor to await on "dirty" Promise instances on lazy relation properties, and persist entities including this resolved data. This allows the entities created using the logic in 1) to be persisted.

Change #1 enables the user to provide a Promise for lazy relation values when creating entities with EntityManager.create() or Repository.create(), for instance:

@Entity()
class Person {
  name: string;
    
  @OneToMany(() => Pet, pet => pet.owner, { lazy: true })
  pets: Promise<Pet[]>;
}

@Entity()
class Pet {
  name: string;

  type: "cat" | "dog" | "dinosaur";

  @ManyToOne(() => Person, person => person.pets, { lazy: true })
  owner: Promise<Person>;
}

const personRepo = getRespository(Person);
const person = personRepo.create({
  name: 'Fred Flintstone',
  pets: Promise.resolve([
    { name: 'Dino', type; "dinosaur" }
  ])
});
// "person" will now be a Person instance, with person.pets a Promise<Pet[]>
// (ie. the object literal resolved by `Promise.resolve()` is transformed to a `Pet` instance).

Change #2 allows saving the entities with unresolved user-provided Promise values on lazy-load properties; for example (given the same above entity setup):

const manager = getEntityManager();
const petRepo = getRespository(Pet);
const person = new Person();
person.name =  'Fred Flintstone';
// e.g. set relation to unresolved Promise loading related records from DB
person.pets = petRepo.findMany({ name: 'Dino' });
// "findMany" promise has not yet resolved, but "save" will now `await` the user-set `Promise`
await manager.save(person);

I expect that you TypeORM maintainers will want some improvements to the supporting changes before merging this PR - particularly those around the identification of "dirty" (ie. user-provided) Promises on lazy properties which is a little nasty as currently implemented.

To identify these "dirty" promises, I've added logic to RelationLoader which sets a hidden entity["__dirty_<propertyName>__"] flag when a user-provided Promise is set. Then changes to RelationMetadata.getEntityValue() check for this flag, and if set, returns the dirty Promise instance (even when getLazyRelationsPromiseValue argument is false). This probably needs to be improved, but I wasn't sure on the best approach.

So I thought rather than having these changes wasting away on my SSD, I'd create this PR and start the discussion :)

Any ideas / suggestions / responses?

@pleerock
Copy link
Member

Im thinking to drop this feature from TypeORM. From the beginning it was experimental and too many people abuse it. And they all had problems.

@Kononnable
Copy link
Contributor

Kononnable commented Oct 21, 2018

Im thinking to drop this feature from TypeORM

You mean whole lazy loading or just some part of it?

@pleerock
Copy link
Member

I mean this owner: Promise<Person>; syntax and the way it works, e.g. await model.owner, basically drop the whole feature. Alternatively we can implement some insane functionality like model.owner = repository.loadRelation("owner", model)

@seawatts
Copy link

seawatts commented Mar 8, 2021

Any updates here?

@AlexMesser
Copy link
Collaborator

This is quite old PR and there are too many conflicts. Closing for now, if somebody interested in these changes please feel free to create a new issue.

@AlexMesser AlexMesser closed this May 29, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Issue with updating parent in Lazy relationships
5 participants