【问题标题】:c# transaction scope with yieldc# 带产量的事务范围
【发布时间】:2016-11-08 15:07:20
【问题描述】:

我的要求是处理多个成本文件,其中有数百万条记录。处理和验证后,我必须将这些记录添加到数据库中。

为了获得更好的性能,我在 foreach 循环中使用“yield”并一次返回一条记录,处理该记录并立即将该记录添加到带有文件编号的数据库中。在这个文件读取过程中,如果我遇到任何数据验证错误,我会抛出 InvalidRecordException。

我的要求是从与该文件相关的表中删除所有记录。简而言之,即使一条记录无效,我也想将该文件标记为无效文件,甚至不将该文件的一条记录添加到数据库中。

任何人都可以在这里帮助我,我如何在这里使用 TransactionScope。

 public class CostFiles
        {
            public IEnumerable<string> FinancialRecords
            {
                get
                {
                    //logic to get list of DataRecords
                    foreach (var dataRecord in DataRecords)
                    {
                        //some processing... which can throw InvalidRecord exception 
                        yield return dataRecord;                       
                    }
                    yield break;
                }
            }
        }


        public void ProcessFileRecords(CostFiles costFile, int ImportFileNumber)
        {

            Database db = new Database();

            using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
            {
                try
                {
                    foreach (var record in costFile.FinancialRecords)
                    {
                        db.Add(record, ImportFileNumber);

                    }
                }
                catch(InvalidRecordException ex)
                {
                    //here i want to delete all the records from the table where import file number is same as input paramter ImportFileNumber
                }
            }
        }

【问题讨论】:

  • 什么是DatabaseTransactionScope(通常)用于分布式事务支持。 Database 是否为此提供支持?没有可以使用的原生事务概念吗?
  • 对于这种类型的事情,我建议使用存储过程来处理您的数据工作。通过痛苦的行来完成这一行对于数百万行来说会非常慢。
  • @DavidOsborne TransactionScope 不是“通常用于分布式事务支持”。如果您不重用相同的 SQL 连接对象,则 TransactionScope 仅将事务提升为分布式事务。
  • SQL Server 具有批量文件导入功能,比您在此处提出的要高效得多。
  • @CAbbott:它需要大量处理并向用户显示经过优化的数据......所以我继续采用这种方法......但是我肯定会考虑 SQL 批量文件导入选项。

标签: c# sql-server yield transactionscope


【解决方案1】:

事务范围的目的是创建一个“全有或全无”的场景,因此要么提交整个事务,要么什么都不提交。看起来您已经有了正确的想法(至少就TransactionScope 而言。在您调用TransactionScope.Complete() 之前,范围实际上不会将记录提交到数据库。如果未调用Complete(),则记录当您离开事务范围时被丢弃。您可以轻松地执行以下操作:

using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
{
    bool errorsEncountered = false;

    try
    {
        foreach (var record in costFile.FinancialRecords)
        {
            db.Add(record, ImportFileNumber);

        }
    }
    catch(InvalidRecordException ex)
    {
        //here i want to delete all the records from the table where import file number is same as input paramter ImportFileNumber
        errorsEncountered = true;
    }

    if (!errorsEncountered)
    {
        scope.Complete();
    }
}

或者您可以让 Add 抛出异常并在事务范围之外处理它,因为该异常将导致 Complete() 不被调用,因此不会添加任何记录。这种方法还有一个额外的好处,就是在我们知道它什么都不做的时候停止处理额外的记录。

try
{
    using (var scope = new TransactionScope(TransactionScopeOptions.Required))
    {
        foreach(var record in costFile.FinancialRecords)
        {
            db.Add(record, ImportFileNumber);
        }

        // if an exception is thrown during db.Add(), then Complete is never called

        scope.Complete()
    }
    catch(Exception ex)
    {
        // handle your exception here
    }
}

编辑如果您不希望将事务提升为分布式事务(可能有额外的安全/网络要求),请确保为事务中的每个数据库调用重复使用相同的 SqlConnection 对象范围。

using (var conn = new SqlConnection("myConnectionString"))
{
    conn.Open();

    using (var scope = new TransactionScope(...))
    {
        foreach(var foo in foos)
        {
            db.Add(foo, conn);
        }

        scope.Complete();
    }
}

【讨论】:

  • @DVK:感谢您的解释。如果我调用 dispose 方法出现异常,它会回滚(删除)具有相同 importFileNumber 的所有记录吗?或者在 catch 块中,我必须调用存储过程,它将使用该 importFileNumber 从数据库中删除所有记录?
  • 如果具有该导入文件编号的所有记录都作为同一事务的一部分添加,则无需调用任何内容。在调用Complete() 之前,这些记录实际上并没有提交到数据库,所以不要调用完成。请记住,如果在范围内调用删除记录也将被视为事务的一部分,因此在调用 Complete() 之前不会删除记录。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-23
  • 1970-01-01
  • 1970-01-01
  • 2010-10-15
  • 1970-01-01
相关资源
最近更新 更多