Skip to content

Implement BusinessDocumentBase combining BusinessBase and BusinessListBase#4815

Open
rockfordlhotka wants to merge 8 commits intomainfrom
rocky/1830-bb-blb
Open

Implement BusinessDocumentBase combining BusinessBase and BusinessListBase#4815
rockfordlhotka wants to merge 8 commits intomainfrom
rocky/1830-bb-blb

Conversation

@rockfordlhotka
Copy link
Member

@rockfordlhotka rockfordlhotka commented Feb 5, 2026

Summary

  • Implements BusinessDocumentBase<T,C>, a new base class that inherits from BusinessBase<T> and adds full BusinessListBase<T,C> collection capabilities
  • Enables the "document pattern" where a single business object has its own managed properties, validation rules, and authorization AND contains a collection of child items
  • Adds IBusinessDocumentBase<C> consolidated interface

Key Features

  • Full IList<C> collection support via composition (MobileList<C>)
  • Deleted child tracking (IContainsDeletedList)
  • N-level undo cascading to collection children
  • IsDirty/IsValid/IsBusy aggregation across properties and children
  • MobileFormatter serialization of collection items
  • CollectionChanged/PropertyChanged events
  • LoadListMode for bulk loading without events
  • Child data access (Child_Update/Child_UpdateAsync)
  • RegisterProperty / RegisterMethod with correct type resolution

Test plan

  • 29 unit tests covering:
    • Create and fetch operations
    • Collection operations (Add, Insert, Remove, RemoveAt, Clear, Contains, IndexOf, Enumerator, Indexer)
    • Status aggregation (IsDirty, IsValid, IsNew across properties and children)
    • CollectionChanged events
    • Clone / serialization preservation
    • N-level undo (BeginEdit/CancelEdit/ApplyEdit for properties and children)

Closes #1830

🤖 Generated with Claude Code

Document the design for a new class that combines BusinessBase and
BusinessListBase capabilities using composition. This enables the
"Document Pattern" where a business object has its own properties
AND contains a collection of child items.

The plan includes:
- Complete interface analysis from both base classes
- Conflict resolution strategies for shared interfaces
- 10-phase implementation plan with specific tasks
- Risk assessment and success criteria

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@hurcane
Copy link

hurcane commented Feb 5, 2026

I looked for a related discussion or issue for this PR, but didn't find one. What is the intended utility for this kind of class?

Looking at this initial plan, I wonder how more complex documents would be handled. By more complex, I mean having multiple child lists. In the invoice example, the editable Invoice class in my project would also have a comments collection, a contacts collection. Thinking across the full project, I don't have any documents with only a single collection.

@StefanOssendorf
Copy link
Contributor

You are describing a business (base) object. I think the purpose of this PR is to get the whole rules stuff etc. into a business list class.
For example you have a PositionList which has rules to enforce (e.g. only one item with Type X is allowed) and so on. This kind of rule is currently not possible because the list itself can't define rules.

@hurcane
Copy link

hurcane commented Feb 5, 2026

I get that use case. Those business rules currently have to be handled in the parent class. We typically use properties on the child classes to indicate their error state. For example, the child will have an IsDuplicate property with a rule that ensures the property is false.

I'm all for simplifying this scenario, but it wasn't clear in the description and the generated plan. Sorry for butting in without understanding the full context.

@rockfordlhotka
Copy link
Member Author

I get that use case. Those business rules currently have to be handled in the parent class. We typically use properties on the child classes to indicate their error state. For example, the child will have an IsDuplicate property with a rule that ensures the property is false.

I'm all for simplifying this scenario, but it wasn't clear in the description and the generated plan. Sorry for butting in without understanding the full context.

#1830

…BusinessListBase

Adds BusinessDocumentBase<T,C>, a new base class that inherits from BusinessBase<T>
and adds full BusinessListBase<T,C> collection capabilities. This enables the
"document pattern" where a single business object has its own managed properties,
validation rules, and authorization AND contains a collection of child items.

Key features:
- Full IList<C> collection support via composition (MobileList<C>)
- Deleted child tracking (IContainsDeletedList)
- N-level undo cascading to collection children
- IsDirty/IsValid/IsBusy aggregation across properties and children
- MobileFormatter serialization of collection items
- CollectionChanged/PropertyChanged events
- LoadListMode for bulk loading without events
- Child data access (Child_Update/Child_UpdateAsync)

Includes IBusinessDocumentBase<C> interface and 29 unit tests covering
create/fetch, collection operations, status aggregation, events, clone,
and n-level undo.

Implements #1830

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rockfordlhotka rockfordlhotka changed the title Add BusinessDocumentBase implementation plan Implement BusinessDocumentBase combining BusinessBase and BusinessListBase Feb 6, 2026
rockfordlhotka and others added 2 commits February 17, 2026 20:04
- Add 30 new tests covering: NotUndoable, AddNew/Async, advanced undo
  (nested BeginEdit/ApplyEdit, deleted list tracking), equality, event
  suppression, save workflow, advanced clone/undo state, WaitForIdle,
  and serialization roundtrip parent reference preservation
- Add metastate PropertyChanged event tests (Xaml mode, SkipOnCIServer)
  mirroring BasicModernTests: MakeOld, MarkDeleted, property/child changes
- Expose public API gaps: AddNew(), AddNewAsync(), SuppressListChangedEvents,
  RaiseListChangedEvents (public read)
- Fix InsertItem/RemoveItem to call OnChildChanged so IsDirty/IsValid/
  IsSavable PropertyChanged fires when collection items are added/removed
- Add test infrastructure: NotUndoableData, MakeOld(), DataPortal_DeleteSelf,
  DeletedCount on TestDocument; AsyncRuleText+rule on DocumentLineItem;
  MetastateDocument and MetastateLineItem for metastate tests
- Delete BusinessDocumentBase-PLAN.md (planning artifact, not for shipping)

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements BusinessDocumentBase<T,C>, a new base class that combines the capabilities of BusinessBase<T> and BusinessListBase<T,C> to support the "document pattern" where a single business object has both its own managed properties and a collection of child items. This addresses issue #1830 regarding adding rules engine support to collection-based types.

Changes:

  • New BusinessDocumentBase<T,C> class providing full BusinessBase property management and BusinessListBase collection capabilities
  • New IBusinessDocumentBase<C> interface consolidating BusinessBase and BusinessListBase contracts
  • Comprehensive test suite with 29 unit tests covering CRUD operations, status aggregation, N-level undo, serialization, and metastate events

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
Source/Csla/BusinessDocumentBase.cs Main implementation combining BusinessBase inheritance with IList collection management, deleted child tracking, undo cascading, and child data access
Source/Csla/IBusinessDocumentBase.cs Interface definition consolidating IBusinessBase and IBusinessListBase
Source/tests/Csla.test/BusinessDocumentBase/BusinessDocumentBaseTests.cs Comprehensive test suite (29 tests) covering collection operations, status aggregation, undo, serialization, and async behavior
Source/tests/Csla.test/BusinessDocumentBase/BusinessDocumentBaseMetastateTests.cs Tests for PropertyChanged metastate events (IsDirty, IsValid, etc.) when properties or children change
Source/tests/Csla.test/BusinessDocumentBase/TestDocument.cs Test document class combining properties (DocumentNumber, DocumentDate) with child collection of DocumentLineItems
Source/tests/Csla.test/BusinessDocumentBase/DocumentLineItem.cs Child business object for testing with Description, Amount properties and async rule support
Source/tests/Csla.test/BusinessDocumentBase/MetastateDocument.cs Document class for metastate event testing with validation rules ([Required] on Name property)
Source/tests/Csla.test/BusinessDocumentBase/MetastateLineItem.cs Minimal child object for metastate document tests

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

rockfordlhotka and others added 2 commits February 17, 2026 21:24
- SetItem: add IsChild guard (throws InvalidOperationException for
  non-child items) to match InsertItem's enforcement of the child
  constraint; previously the indexer setter accepted any object
- RegisterProperty: add four missing lambda-expression overloads
  (relationship, friendlyName, friendlyName+default,
  friendlyName+default+relationship) to match the full set present
  on BusinessBase

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Adds IndexerSet_NonChild_ThrowsInvalidOperationException to verify
that doc[i] = nonChild throws InvalidOperationException, mirroring
the existing InsertNonChild test and covering the SetItem fix from
the Copilot review.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Copy link
Contributor

@StefanOssendorf StefanOssendorf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I skipped the tests for now until the implementation is discussed.

{
/// <summary>
/// Base class for an editable business object that has its own
/// properties AND contains a collection of child items.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

contains or is a collection of child items?
For me it should be an is?

IList<C>,
IBusinessDocumentBase<C>
where T : BusinessDocumentBase<T, C>
where C : IEditableBusinessObject
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we add the notnull constraint here to make clear a
FooDocuments<FooDocuments, FooDocument?> isn't allowed?


#region IContainsDeletedList

IEnumerable<IEditableBusinessObject> IContainsDeletedList.DeletedList
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we want to be memory efficient here we could to:
(IEnumerable<IEditableBusinessObject>)(_deletedList ?? Enumerable.Empty<IEditableBusinessObject>());

/// <param name="item">Child object to check.</param>
/// <exception cref="ArgumentNullException"><paramref name="item"/> is <see langword="null"/>.</exception>
[EditorBrowsable(EditorBrowsableState.Advanced)]
public bool ContainsDeleted(C item)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be public or better be protected and the user should implement functionality to check that? 🤔

DeletedList.Add(child);
}

private void UnDeleteChild(C child)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't there missing a UnDeleteChild or the like? I just tested this and Deleting+Readding still deletes the item on the server side ^^'

/// <param name="index">Index of the item to insert.</param>
/// <param name="item">Item to insert.</param>
/// <exception cref="ArgumentNullException"><paramref name="item"/> is <see langword="null"/>.</exception>
protected virtual void InsertItem(int index, C item)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null guard missing?

/// <param name="index">The zero-based index of the item to replace.</param>
/// <param name="item">The new value for the item at the specified index.</param>
/// <exception cref="ArgumentNullException"><paramref name="item"/> is <see langword="null"/>.</exception>
protected virtual void SetItem(int index, C item)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null guard missing?


void IEditableCollection.SetParent(IParent? parent)
{
SetParent(parent!);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ! is wrong here. We have to handle the IParent?.

/// <exception cref="ArgumentNullException"><paramref name="propertyName"/> is <see langword="null"/>.</exception>
protected static new PropertyInfo<P> RegisterProperty<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] P>(string propertyName)
{
if (propertyName is null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

string.IsNullOrWhiteSpace should here be used. An empty property is also invalid.

/// <param name="info">PropertyInfo object for the property.</param>
/// <returns>The provided IPropertyInfo object.</returns>
/// <exception cref="ArgumentNullException"><paramref name="info"/> is <see langword="null"/>.</exception>
protected static new PropertyInfo<P> RegisterProperty<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.All)] P>(PropertyInfo<P> info)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Idea: Should we still support the "old way" registrations or skip them in newer types and only use the modern version/art?

- Fix doc comment: "contains" → "is" a collection
- Add notnull constraint to C type parameter
- Use Enumerable.Empty for IContainsDeletedList to avoid lazy allocation
- Add UnDeleteChild logic in InsertItem for re-added deleted children
- Add null guards on public collection methods (Add, Insert, Remove, Contains, IndexOf, CopyTo)
- Remove null-forgiving operator on IEditableCollection.SetParent
- Use string.IsNullOrWhiteSpace for RegisterProperty/RegisterMethod name validation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@rockfordlhotka
Copy link
Member Author

Addressed the review feedback from @StefanOssendorf and Copilot:

  • Doc comment (:26): Changed "contains" → "is" a collection of child items
  • notnull constraint (:44): Added where C : notnull, IEditableBusinessObject to prevent nullable child types
  • Memory efficiency (:76): IContainsDeletedList.DeletedList now uses _deletedList ?? Enumerable.Empty<>() to avoid lazy allocation of the deleted list
  • UnDeleteChild on re-add (:108): InsertItem now checks the deleted list and calls UnDeleteChild if the item is being re-added, fixing the bug where delete + re-add still deleted on the server
  • Null guards (:208-353): Added ArgumentNullException checks on Add, Insert, Remove, Contains, IndexOf, and CopyTo
  • IParent? null-forgiving (:526): Removed the ! operator — SetParent already accepts IParent?
  • IsNullOrWhiteSpace (:926): All RegisterProperty(string) and RegisterMethod(string) overloads now use string.IsNullOrWhiteSpace with ArgumentException

Not changed:

  • ContainsDeleted visibility (:86): Kept public to match BusinessListBase convention
  • Legacy RegisterProperty support (:909): Left as-is — this is a design decision for broader discussion
  • SetItem IsChild check (Copilot :388): Already fixed in a prior commit
  • RegisterProperty overloads (Copilot :1112): Already complete in a prior commit

All 29 existing tests pass.

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.

Add rules engine to BusinessListBase

4 participants