将DataTable bulk的整个值插入postgreSQL表
在SQL中,我们执行类似这样的操作来批量插入数据表
SqlBulkCopy copy = new SqlBulkCopy(sqlCon); copy.DestinationTableName = strDestinationTable; copy.WriteToServer(dtFrom); Blockquote
但是在PostgreSQL中如何做这个操作
使用参数简单插入
您的项目需要引用以下程序集: Npgsql
。 如果此引用在Visual Studio中不可见,则:
- 浏览到连接器的安装文件夹
- 执行:
GACInstall.exe
- 重新启动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();
对绩效的思考
原始post未提及性能要求。 要求解决方案必须:
- 使用
DataTable
插入 - 不使用循环插入数据
如果您要插入大量数据,那么我建议您查看一下您的性能选项。 Postgres文档建议您:
- 禁用自动提交
- 使用
COPY
命令 - 删除索引
- 删除外键约束
- 等等
有关优化Postgres插入的更多信息,请查看:
- PostgresSql.org:插入数据
- PostgresSql.org:插入+性能提示
- StackOverflow:如何加快PostgreSQL中的插入性能
此外,还有许多其他因素会影响系统的性能。 有关高级别的介绍,请查看:
- ADO.NET SQL Server性能瓶颈
- 该post概述了用于优化性能的一般(即非SqlServer)策略。
其他选择
- .NET连接器是否支持Postgres
Copy
命令?- 如果没有,您可以下载
Npgsql
连接器的源代码并添加您自己的BulkCopy()
方法。 请务必先查看源代码的许可协议。
- 如果没有,您可以下载
- 检查Postgres是否支持表值参数 。
- 这种方法允许您将表传入
Postgres
函数,然后可以将数据直接插入目标。
- 这种方法允许您将表传入
- 从供应商处购买Postgres .NET连接器,其中包括所需的function。
其他参考文献
- Postgres .NET Connector – 免费和开源
我前段时间遇到同样的问题。 到目前为止,似乎还没有“随时可用”的解决方案。
我已经阅读过这篇文章,并在那时构建了一个类似的解决方案,直到今天仍在使用。 它基于文本查询,从STDIN读取文件。 它使用ADO.NET Postgre数据提供程序Npgsql 。 您可以根据DataTable创建一个大字符串(或临时文件,内存使用原因),并使用COPY命令将其用作文本查询。 在我们的例子中,它比inser教学行快得多。
也许这不是一个完整的解决方案,但可能是一个很好的开始和我所知道的任何事情。 🙂
我还发现,还没有“随时可用”的解决方案。 可能你可以查看我的另一个答案,其中我描述了我为这个问题创建的一个小帮手,使用另一个帮助器非常简单: https : //stackoverflow.com/a/46063313/6654362我认为这是目前最好的解决方案。 如果post死了,我从链接发布了解决方案。
编辑:我最近遇到了类似的问题,但我们使用的是Postgresql。 我想使用有效的bulkinsert,结果很难。 我没有在这个DB上找到任何适当的免费库。 我只找到了这个帮助器: https ://bytefish.de/blog/postgresql_bulk_insert/,它也在Nuget上。 我编写了一个小的映射器,它以entity framework的方式自动映射属性:
public static PostgreSQLCopyHelper CreateHelper (string schemaName, string tableName) { var helper = new PostgreSQLCopyHelper ("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("dbo", nameof(Model.Undertaking)); undertakingHelper.SaveAll(transaction.UnderlyingTransaction.Connection as Npgsql.NpgsqlConnection, undertakingsToAdd));
我展示了一个带事务的示例,但也可以通过从上下文中检索的普通连接来完成。 undertakingsToAdd是可枚举的普通实体记录,我想将其插入到DB中。
经过几个小时的研究和尝试,我已经得到了这个解决方案,你可以期待更快,最后易于使用和免费! 我真的建议你使用这个解决方案,不仅仅是出于上面提到的原因,而且因为它是我唯一一个与Postgresql本身没有问题的解决方案,许多其他解决方案可以完美地工作,例如使用SqlServer。
你真的想使用DataTable吗? 如果您可以使用EF Code First,那么您可以使用本文中的代码。 本文的想法是使用Npgsql COPY作为基础+从DB本身获取元数据,将其与模型类中的元数据合并,并动态生成用于写入数据的代码。
用法是这样的:
var uploader = new NpgsqlBulkUploader(context); var data = GetALotOfData(); uploader.Insert(data); // OR uploader.Update(data);