1. 简介

工作单元:维护受事务影响的对象列表,并协调对象改变的持久化和解决并发场景的问题

  1. EntityFrameworkCore 中使用 DbContext 封装了:

    • 实体对象状态记录跟踪
    • 数据库的交互
    • 数据库事务
  2. 关于协调对象改变的持久化是通过调用 DbContext 的相关方法实现的
  3. 在并发场景下 DbContext 的使用也完全交给了开发者处理,主要靠文档规范说明 DbContext 的使用。

2. DbContext 生命周期和使用规范

2.1. 生命周期

DbContext 的生命周期创建实例时开始,并在释放实例时结束。 DbContext 实例旨在用于单个工作单元。 这意味着 DbContext 实例的生命周期通常很短。

使用 Entity Framework Core (EF Core) 时的典型工作单元包括:

  • 创建 DbContext 实例
  • 根据上下文跟踪实体实例。 实体将在以下情况下被跟踪
    • 查询返回
    • 添加附加DbContext
  • 根据需要对所跟踪的实体进行更改以实现业务规则
  • 调用 SaveChangesSaveChangesAsync
    • EF Core 将检测所做的更改,并将这些更改写入数据库。
  • 释放 DbContext 实例

2.2. 使用规范

  • 使用后释放 DbContext 非常重要。 这可确保释放所有非托管资源,并注销任何事件其他钩子(hooks),以防止实例在保持引用时出现内存泄漏。
  • DbContext 不是线程安全的。 不要在线程之间共享 DbContext。 请确保在继续使用 DbContext 实例之前,等待所有异步调用。
  • EF Core 代码引发的 InvalidOperationException 可以使 DbContext 进入不可恢复的状态。
    • 此类异常指示程序错误,并且不应该从其中恢复。

2.3. 避免 DbContext 线程处理问题

  • Entity Framework Core 不支持在同一 DbContext 实例上运行多个并行操作。

    • 这包括异步查询的并行执行以及从多个线程进行的任何显式并发使用。
  • 因此,始终 await 异步调用,或对并行执行的操作使用单独的 DbContext 实例。
  • EF Core 检测到尝试同时使用 DbContext 实例时,将会抛出异常 InvalidOperationException ,其中包含类:
    • 在上一个操作完成之前,第二个操作已在此 DbContext 中启动。

      • 使用同一个 DbContext 实例的不同线程不保证实例成员是线程安全的,因此抛出此异常。

如果框架没检测到并发访问,可能会导致不可预知的行为:应用程序崩溃数据损坏

并发访问 DbContext 实例的常见情况:

  • 异步操作缺陷

    • 使用异步方法,EF Core 可以启动以非阻塞式访问数据库的操作。 但是,如果调用方不等待其中一个方法完成,而是继续对 DbContext 执行其他操作,则 DbContext 的状态可能会(并且很可能会)损坏
  • 通过依赖注入隐式共享 DbContext 实例

    • 默认情况下 AddDbContext 扩展方法使用有范围的生命周期来注册 DbContext 类型。
    • 这样可以避免在大多数 ASP.NET Core 应用程序中出现并发访问问题
      • 因为在给定时间内只有一个线程在执行每个客户端请求,并且每个请求都有单独的依赖注入范围(dependency injection scope)(因此有单独的 DbContext 实例)。
      • 对于 Blazor Server 托管模型,一个逻辑请求用来维护 Blazor 用户线路,因此,如果使用默认注入范围,则每个用户线路只能提供一个范围内的 DbContext 实例。
  • 建议

    • 任何显式的并行执行多个线程的代码都应确保 DbContext 实例不会同时访问。
    • 使用依赖注入可以通过以下方式实现:
      • DbContext 注册为范围的,并为每个线程创建范围的服务提供实例(使用 IServiceScopeFactory)
      • DbContext 注册为瞬时的(transient)
        • 程序初始化时使用具有 ServiceLifetime 参数的 AddDbContext 方法的重载

引用:

3. 封装-工作单元

上面的内容来源于官方文档,结合 使用规范建议 的要求开始进行设计和封装。

首先声明:

其实DbContext 的设计是很棒的,对于使用者更自由,更开放

本文分享的关于工作单元的设计和封装是针对我们经常面临的特定的场景下对 DbContext取用生命周期的维护进行的,目的是更优美,更方便的完成对工作单元的使用。

3.1. 分析

DbContext 的生存期从创建实例时开始,并在释放实例时结束。我们对数据库的操作都需要通过 DbContext 进行实现。简单粗暴的方式是使用是 new 一个 DbContext 对象,操作完再进行 Dispose ,但这样对使用者不友好。考虑再三,我认为可以从解决以下几个问题:

  • DbContext取用的封装

  • DbContext生命周期维护的封装
    • 根据 DbContext 的初始化对依赖注入的配置,使用范围型的方式依赖注入注册 DbContext
    • 通过范围型(IServiceScope)的服务提供者(ServiceProvider)控制 DbContext生命周期
  • DbContextCURD进行封装
    • 采用仓储的方式对 Insert, Update, Get, Delete 等数据访问操作进行封装

3.2. 设计

3.2.1. 类图

classDiagram

class AsyncLocalCurrentUnitOfWorkHandleProvider~TDbContext~{
-AsyncLocal~LocalUowWrapper~ AsyncLocalUow$
+GetCurrentUow()$ IUnitOfWorkHandle~TDbContext~
+SetCurrentUow(IUnitOfWorkHandle~TDbContext~ value)$
}

class LocalUowWrapper~TDbContext~{
+ IUnitOfWorkHandle~TDbContext~ UnitOfWorkHandle
+ LocalUowWrapper~TDbContext~ Outer
}

class IUnitOfWorkHandle~TDbContext~{
<<Interface>>
+ GetActiveUnitOfWork() TDbContext
+ SaveChange() int
+ SaveChangeAsync() Task<int>
+ IsDisposed() bool
}
UnitOfWorkHandle~TDbContext~
InnerUnitOfWorkHandle~TDbContext~

class IUnitOfWorkManager{
<<Interface>>
+Begin() IUnitOfWorkHandle~TDbContext~
+BeginNew() IUnitOfWorkHandle~TDbContext~
}

AsyncLocalCurrentUnitOfWorkHandleProvider~TDbContext~ ..> LocalUowWrapper~TDbContext~
AsyncLocalCurrentUnitOfWorkHandleProvider~TDbContext~ ..> UnitOfWorkHandle~TDbContext~
UnitOfWorkHandle~TDbContext~ ..|> IUnitOfWorkHandle~TDbContext~
InnerUnitOfWorkHandle~TDbContext~ ..|> IUnitOfWorkHandle~TDbContext~
UnitOfWorkManager ..|> IUnitOfWorkManager
UnitOfWorkManager ..> UnitOfWorkHandle~TDbContext~
UnitOfWorkManager ..> InnerUnitOfWorkHandle~TDbContext~
UnitOfWorkManager ..> AsyncLocalCurrentUnitOfWorkHandleProvider~TDbContext~

3.2.2. 时序图

sequenceDiagram
participant User
participant UnitOfWorkManager
participant UnitOfWorkHandle
participant InnerUnitOfWorkHandle
participant AsyncLocalCurrentUnitOfWorkHandleProvider
par Begin() 创建 IUnitOfWorkHandle
User->>+UnitOfWorkManager: Begin()
UnitOfWorkManager->>AsyncLocalCurrentUnitOfWorkHandleProvider: 读取当前 UnitOfWorkHandle
AsyncLocalCurrentUnitOfWorkHandleProvider -->> UnitOfWorkManager:

alt 当前不存在 UnitOfWorkHandle

UnitOfWorkManager->>UnitOfWorkManager: BeginNew
else 当前存在 UnitOfWorkHandle
UnitOfWorkManager->>InnerUnitOfWorkHandle: 创建对象
InnerUnitOfWorkHandle-->>UnitOfWorkManager:
end
UnitOfWorkManager-->>-User:
and BeginNew() 创建 IUnitOfWorkHandle
User->>+UnitOfWorkManager: BeginNew()

UnitOfWorkManager->>UnitOfWorkHandle: 创建对象
UnitOfWorkHandle-->>UnitOfWorkManager:

UnitOfWorkManager->>AsyncLocalCurrentUnitOfWorkHandleProvider: 设置当前 UnitOfWorkHandle
alt 当前值不为空
AsyncLocalCurrentUnitOfWorkHandleProvider->>AsyncLocalCurrentUnitOfWorkHandleProvider: 构建新 LocalUowWrapper 并将当前值作为其Outer属性
else 当前值为空
AsyncLocalCurrentUnitOfWorkHandleProvider->>AsyncLocalCurrentUnitOfWorkHandleProvider: 赋值为新构建 LocalUowWrapper
end
AsyncLocalCurrentUnitOfWorkHandleProvider -->> UnitOfWorkManager:

UnitOfWorkManager-->>-User:
end

User->>+InnerUnitOfWorkHandle: Dispose()
InnerUnitOfWorkHandle->>InnerUnitOfWorkHandle: 空实现
InnerUnitOfWorkHandle-->>-User: return

User->>+UnitOfWorkHandle: Dispose()
UnitOfWorkHandle->>UnitOfWorkHandle: 释放当前 IServiceScope
UnitOfWorkHandle->>AsyncLocalCurrentUnitOfWorkHandleProvider: 清除当前 UnitOfWorkHandle
alt 当前值的Outer不为空
AsyncLocalCurrentUnitOfWorkHandleProvider->>AsyncLocalCurrentUnitOfWorkHandleProvider: 将当前值设置为原始值的Outer属性
else 当前值的Outer为空
AsyncLocalCurrentUnitOfWorkHandleProvider->>AsyncLocalCurrentUnitOfWorkHandleProvider: 当前值设置为: null
end
AsyncLocalCurrentUnitOfWorkHandleProvider -->> UnitOfWorkHandle:
UnitOfWorkHandle-->>-User: return

3.2.3. 说明

  • 使用泛型的方式封装工作单元相关类,当前生效的 DbContext 显而易见,访问多个数据库时互不干扰;
  • IUnitOfWorkManager 负责创建工作单元处理器
  • 通过 IUnitOfWorkHandle<TDbContext> 可访问当前的 DbContext 已完成数据访问,运行事务等相关操作;
  • 静态类AsyncLocalCurrentUnitOfWorkHandleProvider<TDbContext> 提供 DbContext 封装类型 IUnitOfWorkHandle<TDbContext> 对象的存取;
    • GetCurrentUow(): IUnitOfWorkHandle<TDbContext>void SetCurrentUow(IUnitOfWorkHandle<TDbContext> value) 负责将当前 IUnitOfWorkHandle<TDbContext> 的包装类型 LocalUowWrapper 的对象存储到类型为AsyncLocal<LocalUowWrapper> 的字段中,保证线程隔离;
  • LocalUowWrapper<TDbContext> 类型提供了 Outer 属性用于处理当工作单元处理器在嵌套方法中穿梭时,保证当前的工作单元处理器是设计者需要的;
  • InnerUnitOfWorkHandle<TDbContext> 的实现是:
    • 应对场景是:

      • 外部存在工作单元时使用该工作单元;
      • 外部不存在时需要创建工作单元;
    • 此实现类不是 DbContext 对象的代理,而是为了嵌套接力;
    • 也就是通过 UnitOfWorkManager 对象调用 Begin() 时在两种场景下返回不同类型的对象,详细参见源码部分。

3.3. 源代码

3.3.1. 工作单元

  1. IUnitOfWorkManagerUnitOfWorkManager

    1. using Microsoft.EntityFrameworkCore;
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Text;
    5. namespace EntityFramework.UnitOfWorks
    6. {
    7. public interface IUnitOfWorkManager
    8. {
    9. IUnitOfWorkHandle<TDbContext> Begin<TDbContext>() where TDbContext : DbContext;
    10. IUnitOfWorkHandle<TDbContext> BeginNew<TDbContext>() where TDbContext : DbContext;
    11. }
    12. }
    1. using Microsoft.EntityFrameworkCore;
    2. using Microsoft.Extensions.DependencyInjection;
    3. using System;
    4. using System.Collections.Generic;
    5. using System.Text;
    6. using System.Threading;
    7. namespace EntityFramework.UnitOfWorks.Impl
    8. {
    9. public class UnitOfWorkManager : IUnitOfWorkManager
    10. {
    11. private readonly IServiceProvider _serviceProvider;
    12. public UnitOfWorkManager(IServiceProvider serviceProvider)
    13. {
    14. _serviceProvider = serviceProvider;
    15. }
    16. public IUnitOfWorkHandle<TDbContext> Begin<TDbContext>() where TDbContext : DbContext
    17. {
    18. var uowHander = AsyncLocalCurrentUnitOfWorkHandleProvider<TDbContext>.Current;
    19. if (uowHander == null)
    20. {
    21. return BeginNew<TDbContext>();
    22. }
    23. else
    24. {
    25. return new InnerUnitOfWorkHandle<TDbContext>();
    26. }
    27. }
    28. public IUnitOfWorkHandle<TDbContext> BeginNew<TDbContext>() where TDbContext : DbContext
    29. {
    30. IServiceScope serviceScope = _serviceProvider.CreateScope();
    31. var uowHander = new UnitOfWorkHandle<TDbContext>(serviceScope);
    32. AsyncLocalCurrentUnitOfWorkHandleProvider<TDbContext>.Current = uowHander;
    33. return uowHander;
    34. }
    35. }
    36. }
  2. IUnitOfWorkHandle<TDbContext>, UnitOfWorkHandle<TDbContext>InnerUnitOfWorkHandle<TDbContext>

    1. using Microsoft.EntityFrameworkCore;
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Text;
    5. using System.Threading.Tasks;
    6. namespace EntityFramework.UnitOfWorks
    7. {
    8. public interface IUnitOfWorkHandle<TDbContext> : IDisposable where TDbContext:DbContext
    9. {
    10. TDbContext GetActiveUnitOfWork();
    11. int SaveChange();
    12. Task<int> SaveChangeAsync();
    13. bool IsDisposed { get; }
    14. }
    15. }
    1. using Microsoft.EntityFrameworkCore;
    2. using Microsoft.Extensions.DependencyInjection;
    3. using System;
    4. using System.Collections.Generic;
    5. using System.Text;
    6. using System.Threading;
    7. using System.Threading.Tasks;
    8. namespace EntityFramework.UnitOfWorks.Impl
    9. {
    10. public class UnitOfWorkHandle<TDbContext> : IUnitOfWorkHandle<TDbContext> where TDbContext : DbContext
    11. {
    12. private readonly IServiceScope _serviceScope;
    13. public bool IsDisposed { get; private set; }
    14. public UnitOfWorkHandle(IServiceScope serviceScope)
    15. {
    16. _serviceScope = serviceScope;
    17. }
    18. public TDbContext GetActiveUnitOfWork()
    19. {
    20. return _serviceScope.ServiceProvider.GetRequiredService<TDbContext>();
    21. }
    22. public void Dispose()
    23. {
    24. _serviceScope.Dispose();
    25. IsDisposed = true;
    26. // 清空当前 Handle 或回到 OuterHandle
    27. AsyncLocalCurrentUnitOfWorkHandleProvider<TDbContext>.Current = null;
    28. }
    29. public int SaveChange()
    30. {
    31. var dbContext = GetActiveUnitOfWork();
    32. if (dbContext == null)
    33. {
    34. return 0;
    35. }
    36. return dbContext.SaveChanges();
    37. }
    38. public async Task<int> SaveChangeAsync()
    39. {
    40. var dbContext = GetActiveUnitOfWork();
    41. if (dbContext == null)
    42. {
    43. return await Task.FromResult(0);
    44. }
    45. return await dbContext.SaveChangesAsync(CancellationToken.None);
    46. }
    47. }
    48. }
    1. using Microsoft.EntityFrameworkCore;
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Text;
    5. using System.Threading.Tasks;
    6. namespace Nuctech.TrDevice.EntityFramework.UnitOfWorks.Impl
    7. {
    8. public class InnerUnitOfWorkHandle<TDbContext> : IUnitOfWorkHandle<TDbContext> where TDbContext : DbContext
    9. {
    10. public bool IsDisposed { get; private set; }
    11. public void Dispose()
    12. {
    13. IsDisposed = true;
    14. }
    15. public TDbContext GetActiveUnitOfWork()
    16. => AsyncLocalCurrentUnitOfWorkHandleProvider<TDbContext>.Current?.GetActiveUnitOfWork();
    17. public int SaveChange()
    18. {
    19. return 0;
    20. }
    21. public Task<int> SaveChangeAsync()
    22. {
    23. return Task.FromResult(0);
    24. }
    25. }
    26. }
  3. AsyncLocalCurrentUnitOfWorkHandleProvider<TDbContext>LocalUowWrapper<TDbContext>

    • 由于这两个类是强关联的,所以这里将LocalUowWrapper<TDbContext> 定义为其内部类 LocalUowWrapper
    1. using Microsoft.EntityFrameworkCore;
    2. using System;
    3. using System.Collections.Generic;
    4. using System.Text;
    5. using System.Threading;
    6. namespace EntityFramework.UnitOfWorks.Impl
    7. {
    8. public class AsyncLocalCurrentUnitOfWorkHandleProvider<TDbContext> where TDbContext : DbContext
    9. {
    10. private static readonly AsyncLocal<LocalUowWrapper> AsyncLocalUow = new AsyncLocal<LocalUowWrapper>();
    11. public static UnitOfWorkHandle<TDbContext> Current
    12. {
    13. get { return GetCurrentUow(); }
    14. set { SetCurrentUow(value); }
    15. }
    16. private static UnitOfWorkHandle<TDbContext> GetCurrentUow()
    17. {
    18. var uow = AsyncLocalUow.Value?.UnitOfWorkHandle;
    19. if (uow == null)
    20. {
    21. return null;
    22. }
    23. if (uow.IsDisposed)
    24. {
    25. AsyncLocalUow.Value = null;
    26. return null;
    27. }
    28. return uow;
    29. }
    30. private static void SetCurrentUow(UnitOfWorkHandle<TDbContext> value)
    31. {
    32. lock (AsyncLocalUow)
    33. {
    34. if (value == null)
    35. {
    36. if (AsyncLocalUow.Value == null)
    37. {
    38. return;
    39. }
    40. if (AsyncLocalUow.Value.Outer == null)
    41. {
    42. AsyncLocalUow.Value.UnitOfWorkHandle = null;
    43. AsyncLocalUow.Value = null;
    44. return;
    45. }
    46. var oldValue = AsyncLocalUow.Value;
    47. AsyncLocalUow.Value = AsyncLocalUow.Value.Outer;
    48. oldValue.Outer = null;
    49. }
    50. else
    51. {
    52. if (AsyncLocalUow.Value?.UnitOfWorkHandle == null)
    53. {
    54. if (AsyncLocalUow.Value != null)
    55. {
    56. AsyncLocalUow.Value.UnitOfWorkHandle = value;
    57. }
    58. AsyncLocalUow.Value = new LocalUowWrapper(value);
    59. return;
    60. }
    61. var newValue = new LocalUowWrapper(value) { Outer = AsyncLocalUow.Value };
    62. AsyncLocalUow.Value = newValue;
    63. }
    64. }
    65. }
    66. private class LocalUowWrapper
    67. {
    68. public UnitOfWorkHandle<TDbContext> UnitOfWorkHandle { get; set; }
    69. public LocalUowWrapper Outer { get; set; }
    70. public LocalUowWrapper(UnitOfWorkHandle<TDbContext> unitOfWorkHandle)
    71. {
    72. UnitOfWorkHandle = unitOfWorkHandle;
    73. }
    74. }
    75. }
    76. }

3.3.2. 单元测试

  • 以下单元测试基本涵盖了常见的工作单元使用情况;
  • 事务时直接使用 DbContext 启动的事务,更复杂的情况请查看官方文档;
  1. using Microsoft.EntityFrameworkCore;
  2. using Microsoft.EntityFrameworkCore.Storage;
  3. using Microsoft.Extensions.DependencyInjection;
  4. using EntityFramework.UnitOfWorks;
  5. using EntityFramework.UnitOfWorks.Impl;
  6. using Shouldly;
  7. using System;
  8. using System.Collections.Generic;
  9. using System.Linq;
  10. using System.Threading.Tasks;
  11. using Xunit;
  12. namespace EntityFramework.UnitOfWorkTest
  13. {
  14. public class UnitOfWorkTests
  15. {
  16. public const string ConnectString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=TestDatabase";
  17. private readonly IServiceProvider _serviceProvider;
  18. private readonly IUnitOfWorkManager _unitOfWorkManager;
  19. public UnitOfWorkTests()
  20. {
  21. IServiceCollection services = new ServiceCollection();
  22. services.AddDbContext<PersionDbContext>(options => options.UseSqlServer(ConnectString));
  23. services.AddTransient<IUnitOfWorkManager, UnitOfWorkManager>();
  24. _serviceProvider = services.BuildServiceProvider();
  25. _unitOfWorkManager = _serviceProvider.GetRequiredService<IUnitOfWorkManager>();
  26. }
  27. /// <summary>
  28. /// 正常操作
  29. /// </summary>
  30. /// <returns></returns>
  31. [Fact]
  32. public async Task ShouldNormalOperation()
  33. {
  34. using (var uowHandle = _unitOfWorkManager.Begin<PersionDbContext>())
  35. {
  36. var lists = uowHandle.GetActiveUnitOfWork().Persions.ToList();
  37. //清理
  38. lists.ForEach(u => uowHandle.GetActiveUnitOfWork().Persions.Remove(u));
  39. uowHandle.SaveChange();
  40. }
  41. await AddUser("张三");
  42. await AddUser("李四");
  43. await AddUser("王五");
  44. using (var uowHandle = _unitOfWorkManager.Begin<PersionDbContext>())
  45. {
  46. var lists = uowHandle.GetActiveUnitOfWork().Persions.ToList();
  47. lists.Count.ShouldBe(3);
  48. //清理
  49. lists.ForEach(u => uowHandle.GetActiveUnitOfWork().Persions.Remove(u));
  50. uowHandle.SaveChange();
  51. }
  52. async Task AddUser(string name)
  53. {
  54. using (var uowHandle = _unitOfWorkManager.Begin<PersionDbContext>())
  55. {
  56. uowHandle.GetActiveUnitOfWork().Persions.Add(new Persion() { Name = name });
  57. await uowHandle.SaveChangeAsync();
  58. }
  59. }
  60. }
  61. /// <summary>
  62. /// 超出使用范围使用工作单元
  63. /// </summary>
  64. [Fact]
  65. public void ShouldNotUseUow()
  66. {
  67. var uowHandle = _unitOfWorkManager.Begin<PersionDbContext>();
  68. uowHandle.Dispose();
  69. Assert.Throws<ObjectDisposedException>(() => uowHandle.GetActiveUnitOfWork().Persions.ToList());
  70. }
  71. /// <summary>
  72. /// 工作单元嵌套时,当前工作单IUnitOfWorkHandle和DbContext的实际情况
  73. /// </summary>
  74. [Fact]
  75. public void ShouldAcrossMutiFunction()
  76. {
  77. using (var uowHandle = _unitOfWorkManager.Begin<PersionDbContext>())
  78. {
  79. var outerDbContext = uowHandle.GetActiveUnitOfWork();
  80. uowHandle.ShouldBeOfType<UnitOfWorkHandle<PersionDbContext>>();
  81. using (var innerUowHandle = _unitOfWorkManager.BeginNew<PersionDbContext>())
  82. {
  83. var innerDbContext = innerUowHandle.GetActiveUnitOfWork();
  84. innerUowHandle.GetActiveUnitOfWork().ShouldNotBe(outerDbContext);
  85. innerUowHandle.ShouldBeOfType<UnitOfWorkHandle<PersionDbContext>>();
  86. innerUowHandle.ShouldNotBe(uowHandle);
  87. using (var innerInnerUowHandle = _unitOfWorkManager.BeginNew<PersionDbContext>())
  88. {
  89. innerInnerUowHandle.ShouldBeOfType<UnitOfWorkHandle<PersionDbContext>>();
  90. innerInnerUowHandle.GetActiveUnitOfWork().ShouldNotBe(outerDbContext);
  91. innerInnerUowHandle.ShouldNotBe(uowHandle);
  92. }
  93. innerUowHandle.GetActiveUnitOfWork().ShouldBe(innerDbContext);
  94. }
  95. using (var innerUowHandle = _unitOfWorkManager.Begin<PersionDbContext>())
  96. {
  97. innerUowHandle.ShouldBeOfType<InnerUnitOfWorkHandle<PersionDbContext>>();
  98. innerUowHandle.GetActiveUnitOfWork().ShouldBe(outerDbContext);
  99. }
  100. uowHandle.GetActiveUnitOfWork().ShouldBe(outerDbContext);
  101. }
  102. }
  103. /// <summary>
  104. /// 使用数据库事务
  105. /// </summary>
  106. /// <param name="isCommit">是否提交数据</param>
  107. /// <returns></returns>
  108. [Theory]
  109. [InlineData(true)]
  110. [InlineData(false)]
  111. public async Task ShouldCommitTransaction(bool isCommit)
  112. {
  113. using (var uowHandle = _unitOfWorkManager.Begin<PersionDbContext>())
  114. {
  115. var lists = uowHandle.GetActiveUnitOfWork().Persions.ToList();
  116. lists.ForEach(u => uowHandle.GetActiveUnitOfWork().Persions.Remove(u));
  117. uowHandle.SaveChange();
  118. }
  119. List<string> names = new List<string> { "张三", "李四", "王老五" };
  120. using (var uowHandle = _unitOfWorkManager.Begin<PersionDbContext>())
  121. {
  122. using (var transaction = uowHandle.GetActiveUnitOfWork().Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted))
  123. {
  124. for (int i = 0; i < names.Count; i++)
  125. {
  126. uowHandle.GetActiveUnitOfWork().Persions.Add(new Persion() { Name = names[i] });
  127. //事务期间的SaveChange不会提交到数据库
  128. await uowHandle.SaveChangeAsync();
  129. }
  130. if (isCommit)
  131. {
  132. transaction.Commit();
  133. }
  134. else
  135. {
  136. transaction.Rollback();
  137. }
  138. }
  139. }
  140. using (var uowHandle = _unitOfWorkManager.Begin<PersionDbContext>())
  141. {
  142. uowHandle.GetActiveUnitOfWork().Persions.Count().ShouldBe(isCommit ? 3 : 0);
  143. }
  144. }
  145. }
  146. }

4. 封装-仓储

4.1. 分析

  • 关于仓储类的封装主要是为了更方便的使用工作单元
  • 面向我们经常使用的操作: Select, Insert, Update, Delete 四个方向进行封装。

4.2. 设计

4.2.1. 类图

classDiagram

class IRepository~TDbContext, TEntity~{
+IUnitOfWorkManager UnitOfWorkManager
+TDbContext CurrentDbContext
+BeginUnitOfWork() IUnitOfWorkHandle~TDbContext~
+BeginNewUnitOfWork() IUnitOfWorkHandle~TDbContext~
+Insert(TEntity entity) TEntity
+GetAll() IQueryable~TEntity~
+GetAllList() List~TEntity~
+Update(TEntity entity) TEntity
+Delete(TEntity entity)
}

EFRepository~TDbContext, TEntity~ ..|> IRepository~TDbContext, TEntity~
EFRepository~TDbContext, TEntity~ ..> AsyncLocalCurrentUnitOfWorkHandleProvider~TDbContext~
EFRepository~TDbContext, TEntity~ ..> AsyncLocalCurrentUnitOfWorkHandleProvider~TDbContext~

EFRepository~TDbContext, TEntity~ ..> IUnitOfWorkManager
EFRepository~TDbContext, TEntity~ ..> IUnitOfWorkHandle

4.2.2. 时序图

sequenceDiagram
participant EFRepository
participant AsyncLocalCurrentUnitOfWorkHandleProvider
participant IUnitOfWorkManager
participant IUnitOfWorkHandle

par 执行后需要DbContext继续生效的方法
note right of EFRepository: 比如返回IQueryable的方法
EFRepository->>AsyncLocalCurrentUnitOfWorkHandleProvider: 获取当前生效的 DbContext
AsyncLocalCurrentUnitOfWorkHandleProvider-->>EFRepository:
alt 存在
EFRepository->>EFRepository: 使用此上下文执行操作
else 不存在
EFRepository->>EFRepository: 抛出 ArgumentNullException 异常
end
and 执行后DbContext不生效也没影响的方法
EFRepository->>+IUnitOfWorkManager: Begin()一个工作单元
note right of IUnitOfWorkManager: 创建的工作单元处理器类型受外部影响,前面已介绍
IUnitOfWorkManager->>IUnitOfWorkHandle:
IUnitOfWorkHandle-->>IUnitOfWorkManager:

IUnitOfWorkManager-->>-EFRepository: Begin()一个工作单元
EFRepository->>IUnitOfWorkHandle: 获取生效的DbContext并进行操作
IUnitOfWorkHandle-->>EFRepository:
EFRepository->>IUnitOfWorkHandle: 释放资源: Dispose
IUnitOfWorkHandle-->>EFRepository:

end

4.2.3. 源码

  1. IRepository<TDbContext, TEntity>
  1. using Microsoft.EntityFrameworkCore;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Linq.Expressions;
  6. using System.Threading.Tasks;
  7. using EntityFramework.UnitOfWorks;
  8. namespace EntityFramework.Repositories
  9. public interface IRepository<TDbContext, TEntity> where TEntity : class where TDbContext : DbContext
  10. {
  11. IUnitOfWorkManager UnitOfWorkManager { get; }
  12. TDbContext CurrentDbContext { get; }
  13. IUnitOfWorkHandle<TDbContext> BeginUnitOfWork();
  14. IUnitOfWorkHandle<TDbContext> BeginNewUnitOfWork();
  15. #region Select/Get/Query
  16. IQueryable<TEntity> GetAll();
  17. IQueryable<TEntity> GetAllIncluding(params Expression<Func<TEntity, object>>[] propertySelectors);
  18. List<TEntity> GetAllList();
  19. Task<List<TEntity>> GetAllListAsync();
  20. List<TEntity> GetAllList(Expression<Func<TEntity, bool>> predicate);
  21. Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate);
  22. T Query<T>(Func<IQueryable<TEntity>, T> queryMethod);
  23. TEntity Single(Expression<Func<TEntity, bool>> predicate);
  24. Task<TEntity> SingleAsync(Expression<Func<TEntity, bool>> predicate);
  25. TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate);
  26. Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate);
  27. #endregion
  28. TEntity Insert(TEntity entity);
  29. Task<TEntity> InsertAsync(TEntity entity);
  30. TEntity Update(TEntity entity);
  31. Task<TEntity> UpdateAsync(TEntity entity);
  32. void Delete(TEntity entity);
  33. Task DeleteAsync(TEntity entity);
  34. }
  35. }
  1. EFRepository<TDbContext, TEntity>
  1. using Microsoft.EntityFrameworkCore;
  2. using System;
  3. using System.Collections.Generic;
  4. using System.Linq;
  5. using System.Linq.Expressions;
  6. using System.Threading.Tasks;
  7. using EntityFramework.UnitOfWorks;
  8. using EntityFramework.UnitOfWorks.Impl;
  9. namespace EntityFramework.Repositories.Impl
  10. {
  11. public class EFRepository<TDbContext, TEntity> : IRepository<TDbContext, TEntity> where TEntity : class where TDbContext : DbContext
  12. {
  13. private readonly IUnitOfWorkManager _unitOfWorkManager;
  14. public EFRepository(IUnitOfWorkManager unitOfWorkManager)
  15. {
  16. _unitOfWorkManager = unitOfWorkManager;
  17. }
  18. public virtual TDbContext CurrentDbContext => AsyncLocalCurrentUnitOfWorkHandleProvider<TDbContext>.Current?.GetActiveUnitOfWork();
  19. public IUnitOfWorkManager UnitOfWorkManager => _unitOfWorkManager;
  20. public IUnitOfWorkHandle<TDbContext> BeginUnitOfWork()
  21. {
  22. return _unitOfWorkManager.Begin<TDbContext>();
  23. }
  24. public IUnitOfWorkHandle<TDbContext> BeginNewUnitOfWork()
  25. {
  26. return _unitOfWorkManager.BeginNew<TDbContext>();
  27. }
  28. public TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate)
  29. {
  30. using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
  31. return uowHander.GetActiveUnitOfWork().Set<TEntity>().FirstOrDefault(predicate);
  32. }
  33. public async Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
  34. {
  35. using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
  36. return await uowHander.GetActiveUnitOfWork().Set<TEntity>().FirstOrDefaultAsync(predicate);
  37. }
  38. public IQueryable<TEntity> GetAll()
  39. {
  40. var context = CurrentDbContext;
  41. if (context != null)
  42. {
  43. return context.Set<TEntity>().AsQueryable();
  44. }
  45. throw new ArgumentNullException(nameof(CurrentDbContext));
  46. }
  47. public IQueryable<TEntity> GetAllIncluding(params Expression<Func<TEntity, object>>[] propertySelectors)
  48. {
  49. var context = CurrentDbContext;
  50. if (context != null)
  51. {
  52. var query = context.Set<TEntity>().AsQueryable();
  53. if (propertySelectors != null)
  54. {
  55. foreach (var propertySelector in propertySelectors)
  56. {
  57. query = query.Include(propertySelector);
  58. }
  59. }
  60. return query;
  61. }
  62. throw new ArgumentNullException(nameof(CurrentDbContext));
  63. }
  64. public List<TEntity> GetAllList()
  65. {
  66. using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
  67. return uowHander.GetActiveUnitOfWork().Set<TEntity>().ToList();
  68. }
  69. public List<TEntity> GetAllList(Expression<Func<TEntity, bool>> predicate)
  70. {
  71. using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
  72. return uowHander.GetActiveUnitOfWork().Set<TEntity>().Where(predicate).ToList();
  73. }
  74. public async Task<List<TEntity>> GetAllListAsync()
  75. {
  76. using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
  77. return await uowHander.GetActiveUnitOfWork().Set<TEntity>().ToListAsync();
  78. }
  79. public async Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate)
  80. {
  81. using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
  82. return await uowHander.GetActiveUnitOfWork().Set<TEntity>().Where(predicate).ToListAsync();
  83. }
  84. public T Query<T>(Func<IQueryable<TEntity>, T> queryMethod)
  85. {
  86. using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
  87. return queryMethod(GetAll());
  88. }
  89. public TEntity Single(Expression<Func<TEntity, bool>> predicate)
  90. {
  91. using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
  92. return uowHander.GetActiveUnitOfWork().Set<TEntity>().SingleOrDefault(predicate);
  93. }
  94. public async Task<TEntity> SingleAsync(Expression<Func<TEntity, bool>> predicate)
  95. {
  96. using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
  97. return await uowHander.GetActiveUnitOfWork().Set<TEntity>().SingleOrDefaultAsync(predicate);
  98. }
  99. public TEntity Insert(TEntity entity)
  100. {
  101. using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
  102. entity = uowHander.GetActiveUnitOfWork().Set<TEntity>().Add(entity).Entity;
  103. uowHander.SaveChange();
  104. return entity;
  105. }
  106. public async Task<TEntity> InsertAsync(TEntity entity)
  107. {
  108. using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
  109. entity = uowHander.GetActiveUnitOfWork().Set<TEntity>().Add(entity).Entity;
  110. await uowHander.SaveChangeAsync();
  111. return entity;
  112. }
  113. public TEntity Update(TEntity entity)
  114. {
  115. using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
  116. var context = uowHander.GetActiveUnitOfWork();
  117. AttachIfNot(context, entity);
  118. context.Entry(entity).State = EntityState.Modified;
  119. uowHander.SaveChange();
  120. return entity;
  121. }
  122. public async Task<TEntity> UpdateAsync(TEntity entity)
  123. {
  124. using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
  125. var context = uowHander.GetActiveUnitOfWork();
  126. AttachIfNot(context, entity);
  127. context.Entry(entity).State = EntityState.Modified;
  128. await uowHander.SaveChangeAsync();
  129. return entity;
  130. }
  131. public void Delete(TEntity entity)
  132. {
  133. using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
  134. var context = uowHander.GetActiveUnitOfWork();
  135. AttachIfNot(context, entity);
  136. context.Set<TEntity>().Remove(entity);
  137. uowHander.SaveChange();
  138. }
  139. public async Task DeleteAsync(TEntity entity)
  140. {
  141. using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
  142. var context = uowHander.GetActiveUnitOfWork();
  143. AttachIfNot(context, entity);
  144. context.Set<TEntity>().Remove(entity);
  145. await uowHander.SaveChangeAsync();
  146. }
  147. protected virtual void AttachIfNot(DbContext dbContext, TEntity entity)
  148. {
  149. var entry = dbContext.ChangeTracker.Entries().FirstOrDefault(ent => ent.Entity == entity);
  150. if (entry != null)
  151. {
  152. return;
  153. }
  154. dbContext.Set<TEntity>().Attach(entity);
  155. }
  156. }
  157. }

5. 总结

本片文章目的是分享自己将DbContext的使用封装为工作单元的心得,希望给大家一点启发。

欢迎大家留言讨论,如果文章有错误欢迎指正。

EntityFrameworkCore之工作单元的封装的更多相关文章

  1. 手工搭建基于ABP的框架 - 工作单元以及事务管理

    一个业务功能往往不只由一次数据库请求(或者服务调用)实现.为了功能的完整性,我们希望如果该功能执行一半时出错,则撤销前面已执行的改动.在数据库层面上,事务管理实现了这种完整性需求.在ABP中,一个完整 ...

  2. 5.在MVC中使用泛型仓储模式和工作单元来进行增删查改

    原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...

  3. .NET应用架构设计—工作单元模式(摆脱过程式代码的重要思想,代替DDD实现轻量级业务)

    阅读目录: 1.背景介绍 2.过程式代码的真正困境 3.工作单元模式的简单示例 4.总结 1.背景介绍 一直都在谈论面向对象开发,但是开发企业应用系统时,使用面向对象开发最大的问题就是在于,多个对象之 ...

  4. [ABP]浅谈工作单元 在整个 ABP 框架当中的应用

    ABP在其内部实现了工作单元模式,统一地进行事务与连接管理. 其核心就是通过 Castle 的 Dynamic Proxy 进行动态代理,在组件注册的时候进行拦截器注入,拦截到实现了 Unit Of ...

  5. [Abp 源码分析]六、工作单元的实现

    0.简介 在 Abp 框架内部实现了工作单元,在这里讲解一下,什么是工作单元? Unit Of Work(工作单元)模式用来维护一个由已经被业务事物修改(增加.删除或更新)的业务对象组成的列表.Uni ...

  6. 浅谈工作单元 在整个 ABP 框架当中的应用

    ABP在其内部实现了工作单元模式,统一地进行事务与连接管理. 其核心就是通过 Castle 的 Dynamic Proxy 进行动态代理,在组件注册的时候进行拦截器注入,拦截到实现了 Unit Of ...

  7. EsayUI + MVC + ADO.NET(工作单元)

    关联的设计 关联本身不是一个模式,但它在领域建模的过程中非常重要,所以需要在探讨各种模式之前,先讨论一下对象之间的关联该如何设计.我觉得对象的关联的设计可以遵循如下的一些原则: 关联尽量少,对象之间的 ...

  8. 仓储(Repository)和工作单元模式(UnitOfWork)

    仓储和工作单元模式 仓储模式 为什么要用仓储模式 通常不建议在业务逻辑层直接访问数据库.因为这样可能会导致如下结果: 重复的代码 编程错误的可能性更高 业务数据的弱类型 更难集中处理数据,比如缓存 无 ...

  9. 在MVC中使用泛型仓储模式和工作单元来进行增删查改

    原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...

随机推荐

  1. 重新上手c语言的一些坑

    c语言结构体不能声明函数,放几个函数指针倒是没问题 c语言结构体不能在声明时初始化 声明两个指针 int *a,*b; 或者typedef int* int_P int_P a,b; typedef要 ...

  2. u-boot 移植 --->2、在u-boot新增SOC和板子

    本次主要是要新增一个samsung的芯片到u-boot中,网上查阅资料发现s5pc1xx是与手上的S5PV210的友善的Tiny版子寄存器兼容的比较多,所以就准备以他为基础增加一个我的板子的支持到u- ...

  3. Windows 10 自带 free 屏幕截图/录像软件 Game Bar! 不仅仅是game-游戏呦! 高清晰,高保真,perfect!不仅仅是游戏呦!

    good news! good news! good news! 重要的事情说三遍! Windows 10 自带  屏幕截图/录像软件 Game Bar! 以后再也不用第三方的 盗版软件了! 对于Wi ...

  4. Apple CSS Animation All In One

    Apple CSS Animation All In One Apple Watch CSS Animation https://codepen.io/xgqfrms/pen/LYZaNMb See ...

  5. React Component All In One

    React Component All In One https://reactjs.org/docs/react-api.html#components React Class Component ...

  6. Apple 反人类的设计的产品组合

    Apple 反人类的设计的产品组合 Apple shit design macbook pro 2018 + beats solo3 MBP 的耳机孔在电脑右边, betas 的耳机孔在左边, 组合起 ...

  7. IoT & Raspberry Pi

    IoT & Raspberry Pi https://www.raspberrypi.org/ https://www.raspberrypi.org/training https://pro ...

  8. js & input event & input change event

    js & input event & input change event vue & search & input change <input @click=& ...

  9. window resize & resize observer

    window resize & resize observer https://developer.mozilla.org/en-US/docs/Web/API/Window/resize_e ...

  10. NGK DeFi Baccarat怎么玩能赚钱?

    市面上大多数DeFi项目都是基于以太坊来开发的,除了吞吐量低.存储量小以及交易速度慢等问题以外,高额的Gas手续费将不少终端用户拒之门外. 基于此NGK.IO推出了低门槛的DeFi项目-- Bacca ...