---
title: Save business object
summary: Validates and persists a root or child business object by dispatching delete, insert, or update work through the data-access layer.
status: draft
owner: SAIF
last_reviewed: 2026-07-30
library: SAIF.Framework.Core
reference_type: package
operation_id: OP-CORE-005
trigger: Caller invokes Save() or parent logic invokes SaveChild().
---

# Save business object

This operation is the core persistence pipeline for a single business object, whether it is a root object saved directly or a child object saved on behalf of a parent. It validates the object's entry state, re-runs rule evaluation, picks the correct delete, insert, or update branch purely from the object's own state flags (`IsDeleted`, `IsNew`, `IsDirty`), and then drives the matching before/after hook sequence on the data-access layer.

| Field | Value |
| --- | --- |
| **Operation ID** | OP-CORE-005 |
| **Trigger** | Caller invokes `Save()` on a root object or parent logic invokes `SaveChild()` on a child object. |
| **Inputs** | Business object state flags, current broken-rule state, `CriteriaObject`, and `DataPortalInfo`. |
| **Outputs** | Successful delete/insert/update/no-op result, `False` when errors remain after persistence, or thrown exception for invalid entry states. |
| **Primary components** | `clsBusinessBase.Save`, `clsBusinessBase.SaveChild`, `clsBusinessBase.ExecuteData`, `clsDataAccess.DeleteObject`, `clsDataAccess.InsertObject`, `clsDataAccess.UpdateObject` |
| **Entry point** | `SAIF.Framework.Core\V1\BaseItems\clsBusinessBase.vb` |

## Steps

1. Enter through `Save()` for a root object or `SaveChild()` for a child object.
   - Root save rejects child objects; child save rejects non-child objects.
   - Both entry points reject read-only objects and open edit levels before persistence starts.
2. Re-evaluate validity before saving ([BR-001](../business-rules.md#br-001-rule-registration-and-enforcement-framework), [BR-002](../business-rules.md#br-002-broken-rule-tracking-and-surfacing)).
   - If `IsValid = False`, the method throws `ApplicationException` that includes the current `IDataErrorInfo.Error` text ([BR-001](../business-rules.md#br-001-rule-registration-and-enforcement-framework), [BR-002](../business-rules.md#br-002-broken-rule-tracking-and-surfacing)).
3. Clear current errors and warnings before data work begins.
   - `Rules.BrokenRules.ClearErrorsAndWarnings()` resets the transient error/warning surface so the next persistence attempt starts clean ([BR-002](../business-rules.md#br-002-broken-rule-tracking-and-surfacing)).
4. Branch inside `ExecuteData()` based on object state.
   - If `IsDeleted = True` and `IsNew = True`, the method returns `True` immediately because nothing exists to remove.
   - If `IsDeleted = True` and `IsNew = False`, the method delegates to `DeleteObject(Me)`.
   - If `IsNew = True` and `IsDirty = True`, the method delegates to `InsertObject(Me)`.
   - If `IsDirty = True` and `IsNew = False`, the method delegates to `UpdateObject(Me)`.
   - If the object is not dirty, the method returns `True` without any data-access work.
5. Run the delete, insert, or update hook sequence inside `clsDataAccess`.
   - `OnSave()` runs first.
   - The data-access layer resolves the portal from `CriteriaObject.DataPortalInfo.PortalID`, creates a new portal request, and calls the matching `BeforeDelete`, `BeforeInsert`, or `BeforeUpdate` hook.
   - If one or more requests were queued, the portal connects, processes the batch, runs the matching `After*` hook, and closes any reader results.
   - The portal disconnects in `Finally`, and `OnSaveComplete()` runs after the persistence method finishes.
6. Translate post-persistence errors into the boolean result ([BR-002](../business-rules.md#br-002-broken-rule-tracking-and-surfacing)).
   - After each delete/insert/update branch, `ExecuteData()` returns `False` when `ErrorCount > 0`.
   - Otherwise it returns `True`.

## Process flow

The following diagram visualizes this operation's control flow.

```mermaid
flowchart TD
	Start([Save or SaveChild]) --> Valid{Valid save state?}
	Valid -->|No| EndErr([Throw ApplicationException])
	Valid -->|Yes| Deleted{Deleted?}
	Deleted -->|Yes and new| EndNoop([Return true])
	Deleted -->|Yes and existing| Delete[DeleteObject]
	Deleted -->|No| NewDirty{New and dirty?}
	NewDirty -->|Yes| Insert[InsertObject]
	NewDirty -->|No| Dirty{Dirty?}
	Dirty -->|Yes| Update[UpdateObject]
	Dirty -->|No| EndNoop
	Delete --> Finish[Return false if errors remain]
	Insert --> Finish
	Update --> Finish
	Finish --> EndOk([Return save result])
```

## Failure and edge-case behavior

- A root object cannot call `SaveChild()` and a child object cannot call `Save()` — each entry point enforces the opposite role and throws `ApplicationException` immediately.
- An invalid object fails before any data-access work starts, and the thrown exception carries the current `IDataErrorInfo.Error` text so the caller sees exactly which rule blocked the save.
- A deleted object that was never persisted (`IsDeleted = True` and `IsNew = True`) is treated as a successful no-op, since there is nothing in the data store to remove.
- Data-access failures from the delete, insert, or update helpers are wrapped in `ApplicationException`.

## Related documentation
- [Operations and processes](../operations-processes.md)
- [Business rules](../business-rules.md)

## Sources
- SAIF.Framework.Core\V1\BaseItems\clsBusinessBase.vb
- SAIF.Framework.Core\V1\DataAccessLayer\clsDataAccess.vb
