转到其抽象类 AbpSessionBase 实现,可以看到他的实现是这个样子的:
protected IAmbientScopeProvider<SessionOverride> SessionOverrideScopeProvider { get; } public IDisposable Use(int tenantId, long? userId, string userCode) { return SessionOverrideScopeProvider.BeginScope(SessionOverrideContextKey, new SessionOverride(null, tenantId, userId, userCode)); }所以在这里,它是通过 SessionOverrideScopeProvider 的 BegionScope() 方法创建了可以被 Dispose() 的对象。
接着继续跳转,来到 IAmbientScopeProvider 接口定义,这个接口接受一个泛型参数,可以看到之前在 AbpSessionBase 传入了一个 SessionOverride。这个 SessionOverride 就是封装了 UserId 等信息的存储类,也就是说 SessionOverride 就是允许进行临时值更改的类型定义。
在开始执行 BegionScope() 方法的时候,就针对传入的 value 进行存储,获取 Session 值的时候优先读取存储的值,不存在才执行真正的读取,调用 Dispose() 方法的时候就进行释放。
所以接口提供了两个方法,第一个我们先看 BegionScope() 方法,接收一个 contextKey 用来区分不同的临时值,第二个参数则是要存储的临时值。
第二个方法为 GetValue,从一个上下文(后面讲)当中根据 contextKey 获得存储的临时值。
public interface IAmbientScopeProvider<T> { T GetValue(string contextKey); IDisposable BeginScope(string contextKey, T value); }针对于该接口,其默认实现是 DataContextAmbientScopeProvider ,它的内部可能略微复杂,牵扯到了另一个接口 IAmbientDataContext 和 ScopeItem 类型。
这两个类型一个是上下文,一个是包裹具体临时值对象的类型。我们先从 BeginScope() 方法开始看:
// ScopeItem 的 Id 与其值关联的字典,其键为 Guid,值为具体的 ScopeItem 对象,这里并未与 ContextKey 进行关联。 private static readonly ConcurrentDictionary<string, ScopeItem> ScopeDictionary = new ConcurrentDictionary<string, ScopeItem>(); // 数据的上下文对象,管理 ContextKey 与其 Id。 private readonly IAmbientDataContext _dataContext; public IDisposable BeginScope(string contextKey, T value) { // 将需要临时存储的对象,用 ScopeItem 包装起来,它的外部对象是当前对象 (如果存在的话)。 var item = new ScopeItem(value, GetCurrentItem(contextKey)); // 将包装好的对象以 Id-对象,的形式存储在字典当中。 if (!ScopeDictionary.TryAdd(item.Id, item)) { throw new AbpException("Can not add item! ScopeDictionary.TryAdd returns false!"); } // 在上下文当中设置当前的 ContextKey 关联的 Id。 _dataContext.SetData(contextKey, item.Id); // 集合释放委托,using 语句块结束时,做释放操作。 return new DisposeAction(() => { // 从字典中移除指定 Id 的对象。 ScopeDictionary.TryRemove(item.Id, out item); // 如果包装对象没有外部对象,直接设置上下文关联的 Id 为 NULL。 if (item.Outer == null) { _dataContext.SetData(contextKey, null); return; } // 如果还有外部对象,则设置上下文关联的 Id 为外部对象的 I的。 _dataContext.SetData(contextKey, item.Outer.Id); }); }从上面的逻辑可以看出来,每次我们加入的临时值都是通过 ScopeItem 包裹起来的。而这个 ScopeItem 与我们的工作单元相似,它会有一个外部连接的对象。这个外部连接对象的作用就是解决 using 语句嵌套问题的,例如我们有以下代码:
public void TestMethod() { using(AbpSession.Use(1,2,"3")) { // 一些业务逻辑 // ScopeItem.Outer = null; using(AbpSession.Use(4,5,"6")) { // 一些业务逻辑 // ScopeItem.Outer = 外部对象; } } }那么我们在这里会有同一个 ContextKey,都是提供给 AbpSession 使用的。第一次我在 Use() 内部通过 BeginScope() 方法创建了一个 ScopeItem 对象,包装了临时值,这个 ScopeItem 的外部对象为 NULL。第二次我又在内部创建了一个 ScopeItem 对象,包装了第二个临时值,这个时候 ScopeItem 的外部对象就是第一次包装的对象了。
执行释放操作的时候,首先判断外部对象是否为空。如果为空则直接在上下文当中将绑定的 ScopeItem 的 Id 值设为 NULL,如果不为空,则设置为它的外部对象的 Id。
还是以上面的代码为例,在 Dispose() 被执行之后,由内而外,到最外层的时候在上下文与 ContextKey 关联的 Id 已经被置为 NULL 了。
private ScopeItem GetCurrentItem(string contextKey) { // 从数据上下文获取指定 ContextKey 当前关联的 Id 值。 var objKey = _dataContext.GetData(contextKey) as string; // 不存在则返回 NULL,存在则尝试以 Id 从字典中拿取对象外部,并返回。 return objKey != null ? ScopeDictionary.GetOrDefault(objKey) : null; }