在几个线程中添加具有前一行+1值的新行

时间:2013-11-11 13:44:07

标签: c# sql sql-server multithreading tsql

我有一个表'testTable':

[Id]    [Name]
 0     'something'
 1     'something'
 2     'something'
 3     'something'

Id列不是Identity,而是主键,因此我将值添加为

INSERT INTO testTable VALUES (2, 'something')

我需要C#方法只带[Name]列,在表中插入此参数并返回[Id]列的值。

示例:我使用传递的字符串'test'调用方法。结果应该是:

1)在[Id] = 4和[Name] ='test'的表格中插入行

2)方法返回[Id],即4

我已经实现了具有SQL查询的方法,如:

  declare @Id int;
 set @Id = ISNULL((SELECT MAX(ID) FROM testTable) + 1, 0);

 insert into testTable OUTPUT @Id  values (@Id, @name);

对于单线程,它工作正常。但是,如果我调用此方法,例如,在

Parallel.For(0, 10, <lambda with method>);

我抓住了异常

  

违反PRIMARY KEY约束'ID_PK'。无法插入重复   对象'testTable'中的键

我想我可以使用C#lock关键字,但如果只能使用SQL查询,那就太棒了。谢谢。

1 个答案:

答案 0 :(得分:1)

您应该创建一个标识列,但不能总是控制数据库设计。

当我遇到这种情况时,我使用辅助方法

    /// <summary>
    /// Execute a non-query with a retry to handle collisions with non-identity keys
    /// </summary>
    /// <remarks>The command is retried while unique constraint or duplicate key errors occur
    /// <note type="caution">To be meaningful the command must try different values on each try
    /// e.g. INSERT INTO.. (Key) VALUES (SELECT MAX(Key)+1, ...</note></remarks>
    /// <param name="command">Command to execute</param>
    /// <param name="retries">Maximum number of retries</param>
    /// <returns>Number of rows affected. </returns>
    public static int ExecuteNonQueryWithRetry(this SqlCommand command, int retries) {
        for (int failCount = 0;;) {
            try {
                return command.ExecuteNonQuery();
            }
            catch (SqlException ex) {
                const int UniqueConstraintViolation = 2627;
                const int DuplicateKey = 2601;

                if (++failCount >= retries || 
                    (ex.Number != UniqueConstraintViolation &&
                     ex.Number != DuplicateKey))
                    throw;
            }
        }
    }

辅助方法的用法如下:

var command = new SqlCommand();
command.CommandText = "INSERT INTO testTable ( " +
                      "   ID " +
                      "  ,... " +
                      " ) " +
                      " VALUES ( " +
                      "    (SELECT COALESCE(MAX(Id), 0) + 1 " + 
                      "     FROM testTable) " +
                      "   ,... " +
                      " )";
// assign parameter, connection etc

const int MaxRetries = 2;

if (command.ExecuteNonQueryWithRetry(MaxRetries) != 1)
    throw new Exception("Oops");