Reading and writing
Put, Get, Remove, HasKey, and MultiGet are the bread and butter of RocksDB. Every variant accepts an optional ColumnFamilyHandle, ReadOptions or WriteOptions, and either a string (UTF-8 by default) or arbitrary bytes.
For the upstream semantics — atomicity, ordering, durability — see the Basic Operations wiki.
Put
Put(key, value) inserts or overwrites. It's atomic, durable once the WAL is fsynced, and visible to all subsequent reads.
db.Put("user:42", "Ada Lovelace");
// Bytes
db.Put(keyBytes, valueBytes);
// Spans (net6+)
db.Put("user:42"u8, "Ada Lovelace"u8);
// Into a specific column family
db.Put("u:42", "Ada", users);
// With explicit write options
var sync = new WriteOptions().SetSync(true);
db.Put("k", "v", writeOptions: sync);
Get
Get returns null when the key is missing. The string overload decodes UTF-8 by default; pass a different Encoding to override.
string name = db.Get("user:42"); // null if missing
byte[] raw = db.Get(keyBytes);
// Span fast path — no managed allocation for the value
Span<byte> dst = stackalloc byte[8];
if (db.GetFixedSizeValue(keyBytes, dst))
{
long v = BitConverter.ToInt64(dst);
}
// Custom deserializer — read directly from native memory
long total = db.Get<long>(keyBytes, new LongDeserializer());
class LongDeserializer : ISpanDeserializer<long>
{
public long Deserialize(ReadOnlySpan<byte> data) => BitConverter.ToInt64(data);
}
Avoid allocations
Use the Span<byte> and ISpanDeserializer<T> overloads on hot read paths to avoid the byte[] allocation that the default Get makes.
HasKey
HasKey is a cheap existence check — it doesn't materialise the value. Useful when you only care whether a key is present.
if (db.HasKey("user:42")) { /* … */ }
Note that "may exist" is not "exists" — RocksDB ultimately consults Bloom filters, the block cache, and SST files, but the answer is exact for the high-level API in this binding.
Remove
Deletes a key. Removing a missing key is a no-op (no error).
db.Remove("user:42");
db.Remove(keyBytes);
db.Remove("u:42", users); // in a column family
Internally, RocksDB writes a tombstone that masks the key until compaction reclaims the space. See the Delete Range wiki page if you need to delete many adjacent keys at once — WriteBatch.DeleteRange exposes the same primitive.
MultiGet
MultiGet fetches many keys in one call. It can be significantly faster than calling Get in a loop because RocksDB de-duplicates Bloom-filter and block-cache work across the batch.
string[] keys = { "a", "b", "c" };
var pairs = db.MultiGet(keys); // KeyValuePair<string,string>[]
var bytes = db.MultiGet(new byte[][] { ... }); // KeyValuePair<byte[],byte[]>[]
Missing keys come back with a null value.
Choosing between strings and bytes
| You have | Use |
|---|---|
| ASCII or UTF-8 text keys/values | string overloads. UTF-8 by default. |
| Binary payloads (numbers, protobufs, hashes) | byte[] overloads. |
| Hot path, .NET 6+ | ReadOnlySpan<byte> / "…"u8 literals. Zero allocation. |
| Custom serialization | Get<T>(..., ISpanDeserializer<T>) or Get<T>(..., Func<Stream, T>). |
Worked example
using System.Text;
using RocksDbSharp;
public sealed class UserStore : IDisposable
{
private readonly RocksDb _db;
public UserStore(string path)
{
var options = new DbOptions().SetCreateIfMissing(true);
_db = RocksDb.Open(options, path);
}
public void Save(long userId, string name)
{
Span<byte> key = stackalloc byte[8];
BitConverter.TryWriteBytes(key, userId);
_db.Put(key, Encoding.UTF8.GetBytes(name));
}
public string? Load(long userId)
{
Span<byte> key = stackalloc byte[8];
BitConverter.TryWriteBytes(key, userId);
return _db.Get<string>(
key,
new Utf8Deserializer());
}
public void Dispose() => _db.Dispose();
private sealed class Utf8Deserializer : ISpanDeserializer<string>
{
public string Deserialize(ReadOnlySpan<byte> data)
=> Encoding.UTF8.GetString(data);
}
}