如何检索两个插入查询中的一个插入的内容

时间:2014-07-04 01:42:40

标签: c# sql-server ado.net dbcontext

我正在构建一个API方法,其中包含一个字典,并尝试将数据插入到数据库中。数据本质上是通过外键强制执行的父/子类型关系拆分的。数据库结构是出于特定原因而设计的,不能更改。

我最初是使用Entity Framework编写的,但性能测试表明,由于需要进行所有查询和处理,因此需要大量请求。将所有数据发送到数据库并允许它确定应该插入哪些记录要快得多(我们说的是20-30分钟到20-30秒)。

这是我的问题:最初,我只是返回使用ExecuteNonQuery插入的记录数。容易,对吗?现在,我需要能够找出哪些父记录已成功插入子记录。所以,我一直在努力重构它以促进它。

为清楚起见,我对插入了哪些父记录不感兴趣 - 我只对哪些父记录插入引用所述父记录的新子记录感兴趣。通过这种方式,我可以通过与调用者传递给API的内容进行比较,告知API调用者哪些记录未成功插入。到目前为止,我能看到的最好的方法是使用子OUTPUT查询中的INSERT子句来获取插入的ParentID并将它们存储在表变量中。然后我可以在父表中查找ID并获取我的比较名称。但这需要使用读者,并且由于涉及多个SQL语句,所以发生了不好的事情。

当前呈现的代码导致以下例外情况:

  

无法执行事务操作,因为存在处理此事务的待处理请求。这个SqlTransaction已经完成;它不再可用。测试方法My.Long.Project.Name.UnitTest.UnitTestMethod抛出异常:System.InvalidOperationException:此SqlTransaction已完成;它不再可用了。

虽然修复这些异常很有价值,但我对解决它们并不像解决实际问题那样感兴趣。如果有一条不同的路径,我可以采取非常快的速度并提供我需要的输出,然后我会调查它。这是我的代码。我希望我打算做的很清楚,任何帮助/指导/建议都会受到赞赏。

using (Context dbContext = createDbInstance())
{
    //Not happy about setting MultipleActiveResultSets
    string conn = dbContext.Database.Connection.ConnectionString + ";MultipleActiveResultSets=True";

    SqlCommand newInsertCmd = new SqlCommand {Connection = new SqlConnection(conn)};

    //Set up input variables here, including a TPV

    SqlDataReader reader;
    List<string> results = new List<string>();

    newInsertCmd.Connection.Open();
    SqlTransaction sqlTran = newInsertCmd.Connection.BeginTransaction();
    newInsertCmd.Transaction = sqlTran;

    try
    {
        //The two insert statements work just fine. The other junk here (including the OUTPUT clause) is brand new
        const string qryInsertTrans =
            @"INSERT INTO Parent ([Name], [CreateDate])
            SELECT n.Name, GETUTCDATE() [CreateDate]
            FROM
                @NewRecords n
                LEFT JOIN Parent p ON n.Name = p.Name
            WHERE
                p.ParentID IS NULL;

            DECLARE @OutputVar table(
                ParentID bigint NOT NULL
            );

            INSERT INTO Child ([ParentID], [SomeText], [CreateDate])
            OUTPUT INSERTED.ParentID INTO @OutputVar
            SELECT p.ParentID, n.Text, GETUTCDATE() [CreateDate]
            FROM
                @NewRecords n
                INNER JOIN Parent p ON n.Name = p.Name
                LEFT JOIN Child c ON p.ParentID = c.ParentID AND c.SomeCol = @SomeVal
            WHERE
                c.ChildID IS NULL;

            SELECT p.Name
            FROM Parent p INNER JOIN @OutputVar o ON p.ParentID = o.ParentID";

        newInsertCmd.CommandText = qryInsertTrans;
        reader = await newInsertCmd.ExecuteReaderAsync();

        while (reader.Read())
        {
            results.Add(reader["Name"].ToString());
        }

        sqlTran.Commit();
    }
    catch (Exception ex)
    {
        Debug.WriteLine(ex.Message);

        try
        {
            sqlTran.Rollback();
        }
        catch (Exception exRollback)
        {
            Debug.WriteLine(exRollback.Message);
            throw;
        }

        throw;
    }
    finally
    {
        newInsertCmd.Connection.Close();
    }
}

2 个答案:

答案 0 :(得分:1)

The transaction operation cannot be performed because there are
pending requests working on this transaction. This SqlTransaction
has completed; it is no longer usable.

错误很可能是由于ExecuteReaderAsyncawait造成的。尝试常规ExecuteReader而不是await。此应该允许.Net启动的事务工作。

如果没有解决问题,可能是您没有在reader.Close();块中调用finally(这应该是为了没有那个孤立的资源)。实际上,再次查看错误消息可能是在reader.Close();之后但在while之前需要sqlTran.Commit();

但是,由于您只有一个SqlCommand调用,因此确实不需要.Net启动的事务,对吧?这可以在SQL中通过如下构造来干净地处理:

BEGIN TRY
  BEGIN TRANSACTION;

  <your code>

  COMMIT TRAN;
END TRY
BEGIN CATCH
  ROLLBACK TRAN;
  THROW;
END CATCH;

如果第一个INSERT(进入Parent)失败,则将跳过第二个INSERT(进入Child),因为控件将立即传递给CATCH块。

修改
我在IDDNReader接口的MSDN文档中遇到了以下内容,它支持我认为开放的SqlDataReader是罪魁祸首,并且在发布提交之前需要关闭。在&#34;备注&#34; Read()方法的一部分,它说:

  

在使用数据阅读器时,关联的连接正忙   为IDataReader提供服务。在调用Close之前就是这种情况。

这应该解释为什么&#34;关闭DataReader的先前尝试没有解决问题&#34; (在对该问题的评论中作出的陈述的释义)因为很可能你在finally子句中关闭它,因为事务是在try块的末尾提交的,所以为时已晚。 / p>

答案 1 :(得分:1)

等等,我只是再看一遍,这一切都很完美!我不知道这几天是否让我神志不清或明智(甚至存在差异?)但我几乎肯定会增加使用功能。

这是代码,授予我当然没有经过测试甚至编译过,所以我可能会关注一些细节:

using (Context dbContext = createDbInstance())
{
    //Not happy about setting MultipleActiveResultSets
    string conn = dbContext.Database.Connection.ConnectionString + ";MultipleActiveResultSets=True";

    using (var connection = new SqlConnection(conn))
    using (var newInsertCmd = new SqlCommand(connection))
    {
        newInsertCmd.Connection.Open();
    //Set up input variables here, including a TPV

    List<string> results = new List<string>();

    using(SqlTransaction sqlTran = newInsertCmd.Connection.BeginTransaction())
    {
        newInsertCmd.Transaction = sqlTran;

        try
        {
            //The two insert statements work just fine. The other junk here (including the OUTPUT clause) is brand new
            const string qryInsertTrans =
                @"INSERT INTO Parent ([Name], [CreateDate])
                SELECT n.Name, GETUTCDATE() [CreateDate]
                FROM
                    @NewRecords n
                    LEFT JOIN Parent p ON n.Name = p.Name
                WHERE
                    p.ParentID IS NULL;

                DECLARE @OutputVar table(
                    ParentID bigint NOT NULL
                );

                INSERT INTO Child ([ParentID], [SomeText], [CreateDate])
                OUTPUT INSERTED.ParentID INTO @OutputVar
                SELECT p.ParentID, n.Text, GETUTCDATE() [CreateDate]
                FROM
                    @NewRecords n
                INNER JOIN Parent p ON n.Name = p.Name
                LEFT JOIN Child c ON p.ParentID = c.ParentID AND c.SomeCol = @SomeVal
                WHERE
                     c.ChildID IS NULL;

                SELECT p.Name
                FROM Parent p INNER JOIN @OutputVar o ON p.ParentID = o.ParentID";

            newInsertCmd.CommandText = qryInsertTrans;
            using(var reader = await newInsertCmd.ExecuteReaderAsync())
            {
                while (reader.Read())
                {
                    results.Add(reader["Name"].ToString());
                }
            }
            sqlTran.Commit();
        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);

            try
            {
                sqlTran.Rollback();
            }
            catch (Exception exRollback)
            {
                Debug.WriteLine(exRollback.Message);
                throw;
            }

            throw;
        }
    }
}

或者,如果你正在寻找更多的东西,至少在我看来是可读的:

using (Context dbContext = createDbInstance())
{

    List<string> results = new List<string>();

    //Not happy about setting MultipleActiveResultSets
    string conn = dbContext.Database.Connection.ConnectionString + ";MultipleActiveResultSets=True";

    using (var connection = new SqlConnection(conn))
    {
        newInsertCmd.Connection.Open();
        using(SqlTransaction sqlTran = newInsertCmd.Connection.BeginTransaction())
        {
            try
            {
                using (var parentInsert = new SqlCommand(connection))
                {
                    parentInsert .Transaction = sqlTran;

                    //Set up input variables here, including a TPV

                    newInsertCmd.CommandText = 
                            @"INSERT INTO Parent ([Name], [CreateDate])
                            SELECT n.Name, GETUTCDATE() [CreateDate]
                            FROM @NewRecords n
                            LEFT JOIN Parent p ON n.Name = p.Name
                            WHERE p.ParentID IS NULL;";

                    await newInsertCmd.ExecuteNonQueryAsync();
                }

                using (var childInsert = new SqlCommand(connection))
                {
                    childInsert.Transaction = sqlTran;

                    //Set up input variables here, including a TPV

                    newInsertCmd.CommandText = 
                          @"DECLARE @OutputVar table(
                              ParentID bigint NOT NULL
                          );

                          INSERT INTO Child ([ParentID], [SomeText], [CreateDate])
                          OUTPUT INSERTED.ParentID INTO @OutputVar
                          SELECT p.ParentID, n.Text, GETUTCDATE() [CreateDate]
                          FROM NewRecords n
                          INNER JOIN Parent p ON n.Name = p.Name
                          LEFT JOIN Child c ON p.ParentID = c.ParentID AND c.SomeCol = @SomeVal
                          WHERE c.ChildID IS NULL;

                          SELECT p.Name
                          FROM Parent p INNER JOIN @OutputVar o ON p.ParentID = o.ParentID";

                   using(var reader = await childInsert.ExecuteReaderAsync())
                   {
                       while (reader.Read())
                       {
                           results.Add(reader["Name"].ToString());
                       }
                   }
                }
                sqlTran.Commit();
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);

                try
                {
                    sqlTran.Rollback();
                }
                catch (Exception exRollback)
                {
                    Debug.WriteLine(exRollback.Message);
                    throw;
                }

                throw;
            }
        }
    }
}

为了更好的衡量,嵌入式选项:

using (Context dbContext = createDbInstance())
{
    //Not happy about setting MultipleActiveResultSets
    string conn = dbContext.Database.Connection.ConnectionString + ";MultipleActiveResultSets=True";

    using (var connection = new SqlConnection(conn))
    using (var newInsertCmd = new SqlCommand(connection))
    {
        newInsertCmd.Connection.Open();
        //Set up input variables here, including a TPV

        List<string> results = new List<string>();

        //The two insert statements work just fine. The other junk here (including the OUTPUT clause) is brand new
        const string qryInsertTrans =
            @"BEGIN TRY
                BEGIN TRANSACTION;

                INSERT INTO Parent ([Name], [CreateDate])
                SELECT n.Name, GETUTCDATE() [CreateDate]
                FROM @NewRecords n
                    LEFT JOIN Parent p ON n.Name = p.Name
                WHERE p.ParentID IS NULL;

                DECLARE @OutputVar table(
                ParentID bigint NOT NULL
                );

                INSERT INTO Child ([ParentID], [SomeText], [CreateDate])
                OUTPUT INSERTED.ParentID INTO @OutputVar
                SELECT p.ParentID, n.Text, GETUTCDATE() [CreateDate]
                FROM @NewRecords n
                    INNER JOIN Parent p ON n.Name = p.Name
                LEFT JOIN Child c ON p.ParentID = c.ParentID AND c.SomeCol = @SomeVal
                WHERE c.ChildID IS NULL;

                SELECT p.Name
                FROM Parent p INNER JOIN @OutputVar o ON p.ParentID = o.ParentID

                COMMIT TRAN;                          
            END TRY
            BEGIN CATCH
            ROLLBACK TRAN;
            THROW;
            END CATCH;";

        newInsertCmd.CommandText = qryInsertTrans;
        using(var reader = await newInsertCmd.ExecuteReaderAsync())
        {
            while (reader.Read())
            {
                results.Add(reader["Name"].ToString());
            }
        }
    }
}