这个模式都说烂了,但是为什么几乎都是:

public abstract class RepositoryBase : IDisposable
{
	// ... 省略 field 声明

	protected RepositoryBase() {
		this._context = this.CreateContext();
	}
	
	// ... 省略 Add, Delete, Update, Get
	
	public void Dispose() {
		this._context.Dispose();
	}
}

首先,继承下来的各个 Repository 难道都要用这种方法调用?

using (var repository = new XxxRepository()) {
	repository.Add(...);
	repository.Delete(...)
	// ...
	repository.SaveChanges();
}

显然,在实际中,这种小儿科的需求简直太少了。第一,一个稍稍有些流程的业务就会用到多于一个的 Repository,你如何控制事务呢?第二,如此明显的控制 Repository 的生命周期,你的代码中将充斥 using,你真的会这样写吗?

对于第一个问题,有人说,简单啊,这样就行了:

using (var tr = new TransactionScope())
using (var repository1 = new XxxRepository())
using (var repository2 = new YyyRepository()) 
{
	repository1.Add(...);
	repository2.Delete(...)
	// ...
	repository.SaveChanges();
	tr.Complete();
}

而事实,如果你使用了这些代码,有可能会直接抛出异常(还算比较好),也有可能悄悄的成功(背后做了很多你不知道的事情)。

如果你没有部署DTS(Distributed Transaction Server),那么,在一个 TransactionScope 中出现了多个ObjectContext,导致 Transaction 升级为一个分布式的 Transaction 时会发生失败。相反,这个 Transaction 正式升级为一个分布式 Transaction,他疯狂的降低性能,并且可能造成一致性问题(如果你碰巧使用了 Mirror)。

所以,上述 Repository 也就能去上上课。实际项目中我们还是需要一些技巧。我提供一种非常平实的方法(但是有效),不用什么花哨的技术,当然还会有更优美的方法等着各位去实现。

首先,我们罗列一下限制条件:

  1. 我们不能没完没了的持有 Context,应当尽可能早的释放他;
  2. 一个 Context 只能够在一个线程中使用;

然后我们谈一下期望:

  1. 我们希望当我们使用 Repository 方法的时候,Context一定是有效的;
  2. 我们不希望手动控制 Context 的生命周期;
  3. 我们希望能够在外部灵活的加入事务的范围,令多个 Repository 协同工作。

为了使多个 Repository 协同工作,显然 Context 不应该定义在 Repository 中,而应该在外部进行管理。一个 Context 总是在一个线程使用,并且在事务中,只希望出现一个 Context 实例,则考虑使用线程内存储的方法保存 Context。

// 别声明为 public 的, 我们本意就是隐藏 Context.
internal class ThreadStaticContext 
{
	[ThreadStatic]
	private static YourContextType Context;
	
	public static bool IsAvailable() 
	{
		return Context != null;
	}
	
	public static YourContextType GetOrCreated() 
	{
		if (Context == null) 
		{
			// 当然, 咱们可以公布一些静态的属性进行配置, 例如连接字符串什么的.
			Context = YourContextType.Create();
		}
		
		return Context;
	}
	
	public static void Destory()
	{
		if (Context != null) 
		{
			Context.Dispose();
			Context = null;
		}
	}
}

其次,有两个点,需要确保 Context 的有效,第一,在 Repository 进行数据操作的时候;第二,在 Transaction 范围内。首先在 Repository 基类中定义如下的方法:

public abstract class RepositoryBase 
{
	protected T Query<T>(Func<YourContextType, T> queryProc)
	{
		bool isCreatedByMe = false;
		
		if (!ThreadStaticContext.IsAvailable()) {
			ThreadStaticContext.GetOrCreated();
			isCreatedByMe = true;
		}
		
		try 
		{
			return queryProc(ThreadStaticContext.GetOrCreated());
		}
		finally
		{
			if (isCreatedByMe)
			{
				ThreadStaticContext.Destory();
			}
		}
	}
}

这样我们只需要确保使用 Query 方法书写 Repository 中的数据操作:

public User Get(int id) 
{
	return this.Query(
		context => (from u in context.User where u.id == id).SingleOrDefault());
}

而后,我们需要包装一下 TransactionScope,以便在创建 Transaction 的时候确保 Context 的有效性:

public class DataTransaction : IDisposable
{
	private bool _isCreatedByMe;
	private TransactionScope _transactionScope;

	protected DataTransaction()
	{
		this._transactionScope = new TransactionScope();
		
		try 
		{
			if (ThreadStaticContext.IsAvailable())
			{
				ThreadStaticContext.GetOrCreated();
				this._isCreatedByMe = true;
			}
		}
		catch 
		{
			this._transactionScope.Dispose();
			throw;
		}
	}
	
	public void Complete()
	{
		this._transactionScope.Complete();
	}
	
	public void Dispose()
	{
		if (this._isCreatedByMe)
		{
			ThreadStaticContext.Destory();
		}
		
		this._transactionScope.Dispose();
	}
	
	public static DataTransaction Create()
	{
		return new DataTransaction();
	}
}

这样我们就可以这样使用 Repository 了。

using (var tr = DataTransaction.Create())
{
	new RepositoryType1().Add(...);
	new RepositoryType2().Update(...);
	tr.Complete();
}

不但完全看不到 Context 的影子,连 Context 的生存周期也不用担心。当然,现在我们的 Query 方法中有一个 context 参数,实际上这个参数也是不用的,可以考虑基类做成 RepositoryBase<TTable>其中使用 context.GetTable<TTable> 实现 GetAll(),Submit(),AddOnSubmit(),DeleteOnSubmit(),… 方法。这样 Context 再也不会出现了。这个过程就不赘述了。

如果你手头恰好有好用的依赖注入容器(例如Ninject),可以使用其控制生存期,将Context的生存期控制在 Per Thread 一级,可以达到同样的效果。

作者: 夏天可是个好季节 发表于 2011-08-02 23:48 原文链接

推荐.NET配套的通用数据层ORM框架:CYQ.Data 通用数据层框架
新浪微博粉丝精灵,刷粉丝、刷评论、刷转发、企业商家微博营销必备工具"