65.9K
CodeProject 正在变化。 阅读更多。
Home

RavenDB:简单的 NoSQL 数据库

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (3投票s)

2019年12月18日

CPOL
viewsIcon

7111

在本文中,我将引导您完成设置应用程序以与 RavenDB 数据库交互的步骤。我将直接介绍客户端 API,但结尾的附录将解释如何使用 CMake 脚本从其存储库中获取客户端和相关依赖项。

设置 C++ 应用程序以使用 RavenDB 数据库的快速指南

作为设计理念,Hibernating Rhinos 团队致力于让开发者的生活更轻松,我们的 C++ 客户端也不例外。

在本文中,我将引导您完成设置应用程序以与 RavenDB 数据库交互的步骤。我将直接介绍客户端 API,但结尾的 附录将解释如何使用 CMake 脚本从 其存储库中获取客户端和相关依赖项。

RavenDB 客户端 API

作为我们想要存储的数据示例,我定义了一个对象来保存“user”记录。该记录将作为 文档存储在数据库中(对于 SQL 爱好者来说,文档在某种程度上等同于表中的一行,只不过它不需要遵守模式,并且可以包含任意复杂的对象图)。

struct user  
{  
    std::string  id;  
    std::string  name;  
    int  age{};  
    std::vector<std::string> emails;  
};  

CRUD 操作

让我们看一些代码,然后我将在下面进行详细分解。RavenDB 客户端的设计宗旨是具有相似的函数名称和用法模式,因此如果您有使用我们其他客户端的经验,您可能会觉得这个很熟悉。以下代码将我们的应用程序连接到 RavenDB 服务器,然后创建并存储我们的第一个 `user`。

auto doc_store = ravendb::client::documents::DocumentStore::create();
doc_store->set_urls({ "http://live-test.ravendb.net" });
doc_store->set_database("Demo");
doc_store->initialize();
{
    auto session = doc_store->open_session();
    auto user_record = std::make_shared<user>();
    user_record->name = "John Doe";
    user_record->emails = {"john.doe@example.com","non_existing@example.com","bar@example.com"};
    session.store(user_record);
    user_record->age = 35;
    session.save_changes(); //Commits the record to the database
}

需要为我们的记录类定义序列化和反序列化。在 附录中,我将解释如何使用 nlohmann/json 库轻松完成此操作。

如果此时我们在 RavenDB Studio 中查看,我们会看到这个

我们可以为此文档指定 ID,但由于我们没有指定,RavenDB 会自动生成一个唯一的 ID:`users/1-A`。

下面我们可以看到该文档是 JSON 格式的,并且其结构与我们的 `user` 类的结构非常匹配。

刚才发生了什么?

上面的代码做了一些“魔法”。RavenDB C++ 客户端允许我们使用高级 API 和我们的原生应用程序对象与数据库进行交互。一旦定义了序列化,它就会自动处理。您还可能注意到,在调用 `store()` 之后,`user` 的 `age` 字段被修改了,但当您调用 `save_changes()` 时,此更改仍然会被持久化。

现在我们已经在 RavenDB 上存储了第一个文档,让我们看看如何读取、更新和删除。

{
    auto session = store->open_session("TestDB");

    //Load a document by its id
    const std::shared_ptr<user> user = session.load<user>("users/1-A");
    //Modify one of its fields
    user->age = 19; //No trip to the server yet
    session.delete_document("users/2-A"); //No trip to the server yet
    session.save_changes(); //Send the server both commands in one trip
}

`session` 对象允许我们在单个 ACID 事务中执行许多操作。我们不必每次进行删除或部分更新时都与服务器进行往返通信,所有这些操作都将在调用 `save_changes()` 时一次性提交。

到处都是查询!

RavenDB 提供了专用搜索引擎的查询功能。以下是一些示例查询:

//A ‘starts with’ query to fetch all users whose name starts with "John":
const std::vector<std::shared_ptr<user>> usersNamedJohn =
    session.query<user>()->where_starts_with("name", "John")
                         ->order_by_descending("name")
                         ->to_list();

//A 'full-text search’ query on the field "name", using RQL:
const std::vector<std::shared_ptr<user>> rawUsersNamedJohn =
        session.advanced().raw_query<user>("from users where search(name, $name_to_search)")
                                        ->add_parameter("name_to_search","john")
                                        ->to_list();                    

这段代码也相当“神奇”。在存储了我们的文档之后,我们只需指定我们感兴趣的数据,让 RavenDB 来弄清楚如何检索它。

上面的 `raw_query()` 使用 Raven 查询语言 (RQL)。这是我们设计的查询语言,用于公开我们所有的底层查询选项。如果您了解 SQL,或者即使不了解,它也非常容易学习。

如果我们想进行聚合怎么办?此查询计算有多少用户拥有相同的电子邮件。

std::string aggregation_rql_query =
   "from users "
   "group by emails[] " //since 'emails' is an array, treat each item as group key
   "select key() as email, count() as count"; //projection

const auto rawCountEmailsPerUser =
   session.advanced().raw_query<email_count_result>
    (aggregation_rql_query)->to_list();

此聚合查询将结果投影到下面定义的另一个对象中。

struct email_count_result
{
  std::string email; //key of the aggregation
  int count{}; //count of emails
};

正如在 附录中所解释的,这个类也需要进行序列化。

现在让我们来看一个包含所有元素的查询,包括括号和逻辑运算符。

std::string complex_query =
     "from users "
     "where emails[] in ( $email_list ) or "
     "(age > 25 and endsWith(name, 'Doe'))";

const vector<shared_ptr<user>> complexQueryResult =
  session.advanced().raw_query<user>(complex_query)
     ->add_parameter("email_list",
         nlohmann::json::array(
              {"john.doe@example.com","jane.doe@example.com" }))
     ->to_list();

结束

如您所见,与 RavenDB 交互和查询 RavenDB 非常简单、直观,并且不需要大量的复杂“管道”代码。

您可以从 GitHub 下载本文的完整 演示源代码。它假定在本地运行了一个 RavenDB 实例,但您也可以将其指向我们的演示服务器:http://live-test.ravendb.net

要开始使用 C++ 客户端,您可以从 GitHub 仓库下载它,并使用 CMake 进行编译,如下面的 附录所示。为了方便起见,它会编译静态链接和动态链接库。

我想了解更多关于 RavenDB 的信息!

没问题!您可以在 RavenDB 文档中找到详细信息,并在我们的 训练营中找到包含代码示例的分步指南。

请参阅 Inside RavenDB 4.0第二章,了解有关下载和设置本地实例的帮助。

附录:设置序列化

在演示项目中,我们使用 `set_val_to_json()` 和 `get_val_from_json()` 来定义 `user` 记录的序列化和反序列化。这些是 RavenDB C++ 客户端中定义的实用函数,为了方便起见,它们简化了 nlohmann/json 库的某些功能。

inline void to_json(nlohmann::json& j, const user& u)
{
    using ravendb::client::impl::utils::json_utils::set_val_to_json;
    set_val_to_json(j, "name", u.name);
    set_val_to_json(j, "age", u.age);
    set_val_to_json(j, "emails", u.emails);
}
 
inline void from_json(const nlohmann::json& j, user& u)
{
    using ravendb::client::impl::utils::json_utils::get_val_from_json;
    get_val_from_json(j, "name", u.name);
    get_val_from_json(j, "age", u.age);
    get_val_from_json(j, "emails", u.emails);
}

附录:设置依赖项

获取源代码

在根目录的 `CMakeLists.txt` 中,以下命令将获取 RavenDB 客户端源代码,并将其设置为编译目标,我们将对其进行静态链接到父应用程序。

set (RAVENCPP_SRC "${PROJECT_SOURCE_DIR}/libs/RavenDB" CACHE INTERNAL "RAVENCPP_SRC")
#fetch the RavenDB C++ client from the repo using ‘git clone’
include(FetchContent)
FetchContent_Declare(
    ravendb_client_cpp
    GIT_REPOSITORY https://github.com/ravendb/ravendb-cpp-client.git
    GIT_TAG master
    SOURCE_DIR ${RAVENCPP_SRC}/repository
    SUBBUILD_DIR ${RAVENCPP_SRC}/subbuild
    BINARY_DIR ${RAVENCPP_SRC}/binary
)
FetchContent_GetProperties(ravendb_client_cpp)
if(NOT ravendb_client_cpp_POPULATED)
    FetchContent_Populate(ravendb_client_cpp)
    #since we don't want to compile tests and tryouts, only the client
    set(BUILD_TRYOUTS OFF)
    set(BUILD_TESTS OFF)
    add_subdirectory("${RAVENCPP_SRC}/repository/"  "${RAVENCPP_SRC}/binary")
endif()

确保包含客户端的头文件文件夹。

list(APPEND CMAKE_INCLUDE_PATH ${RAVENCPP_SRC}/repository/Raven.CppClient)
set(CMAKE_INCLUDE_PATH ${CMAKE_INCLUDE_PATH}  CACHE  INTERNAL  "CMAKE_INCLUDE_PATH")

由于我们将 C++ 客户端编译为父应用程序的一部分,因此我们需要包含其依赖项(RavenDB 通过 vcpkg 管理这些依赖项)。

include_directories(${CMAKE_INCLUDE_PATH})
link_directories(${CMAKE_LIBRARY_PATH})

这将确保在下一步中,我们不仅可以链接到客户端,还可以链接到其所有依赖项。

注意:RavenDB 客户端在变量中添加了相关的头文件和库文件夹。

${CMAKE_INCLUDE_PATH}, ${CMAKE_LIBRARY_PATH}

RavenDB C++ 客户端使用 vcpkg。这是 Microsoft 创建的 C++ 包管理器,可以为 CMake 项目下载和编译依赖项。您可以在其 GitHub 仓库上阅读有关它的更多信息。

链接 (Linking)

现在我们已经获取了客户端源代码,我们需要将其链接到实际的项目(CMake 术语中的“子文件夹”),该项目将连接到 RavenDB。

在子文件夹的 `CMakeLists.txt` 中,添加以下 CMake 命令:

find_package(CURL REQUIRED)
find_package(OpenSSL REQUIRED)
find_package(CURL REQUIRED)
find_package(Threads REQUIRED)

最后,我们链接客户端及其依赖项。

add_executable ([executable name] "[executable name].cpp"  "[executable name].h")
target_include_directories([executable name] PUBLIC  ${CMAKE_INCLUDE_PATH})
target_link_libraries([executable name]
        Raven_CppClient_static #the RavenDB client
        OpenSSL::SSL
        OpenSSL::Crypto
        ${CURL_LIBRARIES}
        Threads::Threads)

© . All rights reserved.