来自 List<> 的 SqlBulkCopy

Posted

技术标签:

【中文标题】来自 List<> 的 SqlBulkCopy【英文标题】:SqlBulkCopy from a List<> 【发布时间】:2011-04-24 05:23:20 【问题描述】:

如何从简单对象的 List 中使用 SqlBulkCopy 进行大插入?

我是否实现了我的自定义 IDataReader ?

【问题讨论】:

迟到了,但是如果你添加这个 EntityDataReader 类,有一个 AsDataReader() 扩展方法可以做到这一点:github.com/matthewschrager/Repository/blob/master/… (完整实施请参见下面的新答案) 【参考方案1】:

只需从您的对象列表中调用create a DataTable 并调用SqlBulkCopy.WriteToServer,传递数据表。

您可能会发现以下有用:

Adding columns to a DataTable。为您要编写的每个属性/字段添加一列。 Adding rows to a DataTable。为列表中的每个对象添加一行。

为获得 SqlBulkCopy 的最佳性能,您应该设置一个适当的 BatchSize。 10,000 似乎效果很好 - 但可以为您的数据配置文件。

使用SqlBulkCopyOptions.TableLock 时,您可能还会观察到更好的结果。

可以在here 找到有关 SqlBulkCopy 性能的有趣且内容丰富的分析。

【讨论】:

【参考方案2】:

使用FastMember,您无需通过DataTable 就可以做到这一点(在我的测试中,性能提高了一倍以上):

using(var bcp = new SqlBulkCopy(connection))
using(var reader = ObjectReader.Create(data, "Id", "Name", "Description"))

    bcp.DestinationTableName = "SomeTable";
    bcp.WriteToServer(reader);

请注意,ObjectReader 也可以与非通用源一起使用,并且没有必要提前指定成员名称(尽管您可能希望使用 SqlBulkCopyColumnMappings 方面,如果您不这样做' t 在 ObjectReader 本身中指定它们)。

【讨论】:

优秀的图书馆!我刚刚试了一下,效果很好。 我知道这是几个月前的事了,但我也遇到了类似的问题。先加载DataTable耗时太长,所以想用这个方法。但是,params 变量中列出的字符串是按底层数据结构迭代的对象的顺序使用的变量的实际名称吗? 从头开始——想通了,答案是肯定的,这些是对象中属性的名称。 @Marc Gravell,当我将它与 bcp 和 List 一起使用时,我收到错误“无法将源字符串转换为日期时间”。我猜我需要添加 ColumnMappings,但要获得列名,我必须进行反射。有没有办法从 ObjectReader 获取属性名称? @Marc Gravell,当我长大后,我想像你一样编码:)。谢谢你总是提供很好的答案【参考方案3】:

根据您首先通过调用SqlBulkCopy 尝试完成的任务,使用表值参数 (TVP) 可能更有意义。使用 TVP 将使发送任何自定义类型的集合变得微不足道。数据可以流式传输,因此您可以避免DataTable(很像@Marc Gravell 的答案),您也可以避免SqlBulkCopy。 TVP 允许在数据到达 SQL Server 后如何处理数据的完全灵活性,因为您调用存储过程将 TVP 数据传递到其中,并且它显示为可以执行任何操作的表变量,而不仅仅是 INSERT(其中SqlBulkCopy 就是这种情况。您还可以通过SqlDataReader 取回数据,例如新创建的IDENTITY 值等数据。我在这个答案上添加了一个示例和一些附加说明:How can I insert 10 million records in the shortest time possible?。几年前,我写了一篇关于 SQL Server Central(需要免费注册)Streaming Data Into SQL Server 2008 From an Application 的文章,该链接的答案中也提到了该文章,提供了一个传入自定义类型的通用列表的工作示例,从300 万行文本文件。

【讨论】:

【参考方案4】:

迟到了,但如果你从 Microsoft 添加这个 EntityDataReader 类,有一个 AsDataReader() 扩展方法可以做到这一点:https://github.com/matthewschrager/Repository/blob/master/Repository.EntityFramework/EntityDataReader.cs

(示例[List].AsDataReader() 实现:)

var connStr = "";
using (var connection = new SqlConnection(connStr)) 

    var startTime = DateTime.Now;
    connection.Open();
    var transaction = connection.BeginTransaction();
    try
    
        //var connStr = connection.ConnectionString;
        using (var sbCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, transaction))
        
            sbCopy.BulkCopyTimeout = 0;
            sbCopy.BatchSize = 10000;
            sbCopy.DestinationTableName = "Foobars";
            var reader = Foobars.AsDataReader();
            sbCopy.WriteToServer(reader);
        
        transaction.Commit();
    
    catch (Exception ex)
    
        Console.WriteLine(ex.Message);
        transaction.Rollback();
    
    finally
    
        transaction.Dispose();
        connection.Close();
        var endTime = DateTime.Now;
        Console.WriteLine("Upload time elapsed: 0 seconds", (endTime - startTime).TotalSeconds);
    

【讨论】:

有些人可能需要为他们要导入的每一列添加列映射:sbCopy.ColumnMappings.Add(new SqlBulkCopyColumnMapping("col1", "col1"));

以上是关于来自 List<> 的 SqlBulkCopy的主要内容,如果未能解决你的问题,请参考以下文章

来自 Java 中 List<String> 的数据框

DisplayNameFor() 来自模型中的 List<Object>

Android 使用来自 MutableStateFlow<List<MyDataObject>>() 的数据对象字段的值更新活动

从分隔符之间的 List<string> 中删除字符(来自文本文件)

急:比较两组数据~~~一组来自excel,一组来自一个List<obj>,数据量excel和list各有1000多~~~

如何从 WCF REST Web 服务中的改造中获取来自@Part List<MultipartBody.Part> 发布请求的文件?