14

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

Jack
  • 3,913
  • 8
  • 41
  • 66
  • 1
    There's a discussion here about using the Windows port of leveldb in your C# project: https://groups.google.com/forum/#!topic/leveldb/RxSaQYVDoaI – Brian Snow Feb 15 '12 at 19:07
  • Now there is: [original_wrapper](https://bitbucket.org/robertvazan/leveldb.net), [db](https://bitbucket.org/robertvazan/leveldb-windows) or cloned to github: [wrapper](https://github.com/ren85/leveldb.net), [db itself](https://github.com/ren85/leveldb-windows) – ren Oct 03 '14 at 12:53

4 Answers4

10

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.

Example

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.

Get Example

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.

Kiril
  • 39,672
  • 31
  • 167
  • 226
  • thanks for your answer!!! What I need to is really simple, I just need a simple interface for putting and getting {key, value}, as simple as that. I don't know anything about P/Invokes or CLI, could you please share more HOW TO DO IT with me? Thanks – Jack Feb 17 '12 at 10:33
  • How comfortable are you with C++? If you've done anything in C++ before, then I'll post an example of how a wrapper would work. If you haven't done anything in C++ before, then it will be difficult to get it right... – Kiril Feb 17 '12 at 15:55
  • I learned C, good at objective-C and Java and C#. I haven't written anything in C++, but if you could give me an example wrapper, I think I will manage to understand it. At least, that's a best start for me. Please give me an example and let me have a try. – Jack Feb 17 '12 at 16:46
  • thank you very much. Where can I find it then? – Jack Feb 17 '12 at 16:56
  • OK, I added an update... haven't compiled it, but it should get you in the right direction. If you post another question on SO with specific issues you run into, then please also make a comment here, because I am more likely to notice it. I'm also tracking the leveldb tags, so keep tagging leveldb-related questions with the tag. – Kiril Feb 17 '12 at 17:42
  • Really thank you for your help. I will have a try now. Also thank your intended continuous help to me. – Jack Feb 17 '12 at 17:55
  • Note that this class is going to be in your Visual Studio C++ project, which then gets built into a DLL with a /clr flag: Configuration Properties -> General -> Whole Program Optimization -> select "Common Language Runtime Support /clr". Additionally, it should build into a DLL: Config Properties -> General -> Target Extension: ".dll". You can then take the DLL and load it into your C# project and instantiate an instance of the `LevelDBWrapper` as you would any other C# object: `LevelDBWrapper db = new LevelDBWrapper("c:/path/to/db");` – Kiril Feb 17 '12 at 19:25
  • thanks for your instruction for the project settings. Can I know where I should put the dll for leveldb? I don't mean the dll that your code will produce. I got a libleveldb.dll from a windows port of leveldb. – Jack Feb 20 '12 at 10:38
  • Are you building the windows port yourself? The DLL is not going to work out easily, so I would recommend you build the windows port into a .lib and statically link your C++/CLI project into it. That is done in your C++/CLI project configuration: specify the path to the `/leveldb/include` folder in the Additional Include Directories, in Linker->General set the Additional Library Dependencies to point to the output directoy of the leveldb windows port (i.e. where the leveldb.lib is located after you build the windows port) and add leveldb.lib to Linker->Input->Additional Dependencies. – Kiril Feb 20 '12 at 15:31
  • After that, from your C++/CLI port you can just `#include "leveldb\db.h"` just like I've demonstrated in the code example. – Kiril Feb 20 '12 at 15:32
  • BTW, which windows port are you using? Is it the [official windows port](http://code.google.com/p/leveldb/source/browse/?name=windows) or another port? If it's not the official port, then my advice about the DLL might not apply. **If it's not the official port**, then you would have to put the DLL in the same folder where your C++/CLI wrapper for leveldb is outputting its DLL: this means that you will need to reference your leveldbWrapper.dll in your C# project and always have the leveldb.dll in the folder of your C# executable (otherwise your executable will not work). – Kiril Feb 20 '12 at 15:43
  • the official one. Your code of put is really working!! Could you please share with me your method of get? if returning byte array, that would be best ! – Jack Feb 21 '12 at 17:43
  • @Jack I'll post a Get example in a bit. A side note: make sure you delete the `options.block_cache` in the `LevelDBWrapper` destructor, otherwise you'll get a memory leak. – Kiril Feb 21 '12 at 18:43
  • Thanks for the code!!! but how can I transform unsigned char* to byte[]? – Jack Feb 23 '12 at 11:10
  • @Jack the `unsigned char*` is transformed into `byte[]` in the last code snippet in my answer (that code would go in your C# application). I also found a more elegant solution that could make it much more seamless and it can stay directly in your C++/CLI wrapper around leveldb: http://stackoverflow.com/questions/6050990/how-can-i-pass-a-binary-blob-from-c-to-c – Kiril Feb 23 '12 at 15:48
  • Thanks for your code again and again. I have tried your last bit of code, which transfer *char to byte[]. But I get an error:"Error Pointers and fixed size buffers may only be used in an unsafe context". I can't use byte* buffer. I can use In IntPtr buffer, but then the same error will occur for the db.Get method. – Jack Mar 07 '12 at 11:39
  • Jack, I would recommend you post another question for the error and link it here so I can look at it. The unsafe context means that you should use the [`unsafe`](http://msdn.microsoft.com/en-us/library/chfa2zb8%28v=vs.71%29.aspx) keyword. – Kiril Mar 07 '12 at 11:42
  • can I email you or something? – Jack Mar 07 '12 at 15:44
  • @Jack send me an e-mail at lirik@mailinator.com I will then forward it to my private e-mail and we'll go from there. Mailinator will automatically delete your e-mail in 2-3 hours. – Kiril Mar 07 '12 at 15:55
  • @Jack, I received your e-mail and I sent you an e-mail to confirm, let me know if you got it. I also added you to my G+. – Kiril Mar 07 '12 at 16:10
4

As much as I can see you could also use LMDB (lightning memory mapped database, http://symas.com/mdb/ ) which seems quite similar to LevelDB and also comes with a .Net wrapper (https://github.com/ilyalukyanov/Lightning.NET) Dont know how well it works though, haven't used it yet...

Jacob
  • 49
  • 1
1

I haven't used it, but I see leveldb-sharp.

Matt Cruikshank
  • 2,932
  • 21
  • 24
-2

I don't know the story here, but there's this project at Microsoft's official Rx-Js page here.

xanadont
  • 7,493
  • 6
  • 36
  • 49