.NET 中的事务性存储库实现






4.60/5 (7投票s)
企业缓存应用程序块的事务性实现。
引言
目前我们知道哪些事务性存储库?这是一个列表:SQL Server、MSMQ、文件系统和注册表(在 Windows Vista/Windows Server 2008 中)。这些足够吗?它们是否能满足企业的各种需求?下面描述的事务性存储库实现展示了实现自己的事务性存储库所需的基本原理,该存储库可以轻松地参与 .NET 中的环境事务和显式事务。给定的实现基于企业库缓存应用程序块。你可能会问,为什么选择缓存应用程序块?世界上有人需要事务性缓存吗?:) 不,我个人不需要。本文的目的是为开发人员提供有关如何设计和实现自定义事务性存储库的基本思路,无论是 XML 文件还是其他任何东西。
因此,想法是我们可以轻松实现自己的事务性资源,这些资源可以轻松地融入 .NET 和 WCF 中引入的事务模型,特别是。
这种事务性存储库的主要优点是什么?主要优点是
- 它是事务性的!!!;)
- 它可以与 SQL Server 和 MSMQ 等其他事务性资源参与单个事务。
背景
.NET 2.0 中引入了一个名为 System.Transactions
的新命名空间。此命名空间提供了环境事务(TransactionScope
类)的功能,并支持分布式事务。Windows Communication Foundation(以及 COM+)开箱即用地支持环境事务。在下面的示例中,我使用了 TransactionScope
类来演示其工作原理。有关更多信息,请参阅 MSDN。
使用代码
首先,我想展示一个如何使用我称之为事务性缓存应用程序块的黑盒的示例代码。
如果在 using
块内的任何位置发生异常,所有事务都将被回滚。而且,您的缓存将保持一致状态。这里的好处是,事务是每个对象键。这意味着在事务期间,只有相关的缓存项**会同步**,而不是整个缓存,这提高了多线程应用程序的性能。此外,我想补充一点,如果存在并发事务试图访问/修改缓存中的同一对象,那么这些事务将被同步;换句话说,事务将被阻塞,直到另一个事务完成。此时,我在这里看到一个巨大的潜在死锁区域,但这就是生活——客户端代码在使用事务期间应小心处理其使用的对象。
在我们继续之前,我想指出下面介绍的解决方案的一些限制
- 当前版本不支持分布式事务,仅支持本地事务(如果有人付费,我很乐意扩展它 :) - 开玩笑)。
- 当前版本仅支持最高的事务隔离级别 - Serializable(可序列化)。
- 您需要安装 MS Enterprise Library 4.0。
- 缓存中的所有对象都应该是可序列化的。
- 当您在事务期间调用
Get
方法时,您将只获得对象的一个副本,而不是对象本身。
让我们回到实现。首先,您的事务性资源应实现 IEnlistmentNotification
接口
public class TransactionalCacheManager : ICacheManager, IEnlistmentNotification
{
void IEnlistmentNotification.Commit(Enlistment enlistment)
{
foreach(var commandItem in CurrentTransactionalRepository.Values)
{
commandItem.Command.Invoke();
}
enlistment.Done();
}
void IEnlistmentNotification.InDoubt(Enlistment enlistment)
{
enlistment.Done();
}
void IEnlistmentNotification.Prepare(PreparingEnlistment preparingEnlistment)
{
preparingEnlistment.Prepared();
}
void IEnlistmentNotification.Rollback(Enlistment enlistment)
{
enlistment.Done();
}
我的实现中的主要方法是 Commit
(在您的实现中,可以是 Rollback
)。我在这里使用了命令模式:在事务执行期间,我将客户端代码执行的所有操作收集到一个临时存储库中。然后,我在 Commit
期间执行所有这些命令。但同时,我的临时存储库有点棘手,它允许客户端代码查看事务中执行的更改(在调用 Get
方法期间)。临时存储库是按调用实现的
[ThreadStatic]
private static ThreadSafeDictionary<string, CommandItem> transactionalRepository;
在事务开始时,我进行设置,在事务结束时,我释放内存。因此,此存储库将仅存在于特定线程内给定事务的范围内。
这是通知事务管理器我希望参与事务的代码片段
void Enlist(string key, Operation operation, object value, Action command)
{
if(Transaction.Current != null)
{
Debug.Assert(Transaction.Current.TransactionInformation.Status ==
TransactionStatus.Active);
//notify transaction manager that we want to participate in transaction
Transaction.Current.EnlistVolatile(this, EnlistmentOptions.None);
CurrentTransactionalRepository[key] =
new CommandItem(operation, value, command);
}
else
{
command.Invoke();
}
}
Enlist
是一个私有方法。我从 Add
和 Remove
等方法调用此方法。我想在这里描述的最后一部分是我用于线程同步的小型自定义类。线程同步不属于本文的范围,我只是想展示一个使用 Monitor.Pulse/Monitor.Wait
方法的有趣技术。这是我的类
private class Synchronizer
{
private object lockObject = new object();
private bool canProceed = false;
public void Wait()
{
lock (lockObject)
{
while (!canProceed) Monitor.Wait(lockObject);
canProceed = true;
}
}
public void Set()
{
lock (lockObject)
{
canProceed = true;
Monitor.Pulse(lockObject);
}
}
}
就是这样。您可以在下载的源代码中找到更多信息。欢迎您提供反馈。期待任何建议。