Skip to content

Fix EditLevelMismatch when cloning object graphs during edit sessions#4821

Merged
rockfordlhotka merged 5 commits intomainfrom
copilot/fix-edit-level-mismatch
Feb 14, 2026
Merged

Fix EditLevelMismatch when cloning object graphs during edit sessions#4821
rockfordlhotka merged 5 commits intomainfrom
copilot/fix-edit-level-mismatch

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Feb 14, 2026

Description

Ports fix from v9.x branch (PR #4796). Cloning object graphs with children during active edit sessions threw EditLevelMismatch exceptions because InsertItem called ResetChildEditLevel during deserialization, forcing child edit levels to 0 despite being correctly deserialized with their original edit levels.

Changes

  • BusinessBindingListBase.cs & BusinessListBase.cs

    • Added _isDeserializing flag (NonSerialized, NotUndoable)
    • Set flag in OnSetChildren during deserialization (try-finally block)
    • Skip ResetChildEditLevel and EditLevelAdded assignment in InsertItem when deserializing
  • BusinessListBaseTests.cs

    • Added 6 tests covering clone+edit scenarios: basic edit level preservation, CancelEdit, ApplyEdit, multi-level undo, children added during edit, and IEditableObject binding

Example

var root = CreateRoot();
var child = root.Children.AddNew();
child.Data = "original";

root.BeginEdit();
child.Data = "modified";

var clonedRoot = root.Clone();  // Previously threw EditLevelMismatch

// Now works: edit levels preserved, undo/redo functional
clonedRoot.CancelEdit();  // Reverts to "original"

Enables clone-then-edit workflows where users inspect/modify clones in separate views while maintaining undo capability on both original and clone.

Original prompt

This section details on the original issue you should resolve

<issue_title>EditLevelMismatch when cloning object</issue_title>
<issue_description>Describe the bug
When cloning an object graph that contains children, the edit level of the children is not correctly preserved.
Example scenario is Invoice with Cost line items. There is a BindingSource for the invoice and one for the child cost list.
Creating a clone and showing a new form with that cloned object triggers an EditLevelMismatch exception.

Version and Platform
CSLA version: 9.1.0
OS: Windows
Platform: WinForms

Code that Fails
I created a simple demo project to showcase the issue here: https://github.com/xal1983/CslaEditLevelMismatch/blob/master/EditLevelMismatch/Form1.cs

But the gist of it is, the child cost and it's field manager have an EditLevel = 1 at the time of cloning the invoice.
The cloned cost however ends up with EditLevel = 0 but it's FieldManager has an EditLevel = 1.
When setting the binding source, the binding source will automaticall call IEditableObject.BeginEdit() on the cost and Csla will raise the exception because the FieldManager and the Cost's EditLevels do not match.

Stack Trace or Exception Detail
at Csla.Core.FieldManager.FieldDataManager.Csla.Core.IUndoableObject.CopyState(Int32 parentEditLevel, Boolean parentBindingEdit) in //Source/Csla/Core/FieldManager/FieldDataManager.cs:line 512
at Csla.Core.UndoableBase.CopyState(Int32 parentEditLevel) in /
/Source/Csla/Core/UndoableBase.cs:line 167
at Csla.Core.BusinessBase.System.ComponentModel.IEditableObject.BeginEdit() in /_/Source/Csla/Core/BusinessBase.cs:line 778
at System.Windows.Forms.CurrencyManager.OnCurrentChanged(EventArgs e)
at System.Windows.Forms.CurrencyManager.ChangeRecordState(Int32 newPosition, Boolean validating, Boolean endCurrentEdit, Boolean firePositionChange, Boolean pullData)
at System.Windows.Forms.CurrencyManager.List_ListChanged(Object sender, ListChangedEventArgs e)
at System.Windows.Forms.BindingSource.OnListChanged(ListChangedEventArgs e)
at System.Windows.Forms.BindingSource.ResetBindings(Boolean metadataChanged)
at System.Windows.Forms.BindingSource.SetList(IList list, Boolean metaDataChanged, Boolean applySortAndFilter)
at System.Windows.Forms.BindingSource.ResetList()
at System.Windows.Forms.BindingSource.ParentCurrencyManager_MetaDataChanged(Object sender, EventArgs e)
at System.Windows.Forms.CurrencyManager.OnMetaDataChanged(EventArgs e)
at System.Windows.Forms.CurrencyManager.List_ListChanged(Object sender, ListChangedEventArgs e)
at System.Windows.Forms.BindingSource.OnListChanged(ListChangedEventArgs e)
at System.Windows.Forms.BindingSource.ResetBindings(Boolean metadataChanged)
at System.Windows.Forms.BindingSource.SetList(IList list, Boolean metaDataChanged, Boolean applySortAndFilter)
at System.Windows.Forms.BindingSource.ResetList()
at System.Windows.Forms.BindingSource.set_DataSource(Object value)
at EditLevelMismatch.Form1.SetDataSource(Invoice i) in E:\Devel\Tests\EditLevelMismatch\EditLevelMismatch\Form1.cs:line 27
at EditLevelMismatch.Form1.button1_Click(Object sender, EventArgs e) in E:\Devel\Tests\EditLevelMismatch\EditLevelMismatch\Form1.cs:line 46

Additional context
So, you may suggest "Call EndEdit or CancelEdit before cloning". however, this is precisely what I can't do. The use case is:
-User sees a list of invoices with the editor
-User start editing an invoice
-User needs to look up information from another invoice while editing the invoice
-At this point the app creates a clone of the in progress edit of the invoice and shows a popup with just the editor for this invoice, any edits being made so far need to be preserved, but the user needs to be able to cancel if they want to discard changes.
</issue_description>

<agent_instructions>I fixed this in the v9.x branch already. The goal now is to take those same fixes and apply them to the main branch.</agent_instructions>

Comments on the Issue (you are @copilot in this section)

@rockfordlhotka @xal1983 I'm not 100% sure this is a supported scenario.

You are in the middle of editing an object graph, so various edit levels within the graph could be all over the place. And you clone the graph, so those edit levels could stay the same - but unless you can figure out the exact edit states of the whole graph how would you bind the clone to a UI? Or do anything else with it in such an indeterminate state?

@xal1983 Hi @rockfordlhotka, I'm not sure why this wouldn't be supported, this app has been relying on this behavior for 20 years (migrating from Csla 4 to 9 atm), it's possible it works because when the BeginEdit is called from a BindingSource it only ever gets to level 1 because of the special handling for System.ComponentModel.IEditableObject. Regardless, the fact is ...

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Copilot AI and others added 3 commits February 14, 2026 18:59
Co-authored-by: rockfordlhotka <2333134+rockfordlhotka@users.noreply.github.com>
Co-authored-by: rockfordlhotka <2333134+rockfordlhotka@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix EditLevelMismatch when cloning object graph Fix EditLevelMismatch when cloning object graphs during edit sessions Feb 14, 2026
Copilot AI requested a review from rockfordlhotka February 14, 2026 19:07
@rockfordlhotka rockfordlhotka merged commit 7b53f5a into main Feb 14, 2026
2 checks passed
@rockfordlhotka rockfordlhotka deleted the copilot/fix-edit-level-mismatch branch February 14, 2026 23:51
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.

EditLevelMismatch when cloning object

2 participants