I wish to use leveldb in my pure C# project.
I have googled for a C# version of leveldb, but got no lucky.
Any one can tell me where I can find a C# version of leveldb?
Thanks
LevelDB is a key/value store built by Google. It can support an ordered mapping from string keys to string values. The core storage architecture of LevelDB is a log-structured merge tree (LSM), which is a write-optimized B-tree variant. It is optimized for large sequential writes as opposed to small random writes.
Usage. LevelDB is used as the backend database for Google Chrome's IndexedDB and is one of the supported backends for Riak.
LevelDB and RocksDB are not distributed databases.
LevelDB continuously inspects files at each level and triggers compactions when the number of files or the total size at a given level goes beyond a set threshold. LevelDB manages 7 levels of files. The list of current SST files is kept in a MANIFEST file.
Not that I know of, but I've been using it in my C# project. If you're familiar with C++, then you can make your own CLI wrapper (shouldn't be that much trouble), build it as a DLL and then you can load that DLL in your C# project like any other assembly reference.
There is a windows port for leveldb and it's a little tricky to get it into Visual Studio, but if you're having trouble I can upload my Visual Studio 2010 solution (which is 75% of the battle) with the whole thing set-up and ready to build (except the CLI wrapper). I can put it up on github or something, which I'm actually planning on doing anyway, but I'll expedite it for you.
Like I said, I've been using that approach for my C# project and it works great. However, if you have really high performance requirements, then I would recommend batching up "work" in order to reduce the P/Invokes.
Please note that I have not compiled this code, but I'm just posting it as an example. Your header file might look like this:
#pragma once
#include <exception>
#include "leveldb\db.h"
using namespace System::Runtime::InteropServices;
// Create the namespace
namespace LevelDBWrapperNS
{
// Note that size_t changes depending on the target platform of your build:
// for 32-bit builds, size_t is a 32-bit unsigned integer.
// for 64-bit builds, size_t is a 64-bit unsigned integer.
// There is no size_t equivalent in C#, but there are ways to
// mimic the same behavior. Alternately, you can change the
// size_t to unsigned long for 32-bit builds or unsigned long long (64-bit)
// Declare the leveldb wrapper
public ref class LevelDBWrapper
{
private:
leveldb::DB* _db;
public:
LevelDBWrapper(const std::string dataDirectory);
~LevelDBWrapper();
// A get method which given a key, puts data in the value array
// and sets the valueSize according to the size of the data it
// allocated. Note: you will have to deallocate the data in C#
void Get(const char* key, const size_t keySize, char* value, size_t &valueSize);
// A put method which takes in strings instead of char*
bool Put(const std::string key, const std::string value);
// A put method which takes in char* pointers
bool Put(const char* key, const size_t keySize, const char* value, const size_t valueSize);
// A delete method
bool Delete(const char* key, const size_t keySize);
private:
void Open(const char* dataDirectory);
};
}
Your cpp file is going to be along the lines of:
#include "LevelDBWrapper.h"
// Use the same namespace as the header
namespace LevelDBWrapperNS
{
LevelDBWrapper::LevelDBWrapper(const std::string dataDirectory)
{
Open(dataDirectory.c_str());
}
LevelDBWrapper::~LevelDBWrapper()
{
if(_db!=NULL)
{
delete _db;
_db= NULL;
}
// NOTE: don't forget to delete the block cache too!!!
/*if(options.block_cache != NULL)
{
delete options.block_cache;
options.block_cache = NULL;
}*/
}
bool LevelDBWrapper::Put(const char* key, const size_t keySize, const char* value, const size_t valueSize)
{
leveldb::Slice sKey(key, keySize);
leveldb::Slice sValue(value, valueSize);
return _db->Put(leveldb::WriteOptions(), sKey, sValue).ok();
}
void LevelDBWrapper::Open(const char* dataDirectory)
{
leveldb::Options options;
// Create a database environment. This will enable caching between
// separate calls (and improve performance). This also enables
// the db_stat.exe command which allows cache tuning. Open
// transactional environment leveldb::Options options;
options.create_if_missing = true;
// Open the database if it exists
options.error_if_exists = false;
// 64 Mb read cache
options.block_cache = leveldb::NewLRUCache(64 * 1024 * 1024);
// Writes will be flushed every 32 Mb
options.write_buffer_size = 32 * 1024 * 1024;
// If you do a lot of bulk operations it may be good to increase the
// block size to a 64k block size. A power of 2 block size also
// also improves the compression rate when using Snappy.
options.block_size = 64 * 1024;
options.max_open_files = 500;
options.compression = leveldb::kNoCompression;
_db = NULL;
// Open the database
leveldb::Status status = leveldb::DB::Open(options, dataDirectory, &_db);
// Check if there was a failure
if(!status.ok())
{
// The database failed to open!
if(status.ToString().find("partial record without end")!=std::string::npos)
{
// Attempting to recover the database...
status = leveldb::RepairDB(dataDirectory, options);
if(status.ok())
{
// Successfully recovered the database! Attempting to reopen...
status = leveldb::DB::Open( options, dataDirectory, &_db);
}
else
{
// Failed to recover the database!
}
}
// Throw an exception if the failure was unrecoverable!
if(!status.ok())
{
throw std::runtime_error(std::string("Unable to open: ") + std::string(dataDirectory) +
std::string(" ") + status.ToString());
}
}
}
}
This should get you in the right direction.
OK, Get will look like this:
// Returns a buffer containing the data and sets the bufferLen.
// The user must specify the key and the length of the key so a slice
// can be constructed and sent to leveldb.
const unsigned char* Get(const char* key, const size_t keyLength, [Out]size_t %bufferLen);
The source is along the lines:
const unsigned char* LevelDBWrapper::Get(const char* key, const size_t keyLength, [Out]size_t %bufferLen)
{
unsigned char* buffer = NULL;
std::string value;
leveldb::Status s = db->Get(leveldb::ReadOptions(), Slice(key, keyLength), &value);
if(s.ok())
{
// we found the key, so set the buffer length
bufferLen = value.size();
// initialize the buffer
buffer = new unsigned char[bufferLen];
// set the buffer
memset(buffer, 0, bufferLen);
// copy the data
memcpy(memcpy((void*)(buffer), value.c_str(), bufferLen);
}
else
{
// The buffer length is 0 because a key was not found
bufferLen = 0;
}
return buffer;
}
Note that different data may have different encoding, so I feel like the safest way to pass data between your unmanaged and managed code is to use pointers and an UnmanagedMemoryStream
. Here is how you would get the data associated with a key in C#:
UInt32 bufferLen = 0;
byte* buffer = dbInstance.Get(key, keyLength, out bufferLen);
UnmanagedMemoryStream ums = new UnmanagedMemoryStream(buffer, (Int32)bufferLen, (Int32)bufferLen, FileAccess.Read);
// Create a byte array to hold data from unmanaged memory.
byte[] data = new byte [bufferLen];
// Read from unmanaged memory to the byte array.
readStream.Read(data , 0, bufferLen);
// Don't forget to free the block of unmanaged memory!!!
Marshal.FreeHGlobal(buffer);
Again, I have not compiled or run the code, but it should get you on the right track.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With