【问题标题】:Insert the whole value of DataTable bulk into postgreSQL table将 DataTable bulk 的整个值插入到 postgreSQL 表中
【发布时间】:2015-07-30 23:22:20
【问题描述】:

在 SQL 中,我们为批量插入数据表执行类似的操作

SqlBulkCopy copy = new SqlBulkCopy(sqlCon);
copy.DestinationTableName = strDestinationTable;            
copy.WriteToServer(dtFrom);

Blockquote

但是在PostgreSQL中怎么做这个操作

【问题讨论】:

  • 伟大的文章先生 @wingedpanther 仍然不明白这一点,因为要求是“DataTable”数据直接插入到表中而没有循环在 c# MVC DataTable 中包含超过 1 条 lac 记录
  • 对于PostgreSQL数据库中的批量插入,您可以使用COPY,将数据导入csv(避免数据表)然后copy它们从csvtable
  • 您可能想将其转换为CSV,然后插入所需的位置。还是不行?

标签: c# asp.net-mvc postgresql datatable


【解决方案1】:

我之前也遇到过同样的问题。到目前为止,似乎还没有“即用型”解决方案。

我已经阅读了this 的帖子,并在当时构建了一个类似的解决方案,直到今天仍在生产中使用。它基于从 STDIN 读取文件的文本查询。它使用 ADO.NET Postgre 数据提供程序Npgsql。您可以根据您的 DataTable 创建一个大字符串(或临时文件,内存使用的原因),并使用 COPY 命令将该字符串用作文本查询。在我们的例子中,它比插入教行快得多。

也许这不是一个完整的解决方案,但可能是一个很好的起点以及我所知道的任何事情。 :)

【讨论】:

  • 需要登录
  • 是的,该文章似乎已被删除。
  • 能否提供新链接,如果有的话
  • @HamidKhan 对不起,我不能。这是 Alexander Kuznetsov 于 2013 年在 SQLblog.com 上发表的系列文章,现已被删除。我猜它已经过时了。
【解决方案2】:

使用参数的简单插入

您的项目将需要引用以下程序集:Npgsql。如果此引用在 Visual Studio 中不可见,则:

  1. 浏览到连接器的安装文件夹
  2. 执行:GACInstall.exe
  3. 重新启动 Visual Studio

样本表

CREATE TABLE "OrderHistory"
(
  "OrderId" bigint NOT NULL,
  "TotalAmount" bigint,
  CONSTRAINT "OrderIdPk" PRIMARY KEY ("OrderId")
)
WITH (
  OIDS=FALSE
);
ALTER TABLE "OrderHistory"
  OWNER TO postgres;
GRANT ALL ON TABLE "OrderHistory" TO postgres;
GRANT ALL ON TABLE "OrderHistory" TO public;
ALTER TABLE "OrderHistory" ALTER COLUMN "OrderId" SET (n_distinct=1);

GRANT SELECT("OrderId"), UPDATE("OrderId"), INSERT("OrderId"), REFERENCES("OrderId") ON "OrderHistory" TO public;
GRANT SELECT("TotalAmount"), UPDATE("TotalAmount"), INSERT("TotalAmount"), REFERENCES("TotalAmount") ON "OrderHistory" TO public;

示例代码

请务必使用以下指令:

using Npgsql;
using NpgsqlTypes;

在您的方法中输入以下源代码:

// Make sure that the user has the INSERT privilege for the OrderHistory table.
NpgsqlConnection connection = new NpgsqlConnection("PORT=5432;TIMEOUT=15;POOLING=True;MINPOOLSIZE=1;MAXPOOLSIZE=20;COMMANDTIMEOUT=20;COMPATIBLE=2.2.4.3;DATABASE=test;HOST=127.0.0.1;PASSWORD=test;USER ID=test");

connection.Open();

DataSet dataSet = new DataSet();

NpgsqlDataAdapter dataAdapter = new NpgsqlDataAdapter("select * from OrderHistory where OrderId=-1", connection);
dataAdapter.InsertCommand = new NpgsqlCommand("insert into OrderHistory(OrderId, TotalAmount) " +
                        " values (:a, :b)", connection);
dataAdapter.InsertCommand.Parameters.Add(new NpgsqlParameter("a", NpgsqlDbType.Bigint));
dataAdapter.InsertCommand.Parameters.Add(new NpgsqlParameter("b", NpgsqlDbType.Bigint));
dataAdapter.InsertCommand.Parameters[0].Direction = ParameterDirection.Input;
dataAdapter.InsertCommand.Parameters[1].Direction = ParameterDirection.Input;
dataAdapter.InsertCommand.Parameters[0].SourceColumn = "OrderId";
dataAdapter.InsertCommand.Parameters[1].SourceColumn = "TotalAmount";

dataAdapter.Fill(dataSet);

DataTable newOrders = dataSet.Tables[0];
DataRow newOrder = newOrders.NewRow();
newOrder["OrderId"] = 20;
newOrder["TotalAmount"] = 20.0;

newOrders.Rows.Add(newOrder);
DataSet ds2 = dataSet.GetChanges();
dataAdapter.Update(ds2);
dataSet.Merge(ds2);
dataSet.AcceptChanges();

connection.Close();

对性能的思考

原始帖子未提及性能要求。要求解决方案必须:

  1. 使用DataTable 插入
  2. 不使用循环插入数据

如果您要插入大量数据,那么我建议您查看一下您的性能选项。 Postgres 文档建议您:

  • 禁用自动提交
  • 使用COPY 命令
  • 删除索引
  • 删除外键约束

有关优化 Postgres 插入的更多信息,请查看:

此外,还有许多其他因素会影响系统的性能。有关高级介绍,请查看:

其他选项

  • .NET 连接器是否支持 Postgres Copy 命令?
    • 如果没有,您可以下载source codeNpgsql 连接器并添加您自己的BulkCopy() 方法。请务必先查看源代码的许可协议。
  • 检查Postgres 是否支持表值参数
    • 这种方法允许您将表传递到 Postgres 函数中,然后该函数可以将数据直接插入到目标中。
  • 从供应商处购买包含所需功能的 Postgres .NET 连接器。

其他参考资料

【讨论】:

  • 我认为这是最好的解决方案。但还要设置 dataAdapter.UpdateBatchSize 并启动一个事务 (connection.BeginTransaction())。对于 Mysql,这提供了巨大的提升,大约是 30 倍。
  • 我遇到了这个异常。任何想法? -> InvalidOperationException:当传递带有修改行的 DataRow 集合时,更新需要有效的 UpdateCommand。
  • 好的。我尝试使用 newOrders.Merge(dt); 添加行但它没有用(上述评论除外)。所以我仍然必须遍历数据表来添加行。
【解决方案3】:

我还发现,目前还没有“即用型”解决方案。也许您可以查看我的其他答案,其中我描述了我为这个问题创建的一个小助手,使用另一个助手非常容易:https://stackoverflow.com/a/46063313/6654362 我认为这是目前最好的解决方案。 我从链接中发布了解决方案,以防帖子死亡。

编辑: 我最近遇到了类似的问题,但我们使用的是 Postgresql。我想使用有效的bulkinsert,结果非常困难。我还没有在这个数据库上找到任何合适的免费库。我只找到了这个助手: https://bytefish.de/blog/postgresql_bulk_insert/ 这也在 Nuget 上。我写了一个小映射器,它以实体框架的方式自动映射属性:

public static PostgreSQLCopyHelper<T> CreateHelper<T>(string schemaName, string tableName)
        {
            var helper = new PostgreSQLCopyHelper<T>("dbo", "\"" + tableName + "\"");
            var properties = typeof(T).GetProperties();
            foreach(var prop in properties)
            {
                var type = prop.PropertyType;
                if (Attribute.IsDefined(prop, typeof(KeyAttribute)) || Attribute.IsDefined(prop, typeof(ForeignKeyAttribute)))
                    continue;
                switch (type)
                {
                    case Type intType when intType == typeof(int) || intType == typeof(int?):
                        {
                            helper = helper.MapInteger("\"" + prop.Name + "\"",  x => (int?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type stringType when stringType == typeof(string):
                        {
                            helper = helper.MapText("\"" + prop.Name + "\"", x => (string)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type dateType when dateType == typeof(DateTime) || dateType == typeof(DateTime?):
                        {
                            helper = helper.MapTimeStamp("\"" + prop.Name + "\"", x => (DateTime?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type decimalType when decimalType == typeof(decimal) || decimalType == typeof(decimal?):
                        {
                            helper = helper.MapMoney("\"" + prop.Name + "\"", x => (decimal?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type doubleType when doubleType == typeof(double) || doubleType == typeof(double?):
                        {
                            helper = helper.MapDouble("\"" + prop.Name + "\"", x => (double?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type floatType when floatType == typeof(float) || floatType == typeof(float?):
                        {
                            helper = helper.MapReal("\"" + prop.Name + "\"", x => (float?)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                    case Type guidType when guidType == typeof(Guid):
                        {
                            helper = helper.MapUUID("\"" + prop.Name + "\"", x => (Guid)typeof(T).GetProperty(prop.Name).GetValue(x, null));
                            break;
                        }
                }
            }
            return helper;
        }

我按以下方式使用它(我有一个名为 Undertaking 的实体):

var undertakingHelper = BulkMapper.CreateHelper<Model.Undertaking>("dbo", nameof(Model.Undertaking));
undertakingHelper.SaveAll(transaction.UnderlyingTransaction.Connection as Npgsql.NpgsqlConnection, undertakingsToAdd));

我展示了一个带有事务的示例,但它也可以通过从上下文中检索到的正常连接来完成。 takingsToAdd 是可枚举的普通实体记录,我想将它们批量插入到数据库中。

经过几个小时的研究和尝试,我得到了这个解决方案,正如您所期望的那样,它的速度要快得多,而且最终易于使用且免费!我真的建议你使用这个解决方案,不仅因为上面提到的原因,而且因为它是唯一一个我对 Postgresql 本身没有问题的解决方案,许多其他解决方案都可以完美地工作,例如 SqlServer。

【讨论】:

    猜你喜欢
    • 2016-08-21
    • 2015-06-06
    • 1970-01-01
    • 2014-01-24
    • 1970-01-01
    • 2020-03-31
    • 2011-09-17
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多