C# 结合 using 语句块的三种实用方法 (2)

转到其抽象类 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() 方法的时候就进行释放。

C# 结合 using 语句块的三种实用方法

所以接口提供了两个方法,第一个我们先看 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; }

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpgyjp.html