stride3d/stride · error · InvalidOperationException

Read-only object database.

Error message

Read-only object database.

What it means

ObjectDatabase.Delete removes an object by id from the write backend. If the database was constructed without a write backend (backendWrite == null), it is read-only and any mutation — including Delete — throws. This is a deliberate guard so read-only mounts (e.g. bundled assets) are never modified.

Solutions

  1. Open the ObjectDatabase with a writable backend (backendWrite) before deleting.
  2. Only call Delete in tooling/editor contexts where a write backend exists.
  3. Check for a public writable/readonly indicator before mutating and handle read-only cases gracefully.

Example fix

// before
objDatabase.Delete(objectId);
// after
if (objDatabase.backendWrite != null) // or use a public IsReadOnly accessor if available
    objDatabase.Delete(objectId);
else
    logger.Warn("Object database is read-only; skipping delete");
Defensive patterns

Strategy: validation

Validate before calling

if (database.backendWrite == null)
    return; // skip mutation on read-only database

Try / catch

try { database.Delete(objectId); }
catch (InvalidOperationException ex) when (ex.Message.Contains("Read-only object database")) {
    logger.Warn($"Skipping delete of {objectId}: database is read-only");
}

Prevention

When it happens

Trigger: Calling ObjectDatabase.Delete(objectId) on an ObjectDatabase created/opened without a writable IOdbBackend.

Common situations: Deleting cached assets at runtime when the database only has a bundle/read backend; cleanup code running against a read-only APK/asset mount; connecting to the store with read-only flags.

Understand the failure class

Background: UnsupportedOperationException and "is not supported" errors: when a library deliberately refuses a call — this error's family across 30 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/22e4de6e82e3a520. Report an issue: GitHub.

Appendix: source

Thrown at sources/core/Stride.Core.Serialization/Storage/ObjectDatabase.cs:218

        if (BundleBackend != null)
            result = result.Union(BundleBackend.EnumerateObjects());

        if (backendRead2 != null)
            result = result.Union(backendRead2.EnumerateObjects());

        return result;
    }

    public IEnumerable<ObjectId> EnumerateLooseObjects()
    {
        return backendRead1.EnumerateObjects();
    }

    public void Delete(ObjectId objectId)
    {
        if (backendWrite == null)
            throw new InvalidOperationException("Read-only object database.");

        backendWrite.Delete(objectId);
    }

    /// <summary>
    /// Refreshes an object's last-use time for mtime-LRU GC and records it in the session working set
    /// (see <see cref="RetouchWorkingSet"/>). Best-effort and throttled; only the writable loose-file
    /// store supports it (bundles/read-only backends are skipped).
    /// </summary>
    public void Touch(ObjectId objectId)
    {
        touchedObjectIds.TryAdd(objectId, 0);
        (backendWrite as FileOdbBackend)?.Touch(objectId);
    }

    /// <summary>
    /// Re-touches every object hit this session so the working set doesn't age out of the mtime-LRU
    /// cache while the editor sits idle (no rebuilds to touch it on-hit). Throttled per file.

View on GitHub (pinned to 96fad776d2)