【问题标题】:Insert entire DataTable into database at once instead of row by row?一次将整个 DataTable 插入数据库而不是逐行插入?
【发布时间】:2012-05-11 10:23:30
【问题描述】:

我有一个 DataTable,需要将整个内容推送到数据库表中。

我可以用一个 foreach 把它全部放在那里,一次插入每一行。由于有几千行,这会非常缓慢。

有没有什么方法可以更快地一次性完成整个数据表?

DataTable 的列数少于 SQL 表。其余的应为空。

【问题讨论】:

标签: c# sql sql-server


【解决方案1】:

我发现 SqlBulkCopy 是一种简单的方法,并且不需要在 SQL Server 中编写存储过程。

这是我如何实现它的示例:

// take note of SqlBulkCopyOptions.KeepIdentity , you may or may not want to use this for your situation.  

using (var bulkCopy = new SqlBulkCopy(_connection.ConnectionString, SqlBulkCopyOptions.KeepIdentity))
{
      // my DataTable column names match my SQL Column names, so I simply made this loop. However if your column names don't match, just pass in which datatable name matches the SQL column name in Column Mappings
      foreach (DataColumn col in table.Columns)
      {
          bulkCopy.ColumnMappings.Add(col.ColumnName, col.ColumnName);
      }

      bulkCopy.BulkCopyTimeout = 600;
      bulkCopy.DestinationTableName = destinationTableName;
      bulkCopy.WriteToServer(table);
}

【讨论】:

    【解决方案2】:

    由于您已经有一个 DataTable,并且我假设您使用的是 SQL Server 2008 或更高版本,因此这可能是最直接的方法。首先,在您的数据库中,创建以下两个对象:

    CREATE TYPE dbo.MyDataTable -- you can be more speciifc here
    AS TABLE
    (
      col1 INT,
      col2 DATETIME
      -- etc etc. The columns you have in your data table.
    );
    GO
    
    CREATE PROCEDURE dbo.InsertMyDataTable
      @dt AS dbo.MyDataTable READONLY
    AS
    BEGIN
      SET NOCOUNT ON;
    
      INSERT dbo.RealTable(column list) SELECT column list FROM @dt;
    END
    GO
    

    现在在您的 C# 代码中:

    DataTable tvp = new DataTable();
    // define / populate DataTable
    
    using (connectionObject)
    {
        SqlCommand cmd = new SqlCommand("dbo.InsertMyDataTable", connectionObject);
        cmd.CommandType = CommandType.StoredProcedure;
        SqlParameter tvparam = cmd.Parameters.AddWithValue("@dt", tvp);
        tvparam.SqlDbType = SqlDbType.Structured;
        cmd.ExecuteNonQuery();
    }
    

    如果您在问题中提供了更具体的细节,我会给出更具体的答案。

    【讨论】:

    • 如果我没记错的话,这仅适用于相对少量的数据,因为@dt 将逐行填充。 SqlBulkCopy 对于大数据(数千甚至更多)的效率要高得多。
    • @Aaron Bertrand 我为 3 个不同的表插入了超过 600000 条记录。表 A 不依赖于任何其他表,而表 B 需要表 A 的主键,表 C 需要 A 和 B 的主键值。表 D 也需要更新 10000 条记录。我怎样才能做到这一点。
    • 在插入过程中我可以检查并从数据表标题创建一个表
    【解决方案3】:

    考虑this approach,你不需要for循环:

    using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
    {
        bulkCopy.DestinationTableName = 
            "dbo.BulkCopyDemoMatchingColumns";
    
        try
        {
            // Write from the source to the destination.
            bulkCopy.WriteToServer(ExistingSqlTableName);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
    

    【讨论】:

    • 对于像我这样的新手:bulkCopy.WriteToServer(ExitingSqlTableName);这里的 ExistingSqlTableName 表示现有的 sql 数据源。它可以是 sql 表、数据表或阅读器。
    • 这应该是公认的答案。谢谢。
    【解决方案4】:

    如果可以稍微偏离DataTable -> SQL表的直线路径,也可以通过对象列表来完成:

    1) DataTable -> 通用对象列表

    public static DataTable ConvertTo<T>(IList<T> list)
    {
        DataTable table = CreateTable<T>();
        Type entityType = typeof(T);
        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(entityType);
    
        foreach (T item in list)
        {
            DataRow row = table.NewRow();
    
            foreach (PropertyDescriptor prop in properties)
            {
                row[prop.Name] = prop.GetValue(item);
            }
    
            table.Rows.Add(row);
        }
    
        return table;
    }
    

    来源和更多细节可以在here找到。缺少的属性将保持其默认值(ints 为 0,引用类型为 null 等)

    2) 将对象推送到数据库中

    一种方法是使用EntityFramework.BulkInsert 扩展名。不过,需要 EF 数据上下文。

    它生成快速插入所需的 BULK INSERT 命令(用户定义的表类型解决方案比这慢得多)。

    虽然不是直接的方法,但它有助于构建使用对象列表的基础,而不是 DataTables 和 seems to be much more memory efficient

    【讨论】:

      【解决方案5】:

      我更喜欢用户定义的数据类型:它超级快。

      第 1 步:在 Sql Server DB 中创建用户定义表

      CREATE TYPE [dbo].[udtProduct] AS TABLE(
        [ProductID] [int] NULL,
        [ProductName] [varchar](50) NULL,
        [ProductCode] [varchar](10) NULL
      )
      GO
      

      第 2 步:创建具有用户定义类型的存储过程

      CREATE PROCEDURE ProductBulkInsertion 
      @product udtProduct readonly
      AS
      BEGIN
          INSERT INTO Product
          (ProductID,ProductName,ProductCode)
          SELECT ProductID,ProductName,ProductCode
          FROM @product
      END
      

      第 3 步:从 c# 执行存储过程

      SqlCommand sqlcmd = new SqlCommand("ProductBulkInsertion", sqlcon);
      sqlcmd.CommandType = CommandType.StoredProcedure;
      sqlcmd.Parameters.AddWithValue("@product", productTable);
      sqlcmd.ExecuteNonQuery();
      

      可能的问题:更改用户定义的表

      其实并没有sql server命令来改变用户定义的类型 但在管理工作室中,您可以通过以下步骤实现这一目标

      1.为该类型生成脚本。(在新的查询窗口中或作为文件) 2.删除用户定义表。 3.修改创建脚本,然后执行。

      【讨论】:

        【解决方案6】:

        您可以使用表值参数来做到这一点。

        看看下面的文章:

        http://www.codeproject.com/Articles/39161/C-and-Table-Value-Parameters

        【讨论】:

          猜你喜欢
          • 2019-11-05
          • 2021-01-23
          • 1970-01-01
          • 2021-08-22
          • 1970-01-01
          • 2016-08-21
          • 1970-01-01
          • 1970-01-01
          • 2022-09-26
          相关资源
          最近更新 更多