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

Incomplete Entities after using FindOption 'fields' #4464

Closed
montycb opened this issue Jun 19, 2023 · 6 comments
Closed

Incomplete Entities after using FindOption 'fields' #4464

montycb opened this issue Jun 19, 2023 · 6 comments
Milestone

Comments

@montycb
Copy link
Contributor

montycb commented Jun 19, 2023

After loading an entity with the 'fields' FindOption Subsequent load operations will return incomplete entities.
See included testcase

import 'reflect-metadata';
import { Collection, Entity, ManyToMany, ManyToOne, MikroORM, PrimaryKey, Property, wrap, Embeddable, Embedded, EntityCaseNamingStrategy, OneToMany } from '@mikro-orm/core';
import { PostgreSqlDriver } from '@mikro-orm/postgresql';

@Entity()
export class A {
  @PrimaryKey()
    id!: number

  @Property()
    test1!: string

  @ManyToMany({ entity: () => B, mappedBy: 'a' })
    b = new Collection<B>(this)
}

@Entity()
export class B {
  @PrimaryKey()
    id!: number

  @Property()
    test2!: string

  @Property()
    test3!: string

  @ManyToMany({ entity: () => A, inversedBy: 'b' })
    a = new Collection<A>(this)
}


describe('loadItems with fields', () => {
    let orm: MikroORM<SqliteDriver>;

    beforeAll(async () => {
        orm = await MikroORM.init({
            entities: [A, B],
            dbName: ':memory:',
            type: 'sqlite',
        })
    await orm.schema.refreshDatabase()
    })

    test('loadItems with fields', async () => {
      const b = orm.em.create(B, {test2: 'qwer', test3: 'asdf'})
      const a = orm.em.create(A, {test1: 'yxcv', b: [b]})

      await orm.em.persistAndFlush([a, b])
      orm.em.clear()

      await orm.em.findOne(B, 1, {fields: ['test2']}) // fail
      // await orm.em.findOne(B, 1) // ok

      const a2 = await orm.em.findOne(A, 1)
      const test3 = (await a2.b.loadItems())[0].test3
      
      // no 'find' or 'populate' specified, but still undefined if 'fields' was previously used
      expect(test3).toMatch('asdf') 
    })
})
@B4nan
Copy link
Member

B4nan commented Jun 19, 2023

The problem you are describing was previously expected behavior (generally, e.g. for em.findOne as well), and refresh: true was required to update the already loaded entity instance. That partially changed with v5 and auto-refreshing (#2263), which is apparently not working for Collection.init properly (which is used from the loadItems method). You could use em.populate to get around this, I guess I should really make the refactor to use that internally inside the Collection.init in v6 to resolve this.

await orm.em.populate(a2, ['b']);
const test3 = (await a2.b.loadItems())[0].test3; // the `loadItems` call here is a no-op, as we previously loaded the collection via `em.populate()`

Just to be sure, do you understand that the first findOne(B) call will give you the very same B entity instance? Basically doing em.findOne(B, 1) multiple times will give you the cached result from the identity map, only the very first call will query the database.

@montycb
Copy link
Contributor Author

montycb commented Jun 19, 2023

Yes, I understand that this is the same entity, that is exactly the problem I'm having. The cached entity is missing some fields, but is not refreshed even though it is necessary in this case. If the entity was added with 'fields' to the cache, I'm always getting the same entity, even without 'fields'. The cache should probably keep track of this.

@B4nan
Copy link
Member

B4nan commented Jun 19, 2023

The cache should probably keep track of this.

Not really, the fix for this won't change that - you always get only one instance of the entity - the fix will be about loading the missing fields, so the previous instance will no longer be partially loaded. That's why I am asking if you understand this, as that is the core concept here, not a bug. The bug is only in the entity not being reloaded automatically with the loadItems call - but it would be with em.findOne or with em.populate.

If you want to have multiple entity instances, you need to use a separate EM fork (which is what the FindOptions.disableIdentityMap option is doing behind the scenes).

@montycb
Copy link
Contributor Author

montycb commented Jun 19, 2023

Yes, I understand that. I don't want multiple instances of the same entity. I'm not too familiar with the mikro-orm code, so my assumption with the cache is probably wrong. I simply want, that the missing fields are automatically loaded into the existing entity.

@B4nan
Copy link
Member

B4nan commented Jun 24, 2023

I will keep this refactoring for v6, as it might be semi-breaking, the ordering of the collection items might change a bit (although from what I can see in the tests, it was rather wrong in v5 too, as the PK is used for ordering, even if it's a UUID, so probably fine, but let's stay on the safe side). Also there is one serialization problem that this refactoring introduces and is no longer present in v6 due to other refactorings already done in there.

@B4nan B4nan added this to the 6.0 milestone Jun 24, 2023
B4nan added a commit that referenced this issue Jul 31, 2023
This unifies the population mechanism for to-many relations by using the `em.populate()` internally.

Previously, the `Collection.init` (and methods using it, e.g. `loadItems`) were using custom
implementation, which in some cases resulting in different results as opposed to `em.populate()`.
With this PR, the `init` method is also using `em.populate`, yielding the same results as if you
would populate the relation.

Closes #4464
B4nan added a commit that referenced this issue Aug 1, 2023
…4571)

This unifies the population mechanism for to-many relations by using the
`em.populate()` internally.

Previously, the `Collection.init` (and methods using it, e.g.
`loadItems`) were using custom implementation, which in some cases
resulting in different results as opposed to `em.populate()`. With this
PR, the `init` method is also using `em.populate`, yielding the same
results as if you would populate the relation.

Closes #4464
@B4nan
Copy link
Member

B4nan commented Aug 1, 2023

So this ended up as a pretty painful refactoring, but it's finally working as expected.

Closing as implemented in v6 via #4571.

@B4nan B4nan closed this as completed Aug 1, 2023
B4nan added a commit that referenced this issue Sep 10, 2023
…4571)

This unifies the population mechanism for to-many relations by using the
`em.populate()` internally.

Previously, the `Collection.init` (and methods using it, e.g.
`loadItems`) were using custom implementation, which in some cases
resulting in different results as opposed to `em.populate()`. With this
PR, the `init` method is also using `em.populate`, yielding the same
results as if you would populate the relation.

Closes #4464
B4nan added a commit that referenced this issue Sep 20, 2023
…4571)

This unifies the population mechanism for to-many relations by using the
`em.populate()` internally.

Previously, the `Collection.init` (and methods using it, e.g.
`loadItems`) were using custom implementation, which in some cases
resulting in different results as opposed to `em.populate()`. With this
PR, the `init` method is also using `em.populate`, yielding the same
results as if you would populate the relation.

Closes #4464
B4nan added a commit that referenced this issue Sep 24, 2023
…4571)

This unifies the population mechanism for to-many relations by using the
`em.populate()` internally.

Previously, the `Collection.init` (and methods using it, e.g.
`loadItems`) were using custom implementation, which in some cases
resulting in different results as opposed to `em.populate()`. With this
PR, the `init` method is also using `em.populate`, yielding the same
results as if you would populate the relation.

Closes #4464
B4nan added a commit that referenced this issue Sep 30, 2023
…4571)

This unifies the population mechanism for to-many relations by using the
`em.populate()` internally.

Previously, the `Collection.init` (and methods using it, e.g.
`loadItems`) were using custom implementation, which in some cases
resulting in different results as opposed to `em.populate()`. With this
PR, the `init` method is also using `em.populate`, yielding the same
results as if you would populate the relation.

Closes #4464
B4nan added a commit that referenced this issue Oct 2, 2023
…4571)

This unifies the population mechanism for to-many relations by using the
`em.populate()` internally.

Previously, the `Collection.init` (and methods using it, e.g.
`loadItems`) were using custom implementation, which in some cases
resulting in different results as opposed to `em.populate()`. With this
PR, the `init` method is also using `em.populate`, yielding the same
results as if you would populate the relation.

Closes #4464
B4nan added a commit that referenced this issue Oct 17, 2023
…4571)

This unifies the population mechanism for to-many relations by using the
`em.populate()` internally.

Previously, the `Collection.init` (and methods using it, e.g.
`loadItems`) were using custom implementation, which in some cases
resulting in different results as opposed to `em.populate()`. With this
PR, the `init` method is also using `em.populate`, yielding the same
results as if you would populate the relation.

Closes #4464
B4nan added a commit that referenced this issue Oct 21, 2023
…4571)

This unifies the population mechanism for to-many relations by using the
`em.populate()` internally.

Previously, the `Collection.init` (and methods using it, e.g.
`loadItems`) were using custom implementation, which in some cases
resulting in different results as opposed to `em.populate()`. With this
PR, the `init` method is also using `em.populate`, yielding the same
results as if you would populate the relation.

Closes #4464
B4nan added a commit that referenced this issue Oct 25, 2023
…4571)

This unifies the population mechanism for to-many relations by using the
`em.populate()` internally.

Previously, the `Collection.init` (and methods using it, e.g.
`loadItems`) were using custom implementation, which in some cases
resulting in different results as opposed to `em.populate()`. With this
PR, the `init` method is also using `em.populate`, yielding the same
results as if you would populate the relation.

Closes #4464
B4nan added a commit that referenced this issue Nov 2, 2023
…4571)

This unifies the population mechanism for to-many relations by using the
`em.populate()` internally.

Previously, the `Collection.init` (and methods using it, e.g.
`loadItems`) were using custom implementation, which in some cases
resulting in different results as opposed to `em.populate()`. With this
PR, the `init` method is also using `em.populate`, yielding the same
results as if you would populate the relation.

Closes #4464
B4nan added a commit that referenced this issue Nov 5, 2023
…4571)

This unifies the population mechanism for to-many relations by using the
`em.populate()` internally.

Previously, the `Collection.init` (and methods using it, e.g.
`loadItems`) were using custom implementation, which in some cases
resulting in different results as opposed to `em.populate()`. With this
PR, the `init` method is also using `em.populate`, yielding the same
results as if you would populate the relation.

Closes #4464
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

No branches or pull requests

2 participants