@@ -327,7 +327,7 @@ def __init__(self, items=None, prepare_entities_deletes: bool = False):
327327 if items is None :
328328 items = []
329329 self .__deleted_entities = set (map (RefEq , items ))
330- self .__prepare_entities_deleted = prepare_entities_deletes
330+ self ._prepare_entities_deleted = prepare_entities_deletes
331331 self .__on_before_deleted_entities : Union [set , None ] = None
332332
333333 def __getattribute__ (self , item ):
@@ -339,7 +339,7 @@ def __getattribute__(self, item):
339339 "discard" ,
340340 "DeletedEntitiesEnumeratorResult" ,
341341 "_DeletedEntitiesHolder__deleted_entities" ,
342- "_DeletedEntitiesHolder__prepare_entities_deleted " ,
342+ "_prepare_entities_deleted " ,
343343 "_DeletedEntitiesHolder__on_before_deleted_entities" ,
344344 ]:
345345 return super ().__getattribute__ (item )
@@ -358,19 +358,17 @@ def __len__(self) -> int:
358358 )
359359
360360 def __iter__ (self ):
361- deleted_transformed_iterator = (
362- self .DeletedEntitiesEnumeratorResult (item .ref , True ) for item in self .__deleted_entities
363- )
364- if self .__on_before_deleted_entities is None :
365- return deleted_transformed_iterator
366-
367- on_before_deleted_iterator = (
368- self .DeletedEntitiesEnumeratorResult (item .ref , False ) for item in self .__on_before_deleted_entities
369- )
370- return itertools .chain (deleted_transformed_iterator , on_before_deleted_iterator )
361+ # Snapshot the main set so that cascade deletes registered by BeforeDelete
362+ # handlers do not raise "Set changed size during iteration".
363+ yield from (self .DeletedEntitiesEnumeratorResult (item .ref , True ) for item in list (self .__deleted_entities ))
364+ if self .__on_before_deleted_entities :
365+ yield from (
366+ self .DeletedEntitiesEnumeratorResult (item .ref , False )
367+ for item in list (self .__on_before_deleted_entities )
368+ )
371369
372370 def add (self , element : object ) -> None :
373- if self .__prepare_entities_deleted :
371+ if self ._prepare_entities_deleted :
374372 if self .__on_before_deleted_entities is None :
375373 self .__on_before_deleted_entities = set ()
376374 self .__on_before_deleted_entities .add (RefEq (element ))
@@ -393,7 +391,7 @@ def clear(self) -> None:
393391 self .__on_before_deleted_entities .clear ()
394392
395393 def evict (self , entity ) -> None :
396- if self .__prepare_entities_deleted :
394+ if self ._prepare_entities_deleted :
397395 raise RuntimeError ("Cannot evict entity during OnBeforeDelete" )
398396 self .__deleted_entities .discard (RefEq (entity ))
399397
@@ -1034,42 +1032,52 @@ def __prepare_for_creating_revisions_from_ids(self, result: SaveChangesData) ->
10341032 def __prepare_for_entities_deletion (
10351033 self , result : Union [None , SaveChangesData ], changes : Union [None , Dict [str , List [DocumentsChanges ]]]
10361034 ) -> None :
1037- for deleted_entity in self ._deleted_entities :
1038- document_info = self ._documents_by_entity .get (deleted_entity .entity )
1039- if document_info is None :
1040- continue
1041- if changes is not None :
1042- doc_changes = []
1043- change = DocumentsChanges ("" , "" , DocumentsChanges .ChangeType .DOCUMENT_DELETED )
1044- doc_changes .append (change )
1045- changes [document_info .key ] = doc_changes
1046- else :
1047- command = result .deferred_commands_map .get (
1048- IdTypeAndName .create (document_info .key , CommandType .CLIENT_ANY_COMMAND , None )
1049- )
1050- if command :
1051- self .__throw_invalid_deleted_document_with_deferred_command (command )
1035+ """Build delete commands for all entities in the deleted-entities set.
1036+
1037+ While iterating, BeforeDelete event handlers may call session.delete(),
1038+ which stages new deletions for a second pass via DeletedEntitiesHolder.
1039+ """
1040+ self ._deleted_entities ._prepare_entities_deleted = True
1041+ try :
1042+ for deleted_entity in self ._deleted_entities :
1043+ document_info = self ._documents_by_entity .get (deleted_entity .entity )
1044+ if document_info is None :
1045+ continue
1046+ if changes is not None :
1047+ doc_changes = []
1048+ change = DocumentsChanges ("" , "" , DocumentsChanges .ChangeType .DOCUMENT_DELETED )
1049+ doc_changes .append (change )
1050+ changes [document_info .key ] = doc_changes
1051+ else :
1052+ command = result .deferred_commands_map .get (
1053+ IdTypeAndName .create (document_info .key , CommandType .CLIENT_ANY_COMMAND , None )
1054+ )
1055+ if command :
1056+ self .__throw_invalid_deleted_document_with_deferred_command (command )
10521057
1053- change_vector = None
1054- document_info = self ._documents_by_id .get (document_info .key )
1058+ change_vector = None
1059+ document_info = self ._documents_by_id .get (document_info .key )
10551060
1056- if document_info :
1057- change_vector = document_info .change_vector
1061+ if document_info :
1062+ change_vector = document_info .change_vector
10581063
1059- if document_info .entity is not None :
1060- result .on_success .remove_document_by_entity (document_info .entity )
1061- result .entities .append (document_info .entity )
1064+ if document_info .entity is not None :
1065+ result .on_success .remove_document_by_entity (document_info .entity )
1066+ result .entities .append (document_info .entity )
10621067
1063- result .on_success .remove_document_by_id (document_info .key )
1068+ result .on_success .remove_document_by_id (document_info .key )
10641069
1065- change_vector = change_vector if self ._use_optimistic_concurrency else None
1066- self .before_delete_invoke (BeforeDeleteEventArgs (self , document_info .key , document_info .entity ))
1067- result .session_commands .append (
1068- DeleteCommandData (document_info .key , change_vector , document_info .change_vector )
1069- )
1070+ change_vector = change_vector if self ._use_optimistic_concurrency else None
1071+ if deleted_entity .execute_on_before_delete :
1072+ self .before_delete_invoke (BeforeDeleteEventArgs (self , document_info .key , document_info .entity ))
1073+ result .session_commands .append (
1074+ DeleteCommandData (document_info .key , change_vector , document_info .change_vector )
1075+ )
10701076
1071- if changes is None :
1072- result .on_success .clear_deleted_entities ()
1077+ if changes is None :
1078+ result .on_success .clear_deleted_entities ()
1079+ finally :
1080+ self ._deleted_entities ._prepare_entities_deleted = False
10731081
10741082 def __prepare_for_entities_puts (self , result : SaveChangesData ) -> None :
10751083 should_ignore_entity_changes = self .conventions .should_ignore_entity_changes
0 commit comments