跳过 SqlBulkCopy 中的一些列

15
我正在使用 SqlBulkCopy 将两个具有不同列集的 SQL Server 2008 进行比较(将一些数据从prod服务器移动到dev)。因此,我想跳过一些尚不存在/未删除的列。如何做到这一点?可以使用 ColumnMappings 吗?
DataTable table = new DataTable();
using (var adapter = new SqlDataAdapter(sourceCommand))
{
    adapter.Fill(table);
}

table.Columns
    .OfType<DataColumn>()
    .ForEach(c => bulk.ColumnMappings.Add(
        new SqlBulkCopyColumnMapping(c.ColumnName, c.ColumnName)));

bulk.WriteToServer(table)

并且获得:

给定的ColumnMapping与源或目标中的任何列不匹配。

4个回答

19
DataTable table = new DataTable();
using (var adapter = new SqlDataAdapter(sourceCommand))
{
    adapter.Fill(table);
}

using (SqlBulkCopy bulk = new SqlBulkCopy(targetConnection, SqlBulkCopyOptions.KeepIdentity, null) { DestinationTableName = tableName })
{
    foreach (string columnName in GetMapping(stringSource, stringTarget, tableName))
    {
        bulk.ColumnMappings.Add(new SqlBulkCopyColumnMapping(columnName, columnName));
    }

    targetConnection.Open();
    bulk.WriteToServer(table);
}

private static IEnumerable<string> GetMapping(string stringSource, string stringTarget, string tableName)
{
    return Enumerable.Intersect(
        GetSchema(stringSource, tableName),
        GetSchema(stringTarget, tableName),
        StringComparer.Ordinal); // or StringComparer.OrdinalIgnoreCase
}

private static IEnumerable<string> GetSchema(string connectionString, string tableName)
{
    using (SqlConnection connection = new SqlConnection(connectionString))
    using (SqlCommand command = connection.CreateCommand())
    {
        command.CommandText = "sp_Columns";
        command.CommandType = CommandType.StoredProcedure;

        command.Parameters.Add("@table_name", SqlDbType.NVarChar, 384).Value = tableName;

        connection.Open();
        using (var reader = command.ExecuteReader())
        {
            while (reader.Read())
            {
                yield return (string)reader["column_name"];
            }
        }
    }
}

1
@学生:嘿,如果我没记错的话,这些是源数据库和目标数据库的连接字符串。 - abatishchev
它运行得很好,但值得指出的是,您需要对表具有INSERT权限,因此这可能取决于某些IT部门的数据库策略 :-( - Kevin Shea
1
@Kev:坦白地说,我建议你做两件事情:要么换掉你的IT部门,要么如果你不能这样做-换工作。我们在工作中度过了很大一部分生命,生命太短暂了,不应该与不专业/过时/愚蠢的同事共度美好时光。 - abatishchev
@Kev:确实,说起来容易做起来难!但这只是值得考虑的事情。我曾经这样做了,改变了我的生活,摆脱了愚蠢的系统管理员和他们愚蠢的IT政策。每个人都可以做到 :) - abatishchev
1
太好了,使用sp_columns而不是select * from table where 1=0,并从DataReader获取SchemaTable()。你真是个英雄。 - JJS
显示剩余3条评论

15

使用 SqlBulkCopyColumnMapping 时,只有创建映射的列才会被复制。

如果您没有为某个列创建映射,它将被复制过程忽略。

您可以在演示代码(此处)中查看 - AdventureWorks 演示数据库中的示例源表包含比映射或复制的列更多的列。

编辑

没有更多关于数据库模式的信息很难确定,但很可能问题出在这个语句上:

new SqlBulkCopyColumnMapping(c.ColumnName, c.ColumnName)

据您的描述,源表中并非所有列都存在于目标表中。在构建SqlBulkCopyColumnMapping循环时,您需要添加一个过滤器来跳过在目标表中不存在的任何列。

我的C#水平不够好,无法给出我信心十足的示例,但在伪代码中,它应该是这样的:

foreach column c in sourcetable
{
    if c.ColumnName exists in destination_table.columns
    {
          new SqlBulkCopyColumnMapping(c.ColumnName, c.ColumnName)
    }
}

(我相信可以将此转换为lambda表达式)

请注意,在列名匹配但数据类型不兼容的情况下,这种方法并不特别健壮。


请查看我的更新帖子。我做错了什么?可能是我理解有误 - 源数据有一列,但目标数据没有。我应该比较源/目标模式,并仅使用两者都存在的列。 - abatishchev
@abatishchev - 添加了更多细节 - Ed Harper
谢谢!你帮我澄清了视野。但是很遗憾,你的例子对我来说不太适用,因为我没有目标表,只有它的名称。所以必须调用 sp_Columns 来确定表列。 - abatishchev

6

Ed Harper,以下是没有伪代码的实际情况(在这种情况下,从完全定义的DataTable dt到db中的现有表):

using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connectionString))
{
    bulkCopy.DestinationTableName = "dbo.DepartmentsItems";

    // Write from the source to the destination.
    foreach (DataColumn c in dt.Columns)
    {
        bulkCopy.ColumnMappings.Add(c.ColumnName, c.ColumnName);
    }

    bulkCopy.WriteToServer(dt);
    return dt.Rows.Count;
}

1

是的,我说的就是这个类。但如何跳过源中的一列呢?.Add(new SqlDataMapping("deleted-column-on-target", "")?当然,我可以在底层查询中从源中删除它 - SELECT a,b,c而不是SELECT * - 但这并不是一个解决方案。 - abatishchev
1
如果你不想将数据从源位置复制到目标位置,可以在映射过程中将其排除掉。映射过程只会复制指定列中的数据。 - cjk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接