在 npgsql 中准备语句和批处理
Preparing statements and batching in npgsql
文档 (https://www.npgsql.org/doc/prepare.html#simple-preparation) 中的 Simple Preparation
示例显示了在命令准备好后设置参数的示例。
var cmd = new NpgsqlCommand(...);
cmd.Parameters.Add("param", NpgsqlDbType.Integer);
cmd.Prepare();
// Set parameters
cmd.ExecuteNonQuery();
// And so on
问题
- 参数是怎么设置的?
- 如果使用指定
NpgsqlDbType
的 AddWithValue(String, NpgsqlDbType, Object)
方法,是否可以使用 AddWithValue
而不是 Add
-- 文档说 "setting the value isn't support"?
- 如果同一命令中存在多个语句,这将如何工作?
这个答案 () 表明一个字符串中的多个命令可以一起准备,但不清楚这个 CommandText 字符串是如何创建的。
编辑:我想我快到了,但我不确定如何创建和执行批处理的查询字符串。这是我使用 StringBuilder 构建批处理查询的幼稚尝试。这是行不通的。我该如何正确执行此操作?
using System;
using System.Collections.Generic;
using System.Text;
using Npgsql;
using NpgsqlTypes;
class Model
{
public int value1 { get; }
public int value2 { get; }
public Model(int value1, int value2)
{
this.value1 = value1;
this.value2 = value2;
}
}
class Program
{
static void Main(string[] args)
{
var dataRows = new List<Model>();
dataRows.Add(new Model(3,2));
dataRows.Add(new Model(27,-10));
dataRows.Add(new Model(11,-11));
var connString = "Host=127.0.0.1;Port=5432;Username=postgres;Database=dbtest1";
// tabletest1
// ----------
// id SERIAL PRIMARY KEY
// , value1 INT NOT NULL
// , value2 INT NOT NULL
using (var conn = new NpgsqlConnection(connString))
{
conn.Open();
var cmd = new NpgsqlCommand();
cmd.Connection = conn;
cmd.CommandText = $"INSERT INTO tabletest1 (value1,value2) VALUES (@value1,@value2)";
var parameterValue1 = cmd.Parameters.Add("value1", NpgsqlDbType.Integer);
var parameterValue2 = cmd.Parameters.Add("value2", NpgsqlDbType.Integer);
cmd.Prepare();
var batchCommand = new StringBuilder();
foreach (var d in dataRows)
{
parameterValue1.Value = d.value1;
parameterValue2.Value = d.value2;
batchCommand.Append(cmd.CommandText);
batchCommand.Append(";");
}
Console.WriteLine(batchCommand.ToString());
// conn.ExecuteNonQuery(batchCommand.ToString());
}
}
}
1) 只需捕获从Add()
返回的NpgsqlParameter,然后设置其值属性:
var p = cmd.Parameters.Add("p", NpgsqlDbType.Integer);
cmd.Prepare();
p.Value = 8;
cmd.ExecuteNonQuery();
2) 您可以以相同的方式使用 AddWithValue()
,但是如果您正在准备命令以便多次重复使用它,那就没有意义了。思路是先加没有值的参数,然后prepare,再执行几次,每次都设置值。
3) 可以编写多语句命令。现在一切正常,命令中的所有语句将共享相同的参数列表(位于 NpgsqlCommand 上)。所以相同的模式适用:使用 SQL 和参数创建命令,准备它,然后设置参数值并执行。您的命令中的每个单独语句都将 运行 准备好,受益于性能提升。
这是一个双语句批处理的示例:
cmd.CommandText = "INSERT INTO tabletest1 (value1,value2) VALUES (@v1,@v2); INSERT INTO tabletest1 (value1, value2) VALUES (@v3,@v4)";
var v1 = cmd.Parameters.Add("v1", NpgsqlDbType.Integer);
var v2 = cmd.Parameters.Add("v2", NpgsqlDbType.Integer);
var v3 = cmd.Parameters.Add("v3", NpgsqlDbType.Integer);
var v4 = cmd.Parameters.Add("v4", NpgsqlDbType.Integer);
cmd.Prepare();
while (...) {
v1.Value = ...;
v2.Value = ...;
v3.Value = ...;
v4.Value = ...;
cmd.ExecuteNonQuery();
}
但是,如果 objective 是为了高效地插入大量数据,consider using COPY instead - 它甚至比批量插入更快。
最后,为了完成图片,对于 INSERT 语句,您可以在单个语句中包含多个行:
INSERT INTO tabletest1 (value1, value2) VALUES (1,2), (3,4)
您也可以再次参数化实际值,并准备此命令。这类似于批处理两个 INSERT 语句,并且应该更快(尽管仍然比 COPY 慢)。
在 NpgSQL 6.0 中添加了 batching/pipelining。
这是一个更新的例子:
await using var connection = new NpgsqlConnection(connString);
await connection.OpenAsync();
var batch = new NpgsqlBatch(connection);
const int count = 10;
const string parameterName = "parameter";
for (int i = 0; i < count; i++)
{
var batchCommand = new NpgsqlBatchCommand($"SELECT @{parameterName} as value");
batchCommand.Parameters.Add(new NpgsqlParameter(parameterName, i));
batch.BatchCommands.Add(batchCommand);
}
await batch.PrepareAsync();
var results = new List<int>(count);
await using (var reader = await batch.ExecuteReaderAsync())
{
do
{
while (await reader.ReadAsync())
{
results.Add(await reader.GetFieldValueAsync<int>("value"));
}
} while (await reader.NextResultAsync());
}
Console.WriteLine(string.Join(", ", results));
文档 (https://www.npgsql.org/doc/prepare.html#simple-preparation) 中的 Simple Preparation
示例显示了在命令准备好后设置参数的示例。
var cmd = new NpgsqlCommand(...);
cmd.Parameters.Add("param", NpgsqlDbType.Integer);
cmd.Prepare();
// Set parameters
cmd.ExecuteNonQuery();
// And so on
问题
- 参数是怎么设置的?
- 如果使用指定
NpgsqlDbType
的AddWithValue(String, NpgsqlDbType, Object)
方法,是否可以使用AddWithValue
而不是Add
-- 文档说 "setting the value isn't support"? - 如果同一命令中存在多个语句,这将如何工作?
这个答案 (
编辑:我想我快到了,但我不确定如何创建和执行批处理的查询字符串。这是我使用 StringBuilder 构建批处理查询的幼稚尝试。这是行不通的。我该如何正确执行此操作?
using System;
using System.Collections.Generic;
using System.Text;
using Npgsql;
using NpgsqlTypes;
class Model
{
public int value1 { get; }
public int value2 { get; }
public Model(int value1, int value2)
{
this.value1 = value1;
this.value2 = value2;
}
}
class Program
{
static void Main(string[] args)
{
var dataRows = new List<Model>();
dataRows.Add(new Model(3,2));
dataRows.Add(new Model(27,-10));
dataRows.Add(new Model(11,-11));
var connString = "Host=127.0.0.1;Port=5432;Username=postgres;Database=dbtest1";
// tabletest1
// ----------
// id SERIAL PRIMARY KEY
// , value1 INT NOT NULL
// , value2 INT NOT NULL
using (var conn = new NpgsqlConnection(connString))
{
conn.Open();
var cmd = new NpgsqlCommand();
cmd.Connection = conn;
cmd.CommandText = $"INSERT INTO tabletest1 (value1,value2) VALUES (@value1,@value2)";
var parameterValue1 = cmd.Parameters.Add("value1", NpgsqlDbType.Integer);
var parameterValue2 = cmd.Parameters.Add("value2", NpgsqlDbType.Integer);
cmd.Prepare();
var batchCommand = new StringBuilder();
foreach (var d in dataRows)
{
parameterValue1.Value = d.value1;
parameterValue2.Value = d.value2;
batchCommand.Append(cmd.CommandText);
batchCommand.Append(";");
}
Console.WriteLine(batchCommand.ToString());
// conn.ExecuteNonQuery(batchCommand.ToString());
}
}
}
1) 只需捕获从Add()
返回的NpgsqlParameter,然后设置其值属性:
var p = cmd.Parameters.Add("p", NpgsqlDbType.Integer);
cmd.Prepare();
p.Value = 8;
cmd.ExecuteNonQuery();
2) 您可以以相同的方式使用 AddWithValue()
,但是如果您正在准备命令以便多次重复使用它,那就没有意义了。思路是先加没有值的参数,然后prepare,再执行几次,每次都设置值。
3) 可以编写多语句命令。现在一切正常,命令中的所有语句将共享相同的参数列表(位于 NpgsqlCommand 上)。所以相同的模式适用:使用 SQL 和参数创建命令,准备它,然后设置参数值并执行。您的命令中的每个单独语句都将 运行 准备好,受益于性能提升。
这是一个双语句批处理的示例:
cmd.CommandText = "INSERT INTO tabletest1 (value1,value2) VALUES (@v1,@v2); INSERT INTO tabletest1 (value1, value2) VALUES (@v3,@v4)";
var v1 = cmd.Parameters.Add("v1", NpgsqlDbType.Integer);
var v2 = cmd.Parameters.Add("v2", NpgsqlDbType.Integer);
var v3 = cmd.Parameters.Add("v3", NpgsqlDbType.Integer);
var v4 = cmd.Parameters.Add("v4", NpgsqlDbType.Integer);
cmd.Prepare();
while (...) {
v1.Value = ...;
v2.Value = ...;
v3.Value = ...;
v4.Value = ...;
cmd.ExecuteNonQuery();
}
但是,如果 objective 是为了高效地插入大量数据,consider using COPY instead - 它甚至比批量插入更快。
最后,为了完成图片,对于 INSERT 语句,您可以在单个语句中包含多个行:
INSERT INTO tabletest1 (value1, value2) VALUES (1,2), (3,4)
您也可以再次参数化实际值,并准备此命令。这类似于批处理两个 INSERT 语句,并且应该更快(尽管仍然比 COPY 慢)。
在 NpgSQL 6.0 中添加了 batching/pipelining。
这是一个更新的例子:
await using var connection = new NpgsqlConnection(connString);
await connection.OpenAsync();
var batch = new NpgsqlBatch(connection);
const int count = 10;
const string parameterName = "parameter";
for (int i = 0; i < count; i++)
{
var batchCommand = new NpgsqlBatchCommand($"SELECT @{parameterName} as value");
batchCommand.Parameters.Add(new NpgsqlParameter(parameterName, i));
batch.BatchCommands.Add(batchCommand);
}
await batch.PrepareAsync();
var results = new List<int>(count);
await using (var reader = await batch.ExecuteReaderAsync())
{
do
{
while (await reader.ReadAsync())
{
results.Add(await reader.GetFieldValueAsync<int>("value"));
}
} while (await reader.NextResultAsync());
}
Console.WriteLine(string.Join(", ", results));