关于c#:使用SqlBulkCopy时,在错误的列上违反了唯一约束

Unique constraint being violated on the wrong column when using SqlBulkCopy

我正在尝试使用SqlBulkCopy作为一次执行多个INSERT的方法,但出于某种原因,我在运行WriteToServer(DataTable)时遇到了唯一的约束违规。关于这个SqlException的奇怪之处在于它是这么说的。

我的表架构:

1
2
3
4
5
CREATE TABLE Product (
  ID   INT IDENTITY (1, 1) PRIMARY KEY,
  Name NVARCHAR(450) UNIQUE NOT NULL, -- Unique constraint being called
  BulkInsertID NCHAR(6) -- Column the constraint is being called on
);

我能想到为什么会发生这种情况的唯一原因是因为我在DataColumn中分配列名时混淆了列名,但是我多次检查它们并且我找不到它们的任何问题。

最小,完整和可验证的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
class Program
{
    private static SqlConnection connection;
    private static string connectionURL ="Server=ASUS-X750JA\\DIRECTORY;Database=directory;Integrated Security=True;";
    private static Random _random = new Random();

    public static SqlConnection openConnection()
    {
        connection = new SqlConnection(connectionURL);
        connection.Open();
        Console.WriteLine("Opened connection to DB");
        return connection;
    }

    public static void closeConnection()
    {
        connection.Close();
        Console.WriteLine("Closed connection to DB");
    }

    static void Main(string[] args)
    {
        List<string> productNames = new List<string>();
        productNames.Add("Diamond");
        productNames.Add("Gold");
        productNames.Add("Silver");
        productNames.Add("Platinum");
        productNames.Add("Pearl");
        addProducts(productNames);
    }

    private static void addProducts(List<string> productNames)
    {
        const string tableName ="Product";
        DataTable table = new DataTable(tableName);

        string bulkInsertID;
        do
        {
            bulkInsertID = generateID();
        } while (isDuplicateBulkInsertID(tableName, bulkInsertID));

        DataColumn nameColumn = new DataColumn("Name");
        nameColumn.Unique = true;
        nameColumn.AllowDBNull = false;

        DataColumn bulkInsertIDColumn = new DataColumn("BulkInsertID");
        bulkInsertIDColumn.Unique = false;
        bulkInsertIDColumn.AllowDBNull = true;

        table.Columns.Add(nameColumn);
        table.Columns.Add(bulkInsertIDColumn);

        foreach (string productName in productNames)
        {
            DataRow row = table.NewRow();
            row[nameColumn] = productName;
            row[bulkInsertIDColumn] = bulkInsertID;
            table.Rows.Add(row);
        }

        using (SqlConnection connection = openConnection())
        {
            using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
            {
                bulkCopy.DestinationTableName = table.TableName;
                bulkCopy.WriteToServer(table);
            }
        }
    }

    /// <summary>
    /// Generates random 6-character string but it's not like GUID so may need to check for duplicates
    /// </summary>
    /// <returns></returns>
    public static string generateID()
    {
        char[] _base62chars ="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".ToCharArray();
        int length = 6;

        var sb = new StringBuilder(length);

        for (int i = 0; i < length; i++)
            sb.Append(_base62chars[_random.Next(62)]);

        return sb.ToString();
    }

    public static bool isDuplicateBulkInsertID(string tableName, string bulkInsertID)
    {
        string query = string.Format("SELECT BulkInsertID FROM {0} WHERE BulkInsertID = @bulkinsertid", tableName);
        SqlCommand command = new SqlCommand(query, openConnection());
        SqlParameter bulkInsertIDParam = new SqlParameter("@bulkinsertid", SqlDbType.NChar, bulkInsertID.Length);
        bulkInsertIDParam.Value = bulkInsertID;

        command.Parameters.Add(bulkInsertIDParam);
        command.Prepare();
        Task<SqlDataReader> asyncTask = command.ExecuteReaderAsync();
        SqlDataReader reader = asyncTask.Result;
        bool isDuplicate = reader.HasRows;

        closeConnection();
        return isDuplicate;
    }
}

enter image description here

屏幕截图中显示的唯一约束属于Name列,但重复的键值正在发送到BulkInsertID列,我不知道为什么会抛出错误。

编辑:我刚刚更改了我的架构,使用uniqueidentifier作为BulkInsertID列并将row[bulkInsertIDColumn] = bulkInsertID更改为row[bulkInsertIDColumn] = Guid.NewGuid().ToString()。当我重新编写代码时,我发现生成的GUID已经运行但是当我查看表时,GUID位于名称列中。所以我可以得出结论,这不是服务器问题,而是程序中的问题。


因为您有一个标识列,所以批量插入尝试将nameColumn插入ID(并忽略它,因为该列是标识列)并且bulkInsertIDColumn输入到Name。 只需在插入中添加以下内容即可告诉它转到正确的列。

1
2
3
4
5
6
7
using (SqlBulkCopy bulkCopy = new SqlBulkCopy(connection))
{
    bulkCopy.ColumnMappings.Add("Name","Name"); //NEW
    bulkCopy.ColumnMappings.Add("BulkInsertID","BulkInsertID"); //NEW
    bulkCopy.DestinationTableName = table.TableName;
    bulkCopy.WriteToServer(table);
}

另一个选项是将ID列添加到table,并且不要在其中添加任何值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
DataColumn idColumn = new DataColumn("ID");

DataColumn nameColumn = new DataColumn("Name");
//nameColumn.Unique = true; //SqlBulkCopy does not care about these settings.
//nameColumn.AllowDBNull = false;

DataColumn bulkInsertIDColumn = new DataColumn("BulkInsertID");
//bulkInsertIDColumn.Unique = false;
//bulkInsertIDColumn.AllowDBNull = true;

table.Columns.Add(ID);
table.Columns.Add(nameColumn);
table.Columns.Add(bulkInsertIDColumn);

foreach (string productName in productNames)
{
    DataRow row = table.NewRow();
    //We don't do anything with row[idColumn]
    row[nameColumn] = productName;
    row[bulkInsertIDColumn] = bulkInsertID;
    table.Rows.Add(row);
}


看起来它正在为列BulkInsertID抛出UNIQUE约束违规,尽管从发布的表模式中看不到它标有该约束,并且在您的代码中我看到你有bulkInsertIDColumn.Unique = false;。 你确定你没有在其他任何地方将它设置为true

顺便说一句,对我来说看起来它正在抛出异常,因为你试图在循环中创建一个新的Random()实例,如下面所示的尖代码块

1
2
3
4
    do
    {
        bulkInsertID = generateID(); //calling method generateID
    } while (isDuplicateBulkInsertID(tableName, bulkInsertID));

generateID()中,您正在创建Random类的新实例

1
2
3
4
public static string generateID()
{
   ........
    Random _random = new Random(); // creating new instance every time