SaveResult and DeleteResult Reference
Summary
This section provides a comprehensive reference for SaveResult and DeleteResult classes, which contain detailed information about the outcome of database operations.
SaveResult Class
SaveResult is returned by Create, Edit, and Upsert operations and contains information about the operation's success or failure.
Class Overview:
public class SaveResult
{
public string Id { get; set; }
public bool HasError { get; set; }
public bool IsInsert { get; set; }
public List<Error> Errors { get; set; }
public DateTime? Timestamp { get; set; }
}
SaveResult Properties
Id
The ID of the record that was created or updated.
| Property | Type | Description |
|---|
Id | string | The unique identifier of the record affected by the operation |
Usage:
var result = Database.Create(account, new DatabaseOptions { IsApiMode = true });
if (!result.HasError)
{
string newId = result.Id;
SystemInfo.Debug($"Record created with ID: {newId}");
// Use the ID for subsequent operations
var contact = new Contact
{
FirstName = "John",
LastName = "Smith",
AccountId = result.Id // Use the newly created ID
};
}
Behavior:
- Always populated when operation succeeds
- Contains the record ID for both Create and Edit operations
- For Create: Returns the newly generated ID
- For Edit: Returns the same ID that was updated
- For Upsert: Returns the ID of the inserted or updated record
HasError
Indicates whether the operation encountered any errors.
| Property | Type | Description |
|---|
HasError | bool | true if the operation failed; false if successful |
Usage:
var result = Database.Create(account, new DatabaseOptions { IsApiMode = true });
if (!result.HasError)
{
// Operation succeeded
SystemInfo.Debug("Record created successfully");
}
else
{
// Operation failed
SystemInfo.Debug("Operation failed");
}
// Check multiple results
var results = Database.Create(accounts, new DatabaseOptions { IsApiMode = true });
bool allSucceeded = results.All(r => !r.HasError);
bool anyFailed = results.Any(r => r.HasError);
if (allSucceeded)
{
SystemInfo.Debug("All records created successfully");
}
else
{
SystemInfo.Debug($"Some records failed: {results.Count(r => r.HasError)} errors");
}
Behavior:
false when operation succeedstrue when operation fails- Check this property before accessing
Id or other success-related properties - When
HasError = true, the Errors collection contains details
IsInsert
Indicates whether the operation was an insert (for Upsert operations only).
| Property | Type | Description |
|---|
IsInsert | bool | true if record was inserted; false if updated (Upsert only) |
Usage:
var result = Database.Upsert(account, new DatabaseOptions
{
ExternalIdField = "ExternalId__c",
IsApiMode = true
});
if (!result.HasError)
{
if (result.IsInsert)
{
SystemInfo.Debug($"New record created: {result.Id}");
}
else
{
SystemInfo.Debug($"Existing record updated: {result.Id}");
}
}
// Track insert vs update counts
var results = Database.Upsert(accounts, new DatabaseOptions
{
ExternalIdField = "ExternalId__c",
IsApiMode = true
});
int insertCount = results.Count(r => !r.HasError && r.IsInsert);
int updateCount = results.Count(r => !r.HasError && !r.IsInsert);
SystemInfo.Debug($"Inserted: {insertCount}, Updated: {updateCount}");
Behavior:
- Only relevant for Upsert operations
true when a new record was createdfalse when an existing record was updated- Should only be checked when
HasError = false - Not meaningful for Create or Edit operations (only Upsert)
Errors
Collection of error details when the operation fails.
| Property | Type | Description |
|---|
Errors | List<Error> | List of error objects containing detailed error information |
Usage:
var result = Database.Create(account, new DatabaseOptions { IsApiMode = true });
if (result.HasError)
{
SystemInfo.Debug($"Operation failed with {result.Errors.Count} errors:");
foreach (var error in result.Errors)
{
SystemInfo.Debug($"Field: {error.PropertyName}");
SystemInfo.Debug($"Message: {error.Message}");
if (!string.IsNullOrEmpty(error.Code))
{
SystemInfo.Debug($"Code: {error.Code}");
}
}
}
Error Object Structure:
public class Error
{
public string PropertyName { get; set; } // Field that caused the error
public string Message { get; set; } // Human-readable error message
public string Code { get; set; } // Error code (optional)
}
Common Error Scenarios:
// Required field missing
var account = new Account { Type = "Partner" }; // Missing Name
var result = Database.Create(account, new DatabaseOptions { IsApiMode = true });
if (result.HasError)
{
// Error.PropertyName = "Name"
// Error.Message = "Name is required"
}
// Invalid field value
var contact = new Contact
{
FirstName = "John",
LastName = "Smith",
Email = "invalid-email" // Invalid format
};
var result = Database.Create(contact, new DatabaseOptions { IsApiMode = true });
if (result.HasError)
{
// Error.PropertyName = "Email"
// Error.Message = "Invalid email format"
}
// Duplicate unique value
var account = new Account
{
Name = "Test",
ExternalId__c = "DUPLICATE-ID" // Already exists
};
var result = Database.Create(account, new DatabaseOptions { IsApiMode = true });
if (result.HasError)
{
// Error.PropertyName = "ExternalId__c"
// Error.Message = "Duplicate external ID"
}
Behavior:
- Empty when
HasError = false - Contains one or more Error objects when
HasError = true - Each Error provides field name, message, and optional code
- Multiple errors possible for a single operation (e.g., multiple required fields missing)
Timestamp
The date and time when the operation was executed.
| Property | Type | Description |
|---|
Timestamp | DateTime? | UTC timestamp of when the operation completed |
Usage:
var result = Database.Create(account, new DatabaseOptions { IsApiMode = true });
if (!result.HasError)
{
DateTime created = result.Timestamp.Value;
SystemInfo.Debug($"Record created at: {created}");
// Compare with current time
var elapsed = DateTime.UtcNow - created;
SystemInfo.Debug($"Operation took {elapsed.TotalMilliseconds}ms");
}
Behavior:
- Populated on successful operations
- Contains UTC timestamp
- Nullable type (may be null in some scenarios)
- Represents server-side execution time
SaveResult Usage Patterns
Single Record Success Check:
var result = Database.Create(account, new DatabaseOptions { IsApiMode = true });
if (!result.HasError)
{
SystemInfo.Debug($"Success! Created {result.Id}");
}
else
{
SystemInfo.Debug($"Failed: {result.Errors[0].Message}");
}
Bulk Operations Result Processing:
var results = Database.Create(accounts, new DatabaseOptions { IsApiMode = true });
// Count successes and failures
int successCount = results.Count(r => !r.HasError);
int errorCount = results.Count(r => r.HasError);
SystemInfo.Debug($"Created {successCount} of {accounts.Count} accounts");
SystemInfo.Debug($"Failed {errorCount} accounts");
// Collect successful IDs
var successfulIds = results
.Where(r => !r.HasError)
.Select(r => r.Id)
.ToList();
// Collect errors
var errors = results
.Where(r => r.HasError)
.SelectMany(r => r.Errors)
.ToList();
Detailed Error Reporting:
var results = Database.Create(accounts, new DatabaseOptions { IsApiMode = true });
for (int i = 0; i < results.Count(); i++)
{
var result = results.ElementAt(i);
var account = accounts[i];
if (result.HasError)
{
SystemInfo.Debug($"Account '{account.Name}' failed:");
foreach (var error in result.Errors)
{
string fieldInfo = string.IsNullOrEmpty(error.PropertyName)
? "General error"
: $"Field '{error.PropertyName}'";
SystemInfo.Debug($" {fieldInfo}: {error.Message}");
}
}
else
{
SystemInfo.Debug($"Account '{account.Name}' created: {result.Id}");
}
}
Upsert Result Analysis:
var results = Database.Upsert(accounts, new DatabaseOptions
{
ExternalIdField = "ExternalId__c",
IsApiMode = true
});
// Categorize results
var inserted = new List<string>();
var updated = new List<string>();
var failed = new List<string>();
for (int i = 0; i < results.Count(); i++)
{
var result = results.ElementAt(i);
var account = accounts[i];
if (result.HasError)
{
failed.Add($"{account.Name}: {result.Errors[0].Message}");
}
else if (result.IsInsert)
{
inserted.Add(result.Id);
}
else
{
updated.Add(result.Id);
}
}
SystemInfo.Debug($"Upsert complete:");
SystemInfo.Debug($" Inserted: {inserted.Count}");
SystemInfo.Debug($" Updated: {updated.Count}");
SystemInfo.Debug($" Failed: {failed.Count}");
Controller ModelState Integration:
[HttpPost]
public ActionResponse CreateAccount(Account model)
{
var result = Database.Create(model, new DatabaseOptions { IsApiMode = true });
if (result.HasError)
{
// Add errors to ModelState for display
foreach (var error in result.Errors)
{
if (!string.IsNullOrEmpty(error.PropertyName))
{
ModelState.AddModelError(error.PropertyName, error.Message);
}
else
{
ModelState.AddModelError("", error.Message);
}
}
return View(model);
}
AspxPage.AddMessage("Account created successfully!");
return RedirectToAction("Details", new { id = result.Id });
}
DeleteResult Class
DeleteResult is returned by Delete operations and contains information about the deletion outcome.
Class Overview:
public class DeleteResult
{
public string Id { get; set; }
public bool HasError { get; set; }
public List<Error> Errors { get; set; }
}
DeleteResult Properties
Id
The ID of the record that was deleted.
| Property | Type | Description |
|---|
Id | string | The unique identifier of the deleted record |
Usage:
var result = Database.Delete(accountId, new DatabaseOptions { IsApiMode = true });
if (!result.HasError)
{
SystemInfo.Debug($"Deleted record: {result.Id}");
}
Behavior:
- Populated when deletion succeeds
- Contains the ID of the deleted record
- Can be used for logging or audit purposes
HasError
Indicates whether the delete operation encountered any errors.
| Property | Type | Description |
|---|
HasError | bool | true if the operation failed; false if successful |
Usage:
var result = Database.Delete(accountId, new DatabaseOptions { IsApiMode = true });
if (!result.HasError)
{
SystemInfo.Debug("Record deleted successfully");
}
else
{
SystemInfo.Debug("Delete failed");
}
// Bulk delete results
var results = Database.Delete(accounts, new DatabaseOptions { IsApiMode = true });
int deletedCount = results.Count(r => !r.HasError);
int failedCount = results.Count(r => r.HasError);
SystemInfo.Debug($"Deleted {deletedCount}, failed {failedCount}");
Behavior:
false when deletion succeedstrue when deletion fails- Check before assuming deletion was successful
Errors
Collection of error details when the delete operation fails.
| Property | Type | Description |
|---|
Errors | List<Error> | List of error objects containing detailed error information |
Usage:
var result = Database.Delete(accountId, new DatabaseOptions { IsApiMode = true });
if (result.HasError)
{
SystemInfo.Debug($"Delete failed with {result.Errors.Count} errors:");
foreach (var error in result.Errors)
{
SystemInfo.Debug($"Message: {error.Message}");
if (!string.IsNullOrEmpty(error.Code))
{
SystemInfo.Debug($"Code: {error.Code}");
}
}
}
Common Delete Error Scenarios:
// Record not found
var result = Database.Delete("INVALID_ID", new DatabaseOptions { IsApiMode = true });
// Error.Message = "Record does not exist"
// Record has dependent children
var result = Database.Delete(accountId, new DatabaseOptions { IsApiMode = true });
// Error.Message = "Cannot delete account with related opportunities"
// Insufficient permissions
var result = Database.Delete(recordId, new DatabaseOptions { IsApiMode = true });
// Error.Message = "User does not have delete permission"
// Record already deleted
var result = Database.Delete(deletedRecordId, new DatabaseOptions { IsApiMode = true });
// Error.Message = "Record is already deleted"
Behavior:
- Empty when
HasError = false - Contains one or more Error objects when
HasError = true - Each Error provides message and optional code
- PropertyName typically not used in delete errors (applies to entire record)
DeleteResult Usage Patterns
Single Record Delete Check:
var result = Database.Delete(accountId, new DatabaseOptions { IsApiMode = true });
if (!result.HasError)
{
SystemInfo.Debug($"Successfully deleted {result.Id}");
}
else
{
SystemInfo.Debug($"Delete failed: {result.Errors[0].Message}");
}
Bulk Delete Result Processing:
var results = Database.Delete(accounts, new DatabaseOptions { IsApiMode = true });
// Count successes and failures
int deletedCount = results.Count(r => !r.HasError);
int failedCount = results.Count(r => r.HasError);
SystemInfo.Debug($"Deleted {deletedCount} of {accounts.Count} accounts");
SystemInfo.Debug($"Failed {failedCount} accounts");
// Collect deleted IDs
var deletedIds = results
.Where(r => !r.HasError)
.Select(r => r.Id)
.ToList();
// Log failures
foreach (var result in results.Where(r => r.HasError))
{
SystemInfo.Debug($"Failed to delete {result.Id}: {result.Errors[0].Message}");
}
Delete with Dependency Check:
public DeleteResult SafeDelete(string accountId)
{
// Check for dependencies first
long contactCount = Database.Count<Contact>(f => f.AccountId == accountId);
if (contactCount > 0)
{
SystemInfo.Debug($"Cannot delete: {contactCount} related contacts exist");
return new DeleteResult
{
Id = accountId,
HasError = true,
Errors = new List<Error>
{
new Error
{
Message = $"Cannot delete account: {contactCount} related contacts must be removed first"
}
}
};
}
// Safe to delete
return Database.Delete(accountId, new DatabaseOptions { IsApiMode = true });
}
Controller Delete Error Handling:
[HttpPost]
public ActionResponse DeleteAccount(string id)
{
var result = Database.Delete(id, new DatabaseOptions { IsApiMode = true });
if (result.HasError)
{
AspxPage.AddErrorMessage($"Delete failed: {result.Errors[0].Message}");
return RedirectToAction("Details", new { id = id });
}
AspxPage.AddMessage("Account deleted successfully");
return RedirectToAction("Index");
}
Cascade Delete with Results:
public class CascadeDeleteResult
{
public int ContactsDeleted { get; set; }
public int OpportunitiesDeleted { get; set; }
public bool AccountDeleted { get; set; }
public List<string> Errors { get; set; }
}
public CascadeDeleteResult CascadeDeleteAccount(string accountId)
{
var result = new CascadeDeleteResult { Errors = new List<string>() };
// Delete contacts
var contacts = Database.Query<Contact>()
.Where(f => f.AccountId == accountId)
.ToList();
if (contacts.Count > 0)
{
var contactResults = Database.Delete(contacts, new DatabaseOptions { IsApiMode = true });
result.ContactsDeleted = contactResults.Count(r => !r.HasError);
var errors = contactResults
.Where(r => r.HasError)
.SelectMany(r => r.Errors.Select(e => e.Message));
result.Errors.AddRange(errors);
}
// Delete opportunities
var opportunities = Database.Query<Opportunity>()
.Where(f => f.AccountId == accountId)
.ToList();
if (opportunities.Count > 0)
{
var oppResults = Database.Delete(opportunities, new DatabaseOptions { IsApiMode = true });
result.OpportunitiesDeleted = oppResults.Count(r => !r.HasError);
var errors = oppResults
.Where(r => r.HasError)
.SelectMany(r => r.Errors.Select(e => e.Message));
result.Errors.AddRange(errors);
}
// Delete account
var accountResult = Database.Delete(accountId, new DatabaseOptions { IsApiMode = true });
result.AccountDeleted = !accountResult.HasError;
if (accountResult.HasError)
{
result.Errors.AddRange(accountResult.Errors.Select(e => e.Message));
}
return result;
}
Error Class Reference
The Error class provides detailed information about validation and operation errors.
Class Structure:
public class Error
{
public string PropertyName { get; set; }
public string Message { get; set; }
public string Code { get; set; }
}
Properties:
| Property | Type | Description |
|---|
PropertyName | string | The field/property name that caused the error (may be null for general errors) |
Message | string | Human-readable error message |
Code | string | Error code (optional, may be null) |
Error Examples:
// Field validation error
Error fieldError = new Error
{
PropertyName = "Email",
Message = "Invalid email format",
Code = "INVALID_EMAIL"
};
// General error (no specific field)
Error generalError = new Error
{
PropertyName = null,
Message = "Record does not exist",
Code = "NOT_FOUND"
};
// Required field error
Error requiredError = new Error
{
PropertyName = "Name",
Message = "Name is required",
Code = "REQUIRED_FIELD"
};
Best Practices for Result Handling
✅ Always check HasError before accessing success properties
if (!result.HasError)
{
string id = result.Id; // Safe to access
}
✅ Use IsApiMode = true for better error handling
var result = Database.Create(account, new DatabaseOptions { IsApiMode = true });
if (result.HasError)
{
// Handle errors gracefully
}
✅ Log detailed error information
if (result.HasError)
{
foreach (var error in result.Errors)
{
SystemInfo.Debug($"{error.PropertyName}: {error.Message}");
}
}
✅ Process bulk results individually
for (int i = 0; i < results.Count(); i++)
{
var result = results.ElementAt(i);
var record = records[i];
if (result.HasError)
{
// Handle this specific failure
}
}
✅ Use result IDs for subsequent operations
var accountResult = Database.Create(account, new DatabaseOptions { IsApiMode = true });
if (!accountResult.HasError)
{
var contact = new Contact
{
FirstName = "John",
LastName = "Smith",
AccountId = accountResult.Id // Use the new ID
};
}
❌ Don't assume success without checking
// ❌ BAD - Assumes success
var result = Database.Create(account);
string newId = result.Id; // May be null if failed
// ✅ GOOD - Checks first
var result = Database.Create(account, new DatabaseOptions { IsApiMode = true });
if (!result.HasError)
{
string newId = result.Id;
}
❌ Don't ignore errors
// ❌ BAD - Ignores errors
Database.Create(account);
// ✅ GOOD - Handles errors
var result = Database.Create(account, new DatabaseOptions { IsApiMode = true });
if (result.HasError)
{
SystemInfo.Debug($"Create failed: {result.Errors[0].Message}");
}