如何通过 Poco::Data 从数据库读取值(可能为 NULL)到 std::vector?

How to read values (possibly NULL) from database into std::vector via Poco::Data?

我想从 SQL-Server 数据库中读取许多行(值可能为 NULL)到 std::vector。读取单个值 NULL 和非 NULL 都有效。但是我在读取多个值时遇到问题。

根据 POCO Data User Guide,我想出了以下代码:

之前的样板代码:

Poco::Data::ODBC::Connector::registerConnector();
Poco::Data::Session session("ODBC", makeConnectionString());
Poco::Data::Statement select(session);

代码ok,读取单个值:

// note initialization here: otherwise ODBCHandleException will be thrown on non-NULL-value
Poco::Nullable<std::string> n = std::string("");

select << "SELECT col_name FROM table_name WHERE id=6;", into(n);
select.execute();

代码不正常,读取多个值,抛出Poco::Data::ODBC::HandleException

// how to initialize here?
std::vector<Poco::Nullable<std::string>> ns;

select << "SELECT col_name FROM table_name;", into(ns);
select.execute(); // <- error thrown here

有什么可以修复我的代码的建议吗?

尝试了很多方法。最后我用 Poco::Data::RecordSet 替换了 std::vector。现在我可以遍历它的 Poco::Data::Rows,这反过来又给我输入了 Poco::Dynamic::Var 的列值。这里 Poco::Dynamic::Var::isEmpty() 让我检查数据库条目是否为 NULL。

请参阅简化的代码进行说明:

Poco::Data::ODBC::Connector::registerConnector();
Poco::Data::Session session("ODBC", makeConnectionString());

Poco::Data::Statement select(session);
select << "SELECT col_name FROM table_name";
select.execute();

Poco::Data::RecordSet rows(select);        // use Recordset here instead of vector...

std::vector<std::string> beans;            // ...and convert it to vector...

auto convert = [&](Poco::Data::Row row)    // ...with this lambda function.
{
    const Poco::Dynamic::Var var = row["col_name"];

    if (var.isEmpty())                     // convert NULL values to empty string
        return std::string("");

    return var.convert<std::string>();
};

std::transform(rows.begin(), rows.end(), std::back_inserter(beans), convert);

for (const auto& b : beans)
    std::cout << b << std::endl;

好的是,它可以扩展到 更复杂的 版本(如果您非常感兴趣,请阅读):

class GenericTableManager
{
public:
    GenericTableManager(Poco::Data::Session& session, const std::string& tableName);

    template <class T_Bean>
    std::vector<T_Bean> selectAll(std::function<T_Bean(Poco::Data::Row)> convert) const
    {
        Poco::Data::Statement select(m_session);
        select << "SELECT * FROM " << m_tableName;
        select.execute();

        Poco::Data::RecordSet rows(select);

        std::vector<T_Bean> beans;
        std::transform(rows.begin(), rows.end(), std::back_inserter(beans), convert);
        return beans;
    }

    template <class T_Bean>
    size_t insert(
        const T_Bean& bean,
        std::function<Poco::Data::Row(const T_Bean&)> convert) const
    {
        const Poco::Data::Row row = convert(bean);

        Poco::Data::Statement stmt(m_session);
        stmt << "INSERT INTO " << m_tableName
            << " (" << StringUtil::join(*row.names()) << ") "
            << "VALUES (" << placeholders(row.fieldCount()) << ")";

        for (const auto& value : row.values())
            stmt.addBind(Poco::Data::Keywords::bind(value));

        return stmt.execute();
    }
    
private:
    Poco::Data::Session& m_session;
    const std::string m_tableName;

    static std::string placeholders(const size_t n);
};


// on caller side:

// once you have your ORM converters set up...
auto convertOOtoR = [](const MyBean& bean) -> Poco::Data::Row {
    Poco::Data::Row row;
    row.append("myattr1", bean.myattr1);
    row.append("myattr2", bean.myattr2);
    return row;
};

auto convertRtoOO = [](Poco::Data::Row row) -> MyBean{
    MyBean bean;
    Poco::Dynamic::Var var = row["myattr1"];
    bean.myattr1 = var.isEmpty() ? "" : var.convert<std::string>();
    // ...
    return bean;
};

// ...the SQL part boils down to a oneliner
GenericTableManager tm(session, "mytablename");
tm.insert<MyBean>(createMyBean(), convertOOtoR);
std::vector<MyBean> beans = tm.selectAll<MyBean>(convertRtoOO);