Table of Contents


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.

PropertyTypeDescription
IdstringThe 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.

PropertyTypeDescription
HasErrorbooltrue 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 succeeds
  • true 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).

PropertyTypeDescription
IsInsertbooltrue 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 created
  • false 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.

PropertyTypeDescription
ErrorsList<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.

PropertyTypeDescription
TimestampDateTime?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.

PropertyTypeDescription
IdstringThe 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.

PropertyTypeDescription
HasErrorbooltrue 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 succeeds
  • true when deletion fails
  • Check before assuming deletion was successful

Errors

Collection of error details when the delete operation fails.

PropertyTypeDescription
ErrorsList<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:

PropertyTypeDescription
PropertyNamestringThe field/property name that caused the error (may be null for general errors)
MessagestringHuman-readable error message
CodestringError 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}");
}

 

Last updated on 1/8/2026

Attachments