如何在完成 "event" 时连接到 TransactionScope?

How do I hook into TransactionScope on completing "event"?

我想知道是否有一种方法可以连接到当前 运行 事务并在该事务完成时完成一些工作。

目前我正在实施使用 MassTransit/RabbitMQ 发布消息的 EventPublisher,但我只想在 TransactionScope 完成时发布这些消息。

我会在 EventPublisher.PublishEvent() 方法中检查当前是否有交易 运行,如果没有,则触发消息,如果是,则收集消息并等待交易完成送走他们。

var ep = container.GetInstance<IEventPublisher>();

using(var scope = new TransactionScope())
{
  ... do some stuff 
  SaveEntity(entity);
  ep.PublishEvent(new EntitySaved(entity.Id));

  ... do some more stuff ...

  UpdateEntity(differentEntity);
  ep.PublishEvent(new EntityUpdated(differentEntity.Id));

  ... do even more stuff ...

  ep.PublishEvent(new UnrelatedMessage(someData));

  scope.Complete(); // <- only want the actual sending off to RabbitMQ to happen here.
}

我在这里找到了这个 TransactionCompleted 活动 https://docs.microsoft.com/en-us/dotnet/api/system.transactions.transaction.transactioncompleted 但它似乎只在 scope.Complete() 位结束并完成后才会被触发。

我可以用它来检查交易状态是否已完成,然后实际触发我在交易期间收集的那些消息。但我的问题是与 RabbitMQ 的连接可能已断开。然后我就不能发送那些消息了,但是上面的所有工作都已经完成了,但是我永远无法发送那些消息。

我真正想要的是以某种方式连接到事务当前正在完成的位,在该过程中我触发我的消息,如果失败我可以抛出一个异常并让那个仍然是 运行 事务出去了window。

也许 MassTransit 有办法做到这一点,但那里并没​​有真正提供文档。

下面是一些显示问题的示例代码。

    internal class Program
    {
        private static void Main(string[] args)
        {
            const string connectionString = "Data Source=.;Initial Catalog=MyDatabase;Integrated Security=True";

            var sql = @"insert into [SomeTable] (
                [Id]
                ,[Name]
                ,[Index]
                ,[RelationId]
                ) values (@param1, @param2, @param3, @param4) ";

            using (var scope = new TransactionScope())
            {
                Transaction.Current.TransactionCompleted += CurrentOnTransactionCompleted;
                using (var con = new SqlConnection(connectionString))
                {
                    con.Open();

                    using (var cmd = new SqlCommand(sql, con))
                    {
                        cmd.Parameters.Add("@param1", SqlDbType.UniqueIdentifier).Value = Guid.NewGuid();
                        cmd.Parameters.Add("@param2", SqlDbType.NVarChar, 128).Value = "Blah";
                        cmd.Parameters.Add("@param3", SqlDbType.SmallInt).Value = 1;
                        cmd.Parameters.Add("@param4", SqlDbType.UniqueIdentifier).Value = Guid.Parse("a401866d-3bdd-48a4-a78b-d40864c8471b");
                        cmd.CommandType = CommandType.Text;
                        cmd.ExecuteNonQuery();
                    }
                }

                scope.Complete();
            }
        }

        private static void CurrentOnTransactionCompleted(object sender, TransactionEventArgs e)
        {
            // I want to do stuff here but if this stuff fails I need the whole transaction to roll back.
            ... do some stuff that can fail ...

            e.Transaction.Rollback(new Exception("Bad transaction!"));

            // or
            throw new Exception("Bad transaction!"); 

        }
    }

我找错地方了。浏览 microsoft/referencesource 后,我在 TransactionContext.cs 中找到了我要找的东西。

您需要连接一个 IEnlistmentNotification,如下所述:https://docs.microsoft.com/en-us/dotnet/api/system.transactions.transaction.enlistvolatile

我需要将我的代码放入 Prepare 方法中,如果失败则调用 ForceRollback