Removing multiple entities does not invalidate Identity Map #5772

Closed
opened 2026-01-22 15:17:24 +01:00 by admin · 8 comments
Owner

Originally created by @tijsverkoyen on GitHub (Nov 14, 2017).

I have a weird problem, I do not know if I am handling this the correct way.

I have a method: existsComment, with the following code:

public function existsComment(int $id): bool
{
    return $this->repository->find($id) instanceof Comment;
}

I have a record in my MySQL database with id=1. When I call existsComment(1) it returns true. So far everything is ok.

Next I delete the same entity with a custom method in my repository, see the code below:

public function deleteMultipleById(array $ids): void
{
    $builder = $this->createQueryBuilder('c');
    $builder->delete()
                    ->where('c.id IN(:ids)')
                    ->setParameter(
                        ':ids',
                        $ids,
                        Connection::PARAM_INT_ARRAY
                    );

    $query = $builder->getQuery();
    $query->execute();
}

The reason why I want to delete multiple comments at once is only for performance reason. In some case there can be 10000 comments that are spam, and we want to enable the user to delete them at once. If I use the ->remove() method on each Entity, it will result in 10000 queries. A single DELETE query will be more performant.

But if I call the existsComment again, it results in true.

If I look in the database the comments are all removed, so it probably exists only in the Doctrine cache.

If I inspect the UnitOfWork, and check the state of a removed Entity it is 1 (STATE_MANAGED), where I expect 4 (STATE_REMOVED).

So, my question is: what am I doing wrong? Is this intended behaviour? And if it is intended, what is the best way around this?

Originally created by @tijsverkoyen on GitHub (Nov 14, 2017). I have a weird problem, I do not know if I am handling this the correct way. I have a method: existsComment, with the following code: public function existsComment(int $id): bool { return $this->repository->find($id) instanceof Comment; } I have a record in my MySQL database with id=1. When I call `existsComment(1)` it returns true. So far everything is ok. Next I delete the same entity with a custom method in my repository, see the code below: public function deleteMultipleById(array $ids): void { $builder = $this->createQueryBuilder('c'); $builder->delete() ->where('c.id IN(:ids)') ->setParameter( ':ids', $ids, Connection::PARAM_INT_ARRAY ); $query = $builder->getQuery(); $query->execute(); } The reason why I want to delete multiple comments at once is only for performance reason. In some case there can be 10000 comments that are spam, and we want to enable the user to delete them at once. If I use the `->remove()` method on each Entity, it will result in 10000 queries. A single DELETE query will be more performant. But if I call the `existsComment` again, it results in `true`. If I look in the database the comments are all removed, so it probably exists only in the Doctrine cache. If I inspect the UnitOfWork, and check the state of a removed Entity it is 1 (STATE_MANAGED), where I expect 4 (STATE_REMOVED). So, my question is: what am I doing wrong? Is this intended behaviour? And if it is intended, what is the best way around this?
admin closed this issue 2026-01-22 15:17:24 +01:00
Author
Owner

@fesor commented on GitHub (Nov 14, 2017):

Title is miss-leading. Identity map (which is part of unit of work) is not a cache, it is part of current state of unit of work. If you load something into UoW, then make something with it bypassing it, state will not be changed.

If I inspect the UnitOfWork, and check the state of a removed Entity it is 1 (STATE_MANAGED), where I expect 4 (STATE_REMOVED).

Since you are deleting your entities bypassing UoW, it has no information about it. So yes, this is intended behaviour.

what is the best way around this?

This depends on context. First of all, why you need to load all this comments into UoW in the first place? If you are going to delete it via DELETE, then you could collect needed ids also via DBAL (DQL). In this case no entities will be loaded into UoW.

Also if you only need to check that record presents in database, it will be much cheaper (from performance point of view) to add another custom query:

public function exists(int $id): bool
{
    return $this->createQueryBuilder('c')
            ->select('1')
            ->where('id = :id')
            ->setParameter('id', $id)
            ->getQuery()
            ->getOneOrNullResult(Query::HYDRATE_SINGLE_SCALAR) !== null;
}

This will not load any data into UoW.

@fesor commented on GitHub (Nov 14, 2017): Title is miss-leading. Identity map (which is part of unit of work) is not a cache, it is part of current state of unit of work. If you load something into UoW, then make something with it bypassing it, state will not be changed. > If I inspect the UnitOfWork, and check the state of a removed Entity it is 1 (STATE_MANAGED), where I expect 4 (STATE_REMOVED). Since you are deleting your entities bypassing UoW, it has no information about it. So yes, this is intended behaviour. > what is the best way around this? This depends on context. First of all, why you need to load all this comments into UoW in the first place? If you are going to delete it via DELETE, then you could collect needed ids also via DBAL (DQL). In this case no entities will be loaded into UoW. Also if you only need to check that record presents in database, it will be much cheaper (from performance point of view) to add another custom query: ``` public function exists(int $id): bool { return $this->createQueryBuilder('c') ->select('1') ->where('id = :id') ->setParameter('id', $id) ->getQuery() ->getOneOrNullResult(Query::HYDRATE_SINGLE_SCALAR) !== null; } ``` This will not load any data into UoW.
Author
Owner

@tijsverkoyen commented on GitHub (Nov 15, 2017):

The context: I discovered this while writing some tests. There I check if the entity exists (with the existsComment method), then I delete the comment (with deleteMultipleById). And I expected the entity to be deleted.

I really don't want to use get all the entities and delete them in a loop with the ->remove() method, from a performance point of view.

So am I correct, if I state that there is no way to combine the use of UnitOfWork with custom queries thru the QueryBuilder? Or is there a way to "clear" the UnitOfWork?

PS: I changed the title, I'm not that familiar with all Doctrine vocabulary.

@tijsverkoyen commented on GitHub (Nov 15, 2017): The context: I discovered this while writing some tests. There I check if the entity exists (with the `existsComment` method), then I delete the comment (with `deleteMultipleById`). And I expected the entity to be deleted. I really don't want to use get all the entities and delete them in a loop with the `->remove()` method, from a performance point of view. So am I correct, if I state that there is no way to combine the use of UnitOfWork with custom queries thru the QueryBuilder? Or is there a way to "clear" the UnitOfWork? PS: I changed the title, I'm not that familiar with all Doctrine vocabulary.
Author
Owner

@alcaeus commented on GitHub (Nov 15, 2017):

You can either loop through all entities and detach them from the EntityManager or call clear which will either clear all entities or only those of the class you've passed. Either way, clear is a rather blunt tool you should be careful with.

@alcaeus commented on GitHub (Nov 15, 2017): You can either loop through all entities and `detach` them from the EntityManager or call `clear` which will either clear all entities or only those of the class you've passed. Either way, `clear` is a rather blunt tool you should be careful with.
Author
Owner

@tijsverkoyen commented on GitHub (Nov 15, 2017):

And what happens if you update an entity with the query builder? Will the Identity Map be updated? And if not, how could you work around that?

@tijsverkoyen commented on GitHub (Nov 15, 2017): And what happens if you update an entity with the query builder? Will the Identity Map be updated? And if not, how could you work around that?
Author
Owner

@Ocramius commented on GitHub (Nov 15, 2017):

It will not be updated: clearing the UnitOfWork is the safest approach
there.

On 15 Nov 2017 10:22, "Tijs Verkoyen" notifications@github.com wrote:

And what happens if you update an entity with the query builder? Will the
Identity Map be updated? And if not, how could you work around that?


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/doctrine/doctrine2/issues/6827#issuecomment-344518965,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAJakDUi1iGIOHGROP829AxBaqXShiRmks5s2p8ogaJpZM4Qdr9O
.

@Ocramius commented on GitHub (Nov 15, 2017): It will not be updated: clearing the UnitOfWork is the safest approach there. On 15 Nov 2017 10:22, "Tijs Verkoyen" <notifications@github.com> wrote: > And what happens if you update an entity with the query builder? Will the > Identity Map be updated? And if not, how could you work around that? > > — > You are receiving this because you are subscribed to this thread. > Reply to this email directly, view it on GitHub > <https://github.com/doctrine/doctrine2/issues/6827#issuecomment-344518965>, > or mute the thread > <https://github.com/notifications/unsubscribe-auth/AAJakDUi1iGIOHGROP829AxBaqXShiRmks5s2p8ogaJpZM4Qdr9O> > . >
Author
Owner

@tijsverkoyen commented on GitHub (Nov 15, 2017):

So clearing the full UnitOfWork instead of detaching the entities. I guess it will be refetched from the database when it is requested again?

@tijsverkoyen commented on GitHub (Nov 15, 2017): So clearing the full UnitOfWork instead of detaching the entities. I guess it will be refetched from the database when it is requested again?
Author
Owner

@Ocramius commented on GitHub (Nov 15, 2017):

Correct

Marco Pivetta

http://twitter.com/Ocramius

http://ocramius.github.com/

On Wed, Nov 15, 2017 at 9:29 AM, Tijs Verkoyen notifications@github.com
wrote:

So clearing the full UnitOfWork instead of detaching the entities. I guess
it will be refetched from the database when it is requested again?


You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/doctrine/doctrine2/issues/6827#issuecomment-344520609,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AAJakJTbyyvnH3nof7JpBX5kE0vcnnvWks5s2qDggaJpZM4Qdr9O
.

@Ocramius commented on GitHub (Nov 15, 2017): Correct Marco Pivetta http://twitter.com/Ocramius http://ocramius.github.com/ On Wed, Nov 15, 2017 at 9:29 AM, Tijs Verkoyen <notifications@github.com> wrote: > So clearing the full UnitOfWork instead of detaching the entities. I guess > it will be refetched from the database when it is requested again? > > — > You are receiving this because you commented. > Reply to this email directly, view it on GitHub > <https://github.com/doctrine/doctrine2/issues/6827#issuecomment-344520609>, > or mute the thread > <https://github.com/notifications/unsubscribe-auth/AAJakJTbyyvnH3nof7JpBX5kE0vcnnvWks5s2qDggaJpZM4Qdr9O> > . >
Author
Owner

@tijsverkoyen commented on GitHub (Nov 15, 2017):

Thx guys for helping a noob ;-)

@tijsverkoyen commented on GitHub (Nov 15, 2017): Thx guys for helping a noob ;-)
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: doctrine/archived-orm#5772