Dapper 批量插入返回序列 ID

2024-01-07

我正在尝试使用 Dapper 通过 Npgsql 执行批量插入,这会返回新插入行的 id。我的两个示例中都使用了以下插入语句:

var query = "INSERT INTO \"MyTable\" (\"Value\") VALUES (@Value) RETURNING \"ID\"";

首先,我尝试添加具有“Value”属性的对象数组:

var values = new[] {
    new { Value = 0.0 },
    new { Value = 0.5 }
};
var ids = connection.Query<int>(query, values);

但是,失败并出现 NpgsqlException:“错误:42703:列“值”不存在”。看完之后这个问题 https://stackoverflow.com/questions/26279352/dapper-bulk-insert-of-new-items-and-get-back-new-ids,我想也许我必须传递一个 DataTable 对象而不是对象数组:

var dataTable = new DataTable();
dataTable.Columns.Add("Value", typeof(double));
dataTable.Rows.Add(0.0);
dataTable.Rows.Add(0.5);
var ids = connection.Query<int>(query, dataTable);

然而,这会失败并出现完全相同的异常。如何执行批量插入并通过 Npgsql 从 Dapper 中获取生成的序列 ID?

我确实注意到异常的大小写与列名不匹配,但我确信我在表和列名周围有引号,所以我不确定为什么它在例外。只是想我会提到它,以防它以某种方式与错误相关,因为很容易忽略大小写。

- 编辑 -

澄清一下,这是创建表的 SQL

CREATE TABLE "MyTable" (
    "ID" SERIAL PRIMARY KEY,
    "Value" DOUBLE PRECISION NOT NULL
);

使用上面定义的变量“查询”和“值”,这是在每行基础上工作的代码:

var ids = new List<int>();
foreach (var valueObj in values) {
    var queryParams = new DynamicParamaters();
    queryParams.Add("Value", valueObj.Value);
    ids.AddRange(connection.Query<int>(query, queryParams));
}

问题是我需要能够每秒将数百行(也许在不久的将来数千行)插入“MyTable”中,因此等待此循环迭代地将每个值发送到数据库是很麻烦的并且(我假设,但是尚未进行基准测试)耗时。此外,我对可能会或可能不会导致额外插入的值执行额外的计算,其中我需要对“MyTable”条目的外键引用。

由于这些问题,我正在寻找一种替代方案,将单个语句中的所有值发送到数据库,以减少网络流量和处理延迟。再说一次,我还没有对迭代方法进行基准测试......我正在寻找一种进行批量插入的替代方案,这样我就可以对两种方法进行相互比较。


最终,我想出了四种不同的方法来解决这个问题。我生成了 500 个随机值插入 MyTable,并对四种方法中的每一种进行了计时(包括启动和回滚运行该事务的事务)。在我的测试中,数据库位于本地主机上。然而,具有最佳性能的解决方案也只需要与数据库服务器进行一次往返,因此当部署到与数据库不同的服务器时,我发现的最佳解决方案仍然应该击败替代方案。

请注意变量connection and transaction在以下代码中使用,并假定为有效的 Npgsql 数据对象。另请注意,符号Nx 较慢表示操作花费的时间等于最优解乘以N.

方法 #1(1,494ms = 慢 18.7 倍):将数组展开为单独的参数

public List<MyTable> InsertEntries(double[] entries)
{
    // Create a variable used to dynamically build the query
    var query = new StringBuilder(
        "INSERT INTO \"MyTable\" (\"Value\") VALUES ");

    // Create the dictionary used to store the query parameters
    var queryParams = new DynamicParameters();

    // Get the result set without auto-assigned ids
    var result = entries.Select(e => new MyTable { Value = e }).ToList();

    // Add a unique parameter for each id
    var paramIdx = 0;
    foreach (var entry in result)
    {
        var paramName = string.Format("value{1:D6}", paramIdx);
        if (0 < paramIdx++) query.Append(',');
        query.AppendFormat("(:{0})", paramName);
        queryParams.Add(paramName, entry.Value);
    }
    query.Append(" RETURNING \"ID\"");

    // Execute the query, and store the ids
    var ids = connection.Query<int>(query, queryParams, transaction);
    ids.ForEach((id, i) => result[i].ID = id);

    // Return the result
    return result;
}

我真的不确定为什么这会是最慢的,因为它只需要到数据库的一次往返,但事实确实如此。

方法 #2(267ms = 慢 3.3 倍):标准循环迭代

public List<MyTable> InsertEntries(double[] entries)
{
    const string query =
        "INSERT INTO \"MyTable\" (\"Value\") VALUES (:val) RETURNING \"ID\"";

    // Get the result set without auto-assigned ids
    var result = entries.Select(e => new MyTable { Value = e }).ToList();

    // Add each entry to the database
    foreach (var entry in result)
    {
        var queryParams = new DynamicParameters();
        queryParams.Add("val", entry.Value);
        entry.ID = connection.Query<int>(
            query, queryParams, transaction);
    }

    // Return the result
    return result;
}

令我震惊的是,这仅比最佳解决方案慢 3.3 倍,但我预计在实际环境中情况会变得更糟,因为该解决方案需要串行发送 500 条消息到服务器。然而,这也是最简单的解决方案。

方法 #3(223ms = 慢 2.8 倍):异步循环迭代

public List<MyTable> InsertEntries(double[] entries)
{
    const string query =
        "INSERT INTO \"MyTable\" (\"Value\") VALUES (:val) RETURNING \"ID\"";

    // Get the result set without auto-assigned ids
    var result = entries.Select(e => new MyTable { Value = e }).ToList();

    // Add each entry to the database asynchronously
    var taskList = new List<Task<IEnumerable<int>>>();
    foreach (var entry in result)
    {
        var queryParams = new DynamicParameters();
        queryParams.Add("val", entry.Value);
        taskList.Add(connection.QueryAsync<int>(
            query, queryParams, transaction));
    }

    // Now that all queries have been sent, start reading the results
    for (var i = 0; i < result.Count; ++i)
    {
        result[i].ID = taskList[i].Result.First();
    }

    // Return the result
    return result;
}

这正在变得更好,但仍然不是最佳的,因为我们只能将与线程池中可用线程一样多的插入排队。然而,这几乎与非线程方法一样简单,因此它是速度和可读性之间的良好折衷。

方法 #4(134ms = 慢 1.7 倍):批量插入

这种方法需要在运行下面的代码段之前定义以下 Postgres SQL:

CREATE TYPE "MyTableType" AS (
    "Value" DOUBLE PRECISION
);

CREATE FUNCTION "InsertIntoMyTable"(entries "MyTableType"[])
    RETURNS SETOF INT AS $$

    DECLARE
        insertCmd TEXT := 'INSERT INTO "MyTable" ("Value") '
            'VALUES ($1) RETURNING "ID"';
        entry "MyTableType";
    BEGIN
        FOREACH entry IN ARRAY entries LOOP
            RETURN QUERY EXECUTE insertCmd USING entry."Value";
        END LOOP;
    END;
$$ LANGUAGE PLPGSQL;

以及相关代码:

public List<MyTable> InsertEntries(double[] entries)
{
    const string query =
        "SELECT * FROM \"InsertIntoMyTable\"(:entries::\"MyTableType\")";

    // Get the result set without auto-assigned ids
    var result = entries.Select(e => new MyTable { Value = e }).ToList();

    // Convert each entry into a Postgres string
    var entryStrings = result.Select(
        e => string.Format("({0:E16})", e.Value).ToArray();

    // Create a parameter for the array of MyTable entries
    var queryParam = new {entries = entryStrings};

    // Perform the insert
    var ids = connection.Query<int>(query, queryParam, transaction);

    // Assign each id to the result
    ids.ForEach((id, i) => result[i].ID = id);

    // Return the result
    return result;
}

我对这种方法有两个问题。首先,我必须对 MyTableType 成员的排序进行硬编码。如果该顺序发生变化,我必须修改此代码以匹配。第二个是我必须在将所有输入值发送到 postgres 之前将它们转换为字符串(在实际代码中,我有多个列,所以我不能只更改数据库函数的签名以采用双精度) precision[],除非我传入 N 个数组,其中 N 是 MyTableType 上的字段数)。

尽管存在这些缺陷,但这已经越来越接近理想状态,并且只需要与数据库进行一次往返。

-- 开始编辑 --

自最初的帖子以来,我提出了四种其他方法,它们都比上面列出的方法更快。我已经修改了Nx 较慢下面的数字反映了新的最快方法。

方法 #5(105ms = 慢 1.3 倍):与#4相同,没有动态查询

这种方法与方法#4对“InsertIntoMyTable”函数进行以下更改:

CREATE FUNCTION "InsertIntoMyTable"(entries "MyTableType"[])
    RETURNS SETOF INT AS $$

    DECLARE
        entry "MyTableType";
    BEGIN
        FOREACH entry IN ARRAY entries LOOP
            RETURN QUERY INSERT INTO "MyTable" ("Value")
                VALUES (entry."Value") RETURNING "ID";
        END LOOP;
    END;
$$ LANGUAGE PLPGSQL;

除了以下问题之外方法#4,这样做的缺点是,在生产环境中,“MyTable”是分区的。使用这种方法,我需要每个目标分区一种方法。

方法 #6(89ms = 慢 1.1 倍):带数组参数的插入语句

public List<MyTable> InsertEntries(double[] entries)
{
    const string query =
        "INSERT INTO \"MyTable\" (\"Value\") SELECT a.* FROM " +
            "UNNEST(:entries::\"MyTableType\") a RETURNING \"ID\"";

    // Get the result set without auto-assigned ids
    var result = entries.Select(e => new MyTable { Value = e }).ToList();

    // Convert each entry into a Postgres string
    var entryStrings = result.Select(
        e => string.Format("({0:E16})", e.Value).ToArray();

    // Create a parameter for the array of MyTable entries
    var queryParam = new {entries = entryStrings};

    // Perform the insert
    var ids = connection.Query<int>(query, queryParam, transaction);

    // Assign each id to the result
    ids.ForEach((id, i) => result[i].ID = id);

    // Return the result
    return result;
}

唯一的缺点与第一个问题相同方法#4。也就是说,它将实现与排序结合起来"MyTableType"。尽管如此,我发现这是我第二喜欢的方法,因为它非常快,并且不需要任何数据库函数即可正常工作。

方法#7(80ms = 非常慢):与 #1 相同,但不带参数

public List<MyTable> InsertEntries(double[] entries)
{
    // Create a variable used to dynamically build the query
    var query = new StringBuilder(
        "INSERT INTO \"MyTable\" (\"Value\") VALUES");

    // Get the result set without auto-assigned ids
    var result = entries.Select(e => new MyTable { Value = e }).ToList();

    // Add each row directly into the insert statement
    for (var i = 0; i < result.Count; ++i)
    {
        entry = result[i];
        query.Append(i == 0 ? ' ' : ',');
        query.AppendFormat("({0:E16})", entry.Value);
    }
    query.Append(" RETURNING \"ID\"");

    // Execute the query, and store the ids
    var ids = connection.Query<int>(query, null, transaction);
    ids.ForEach((id, i) => result[i].ID = id);

    // Return the result
    return result;
}

这是我最喜欢的方法。它仅比最快的慢一点(即使有 4000 条记录,它的运行时间仍然低于 1 秒),但不需要特殊的数据库函数或类型。我唯一不喜欢的是,我必须对双精度值进行字符串化,然后才能由 Postgres 再次解析。最好以二进制形式发送这些值,这样它们会占用 8 个字节,而不是我为它们分配的 20 个左右字节。

方法 #8(80 毫秒):与 #5 相同,但采用纯 sql

这种方法与方法#5对“InsertIntoMyTable”函数进行以下更改:

CREATE FUNCTION "InsertIntoMyTable"(
    entries "MyTableType"[]) RETURNS SETOF INT AS $$

    INSERT INTO "MyTable" ("Value")
        SELECT a.* FROM UNNEST(entries) a RETURNING "ID";
$$ LANGUAGE SQL;

这种方法与#5 一样,需要每个函数一个函数“我的桌子”分割。这是最快的,因为可以为每个函数生成一次查询计划,然后重复使用。在其他方法中,必须解析查询,然后计划,然后执行。尽管这是最快的,但由于数据库方面的额外要求,我没有选择它方法#7,速度优势非常小。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Dapper 批量插入返回序列 ID 的相关文章

  • 将 ARGB 拆分为字节值

    我有一个 ARGB 值存储为 int 类型 它是通过调用 ToArgb 来存储的 我现在想要来自 int 值的各个颜色通道的字节值 例如 int mycolor 16744448 byte r g b a GetBytesFromColor
  • 如何创建语法突出显示文本框[关闭]

    Closed 这个问题需要多问focused help closed questions 目前不接受答案 如何使用 C Net 创建语法突出显示文本框 Take 闪烁网 http scintillanet codeplex com 并采取其
  • 如何转发声明要在 unique_ptr 的标准容器中使用的类

    在智能指针的标准容器中使用它时 是否可以避免完整的类定义可见 例如 我无法编译以下内容 include
  • 错误:“运行所选代码生成器时出错:包恢复失败”

    我正在尝试将控制器添加到 ASP NET Core 项目中的解决方案中 当我尝试这样做时 我收到此错误 我收到相同的消息 为控制器添加最小依赖项和完整依赖项 我也有这个问题 使用实体框架添加控制器 gt 带有操作的 API 控制器 将给出
  • 等待运算符错误

    我的代码有问题 我怎么解决这个问题 这个问题出现在await操作符中 public MyModel HttpClient client new HttpClient HttpResponseMessage response await cl
  • Monitor.Pulse & Wait - 意外行为

    http www codeproject com Articles 28785 Thread synchronization Wait and Pulse demystified http www codeproject com Artic
  • 带有嵌入 Flash 视频的 PDF 示例?

    有谁知道我在哪里可以查看嵌入 Flash 视频的 PDF 示例 我知道问这个问题很愚蠢 因为你会认为任何面向技术的用户都应该能够使用谷歌找到一个 但我真的找不到 我的另一个问题是 使用 C 中的 API 将 Flash 视频嵌入 PDF 文
  • 将视频上传/保存到数据库或文件系统

    我以前从未尝试过保存视频 所以我对此了解不多 我知道如果视频很小 我可以转换为字节数组并保存到数据库 但是为了提高效率 我想了解如何将任何上传的视频保存到我的服务器文件中 然后只保存该文件的文件路径我的数据库表中的视频 我完全不知道如何开始
  • 我如何知道向量的实际最大大小? (不使用 std::vector::max_size)

    在在线课程中 我正在学习向量 在其中一个例子中 他们解释说 std vector max size 应该给我向量可以达到的最大大小 我决定测试一下 include
  • 用 OpenCL C 编写快速线性系统求解器

    我正在编写一个 OpenCL 内核 它将涉及求解线性系统 目前我的内核太慢了 提高线性系统部分的性能似乎是一个不错的起点 我还应该注意 我并没有尝试使我的线性求解器并行 我正在研究的问题在宏观层面上已经是令人尴尬的并行 以下是我编写的 C
  • Web浏览器控件:如何捕获文档事件?

    我正在使用 WPF 的 WebBrowser 控件加载一个简单的网页 在这个页面上我有一个锚点或一个按钮 我想在我的应用程序后面的代码中 即在 C 中 捕获该按钮的单击事件 WebBrowser 控件是否有办法捕获加载页面元素上的单击事件
  • Cookie 在 ASP.net 中失去价值

    我有以下设置 cookie 的代码 string locale DropDownList this LoginUser FindControl locale SelectedValue HttpCookie cookie new HttpC
  • FFplay成功移入我的Winform中,如何设置它无边框?

    用这个代码 在 C 应用程序中显示 tcp 视频流 来自 FFPLAY FFMPEG https stackoverflow com questions 14201894 show a tcp video stream from ffpla
  • 在 Linq 查询中使用动态列名称

    foreach Dimension dimensions in Enum GetValues typeof Dimension var r new ReferenceTable dimensions referenceItems List
  • ef core 在更新数据库期间不使用 ASPNETCORE_ENVIRONMENT

    我使用 Visual Studio 通过一定的迁移来更新我的所有环境 使用下面的命令效果很好 update database Migration initMigrationProduct c ProductContext Environme
  • 如何在 C 语言中获取输入中的空格

    我想从控制台获取字符数组 它还包含空格 我在 C 中知道的唯一方法是 scanf 但是一旦遇到空格 它就会停止接受输入 我该做什么 这就是我正在做的事情 char address 100 scanf s address 尝试使用 fgets
  • 从 AuthorizeAttribute 继承的属性不起作用

    我目前正在尝试根据用户角色在新的 ASP MVC 5 应用程序中实现安全性 目标是防止用户在没有特定角色 或更高角色 的情况下访问某些控制器或控制器方法 根据到目前为止我所读到的问题 我创建了一个继承 AuthorizeAttribute
  • 如何解决 boost::multi precision::cpp_dec_float 除法错误

    除以boost multiprecision cpp dec float有某种舍入误差 如下 include
  • 为什么我无法通过 lambda 捕获“this”指针?

    考虑以下代码 class A public void foo auto functor this A a this auto functor a The compiler won t accept this instead of a a g
  • 如何使用 .NET 捕获我的桌面视频?

    我想知道是否有任何方法可以使用 NET 捕获我的桌面的视频 截屏视频 我并不是在寻找截屏软件 而只是在寻找一种可以让我自己生成桌面视频的技术 我想过拍摄多个屏幕截图 但我不确定如何以编程方式生成带有图像序列的视频 有人有主意吗 Thanks

随机推荐