EntityFrameworkCore之工作单元的封装
1. 简介
工作单元:维护受事务影响的对象列表,并协调对象改变的持久化和解决并发场景的问题
- 在 EntityFrameworkCore 中使用
DbContext
封装了:- 实体对象状态记录跟踪
- 数据库的交互
- 数据库事务
- 关于协调对象改变的持久化是通过调用
DbContext
的相关方法实现的 - 在并发场景下
DbContext
的使用也完全交给了开发者处理,主要靠文档规范说明DbContext
的使用。
2. DbContext 生命周期和使用规范
2.1. 生命周期
DbContext 的生命周期从创建实例时开始,并在释放实例时结束。 DbContext 实例旨在用于单个工作单元。 这意味着 DbContext
实例的生命周期通常很短。
使用 Entity Framework Core (EF Core) 时的典型工作单元包括:
- 创建 DbContext 实例
- 根据上下文跟踪实体实例。 实体将在以下情况下被跟踪
- 查询返回时
- 添加或附加到
DbContext
- 根据需要对所跟踪的实体进行更改以实现业务规则
- 调用
SaveChanges
或SaveChangesAsync
- 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
的状态可能会(并且很可能会)损坏。
- 使用异步方法,EF Core 可以启动以非阻塞式访问数据库的操作。 但是,如果调用方不等待其中一个方法完成,而是继续对
通过依赖注入隐式共享 DbContext 实例
- 默认情况下
AddDbContext
扩展方法使用有范围的生命周期来注册DbContext
类型。 - 这样可以避免在大多数 ASP.NET Core 应用程序中出现并发访问问题
- 因为在给定时间内只有一个线程在执行每个客户端请求,并且每个请求都有单独的依赖注入范围(dependency injection scope)(因此有单独的
DbContext
实例)。 - 对于 Blazor Server 托管模型,一个逻辑请求用来维护 Blazor 用户线路,因此,如果使用默认注入范围,则每个用户线路只能提供一个范围内的 DbContext 实例。
- 因为在给定时间内只有一个线程在执行每个客户端请求,并且每个请求都有单独的依赖注入范围(dependency injection scope)(因此有单独的
- 默认情况下
建议
- 任何显式的并行执行多个线程的代码都应确保
DbContext
实例不会同时访问。 - 使用依赖注入可以通过以下方式实现:
- 将
DbContext
注册为范围的,并为每个线程创建范围的服务提供实例(使用 IServiceScopeFactory) - 将
DbContext
注册为瞬时的(transient)- 程序初始化时使用具有
ServiceLifetime
参数的AddDbContext
方法的重载
- 程序初始化时使用具有
- 将
- 任何显式的并行执行多个线程的代码都应确保
引用:
- https://docs.microsoft.com/zh-cn/ef/core/dbcontext-configuration/#the-dbcontext-lifetime
- https://docs.microsoft.com/zh-cn/ef/core/dbcontext-configuration/#avoiding-dbcontext-threading-issues
- https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.1#entity-framework-contexts-1
3. 封装-工作单元
上面的内容来源于官方文档,结合 使用规范 和 建议 的要求开始进行设计和封装。
首先声明:
其实DbContext
的设计是很棒的,对于使用者更自由,更开放。
本文分享的关于工作单元的设计和封装是针对我们经常面临的特定的场景下对DbContext
的取用、生命周期的维护进行的,目的是更优美,更方便的完成对工作单元的使用。
3.1. 分析
DbContext
的生存期从创建实例时开始,并在释放实例时结束。我们对数据库的操作都需要通过 DbContext
进行实现。简单粗暴的方式是使用是 new
一个 DbContext
对象,操作完再进行 Dispose
,但这样对使用者不友好。考虑再三,我认为可以从解决以下几个问题:
- 对
DbContext
的取用的封装- 参考Abp对工作单元的封装:
- 封装的非常巧妙和智能,但是也隐藏了很多细节,存在过度封装的嫌疑,但关于
DbContext
的取用可以视为是其中的精华,有很大的借鉴意义。 - 在Abp中
DbContext
是存放在AsyncLocal<T>
类型的静态字段中- 此数据结构的详细介绍请阅读《C#并发编程系列》中的《多线程共享变量和 AsyncLocal》
- 简单来说就是
AsyncLocal<T>
类型的静态字段在被并发访问时其中的一个线程及其辅助线程读取写入共享单对其他线程隔离
- 简单来说就是
- 此数据结构的详细介绍请阅读《C#并发编程系列》中的《多线程共享变量和 AsyncLocal》
- 对
DbContext
的生命周期维护的封装- 根据
DbContext
的初始化对依赖注入的配置,使用范围型的方式依赖注入注册DbContext
- 通过范围型(
IServiceScope
)的服务提供者(ServiceProvider
)控制DbContext
的生命周期。
- 根据
- 对
DbContext
的CURD进行封装- 采用仓储的方式对
Insert
,Update
,Get
,Delete
等数据访问操作进行封装
- 采用仓储的方式对
3.2. 设计
3.2.1. 类图
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. 时序图
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. 工作单元
IUnitOfWorkManager
和UnitOfWorkManager
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Text; namespace EntityFramework.UnitOfWorks
{
public interface IUnitOfWorkManager
{
IUnitOfWorkHandle<TDbContext> Begin<TDbContext>() where TDbContext : DbContext; IUnitOfWorkHandle<TDbContext> BeginNew<TDbContext>() where TDbContext : DbContext;
}
}
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; namespace EntityFramework.UnitOfWorks.Impl
{
public class UnitOfWorkManager : IUnitOfWorkManager
{
private readonly IServiceProvider _serviceProvider; public UnitOfWorkManager(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
} public IUnitOfWorkHandle<TDbContext> Begin<TDbContext>() where TDbContext : DbContext
{
var uowHander = AsyncLocalCurrentUnitOfWorkHandleProvider<TDbContext>.Current;
if (uowHander == null)
{
return BeginNew<TDbContext>();
}
else
{
return new InnerUnitOfWorkHandle<TDbContext>();
}
} public IUnitOfWorkHandle<TDbContext> BeginNew<TDbContext>() where TDbContext : DbContext
{
IServiceScope serviceScope = _serviceProvider.CreateScope();
var uowHander = new UnitOfWorkHandle<TDbContext>(serviceScope);
AsyncLocalCurrentUnitOfWorkHandleProvider<TDbContext>.Current = uowHander; return uowHander;
}
}
}
IUnitOfWorkHandle<TDbContext>
,UnitOfWorkHandle<TDbContext>
和InnerUnitOfWorkHandle<TDbContext>
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks; namespace EntityFramework.UnitOfWorks
{
public interface IUnitOfWorkHandle<TDbContext> : IDisposable where TDbContext:DbContext
{
TDbContext GetActiveUnitOfWork(); int SaveChange(); Task<int> SaveChangeAsync(); bool IsDisposed { get; }
}
}
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks; namespace EntityFramework.UnitOfWorks.Impl
{
public class UnitOfWorkHandle<TDbContext> : IUnitOfWorkHandle<TDbContext> where TDbContext : DbContext
{
private readonly IServiceScope _serviceScope; public bool IsDisposed { get; private set; } public UnitOfWorkHandle(IServiceScope serviceScope)
{
_serviceScope = serviceScope;
} public TDbContext GetActiveUnitOfWork()
{
return _serviceScope.ServiceProvider.GetRequiredService<TDbContext>();
} public void Dispose()
{
_serviceScope.Dispose();
IsDisposed = true;
// 清空当前 Handle 或回到 OuterHandle
AsyncLocalCurrentUnitOfWorkHandleProvider<TDbContext>.Current = null; } public int SaveChange()
{
var dbContext = GetActiveUnitOfWork();
if (dbContext == null)
{
return 0;
}
return dbContext.SaveChanges();
} public async Task<int> SaveChangeAsync()
{
var dbContext = GetActiveUnitOfWork();
if (dbContext == null)
{
return await Task.FromResult(0);
}
return await dbContext.SaveChangesAsync(CancellationToken.None);
}
}
}
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks; namespace Nuctech.TrDevice.EntityFramework.UnitOfWorks.Impl
{
public class InnerUnitOfWorkHandle<TDbContext> : IUnitOfWorkHandle<TDbContext> where TDbContext : DbContext
{
public bool IsDisposed { get; private set; } public void Dispose()
{
IsDisposed = true;
} public TDbContext GetActiveUnitOfWork()
=> AsyncLocalCurrentUnitOfWorkHandleProvider<TDbContext>.Current?.GetActiveUnitOfWork(); public int SaveChange()
{
return 0;
} public Task<int> SaveChangeAsync()
{
return Task.FromResult(0);
}
}
}
AsyncLocalCurrentUnitOfWorkHandleProvider<TDbContext>
和LocalUowWrapper<TDbContext>
- 由于这两个类是强关联的,所以这里将
LocalUowWrapper<TDbContext>
定义为其内部类LocalUowWrapper
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading; namespace EntityFramework.UnitOfWorks.Impl
{
public class AsyncLocalCurrentUnitOfWorkHandleProvider<TDbContext> where TDbContext : DbContext
{
private static readonly AsyncLocal<LocalUowWrapper> AsyncLocalUow = new AsyncLocal<LocalUowWrapper>(); public static UnitOfWorkHandle<TDbContext> Current
{
get { return GetCurrentUow(); }
set { SetCurrentUow(value); }
} private static UnitOfWorkHandle<TDbContext> GetCurrentUow()
{
var uow = AsyncLocalUow.Value?.UnitOfWorkHandle;
if (uow == null)
{
return null;
} if (uow.IsDisposed)
{
AsyncLocalUow.Value = null;
return null;
} return uow;
} private static void SetCurrentUow(UnitOfWorkHandle<TDbContext> value)
{
lock (AsyncLocalUow)
{
if (value == null)
{
if (AsyncLocalUow.Value == null)
{
return;
} if (AsyncLocalUow.Value.Outer == null)
{
AsyncLocalUow.Value.UnitOfWorkHandle = null;
AsyncLocalUow.Value = null;
return;
}
var oldValue = AsyncLocalUow.Value;
AsyncLocalUow.Value = AsyncLocalUow.Value.Outer;
oldValue.Outer = null;
}
else
{
if (AsyncLocalUow.Value?.UnitOfWorkHandle == null)
{
if (AsyncLocalUow.Value != null)
{
AsyncLocalUow.Value.UnitOfWorkHandle = value;
} AsyncLocalUow.Value = new LocalUowWrapper(value);
return;
}
var newValue = new LocalUowWrapper(value) { Outer = AsyncLocalUow.Value };
AsyncLocalUow.Value = newValue;
}
}
} private class LocalUowWrapper
{
public UnitOfWorkHandle<TDbContext> UnitOfWorkHandle { get; set; } public LocalUowWrapper Outer { get; set; } public LocalUowWrapper(UnitOfWorkHandle<TDbContext> unitOfWorkHandle)
{
UnitOfWorkHandle = unitOfWorkHandle;
}
}
}
}
- 由于这两个类是强关联的,所以这里将
3.3.2. 单元测试
- 以下单元测试基本涵盖了常见的工作单元使用情况;
- 事务时直接使用
DbContext
启动的事务,更复杂的情况请查看官方文档;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.Extensions.DependencyInjection;
using EntityFramework.UnitOfWorks;
using EntityFramework.UnitOfWorks.Impl;
using Shouldly;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;
namespace EntityFramework.UnitOfWorkTest
{
public class UnitOfWorkTests
{
public const string ConnectString = @"Data Source=(localdb)\MSSQLLocalDB;Initial Catalog=TestDatabase";
private readonly IServiceProvider _serviceProvider;
private readonly IUnitOfWorkManager _unitOfWorkManager;
public UnitOfWorkTests()
{
IServiceCollection services = new ServiceCollection();
services.AddDbContext<PersionDbContext>(options => options.UseSqlServer(ConnectString));
services.AddTransient<IUnitOfWorkManager, UnitOfWorkManager>();
_serviceProvider = services.BuildServiceProvider();
_unitOfWorkManager = _serviceProvider.GetRequiredService<IUnitOfWorkManager>();
}
/// <summary>
/// 正常操作
/// </summary>
/// <returns></returns>
[Fact]
public async Task ShouldNormalOperation()
{
using (var uowHandle = _unitOfWorkManager.Begin<PersionDbContext>())
{
var lists = uowHandle.GetActiveUnitOfWork().Persions.ToList();
//清理
lists.ForEach(u => uowHandle.GetActiveUnitOfWork().Persions.Remove(u));
uowHandle.SaveChange();
}
await AddUser("张三");
await AddUser("李四");
await AddUser("王五");
using (var uowHandle = _unitOfWorkManager.Begin<PersionDbContext>())
{
var lists = uowHandle.GetActiveUnitOfWork().Persions.ToList();
lists.Count.ShouldBe(3);
//清理
lists.ForEach(u => uowHandle.GetActiveUnitOfWork().Persions.Remove(u));
uowHandle.SaveChange();
}
async Task AddUser(string name)
{
using (var uowHandle = _unitOfWorkManager.Begin<PersionDbContext>())
{
uowHandle.GetActiveUnitOfWork().Persions.Add(new Persion() { Name = name });
await uowHandle.SaveChangeAsync();
}
}
}
/// <summary>
/// 超出使用范围使用工作单元
/// </summary>
[Fact]
public void ShouldNotUseUow()
{
var uowHandle = _unitOfWorkManager.Begin<PersionDbContext>();
uowHandle.Dispose();
Assert.Throws<ObjectDisposedException>(() => uowHandle.GetActiveUnitOfWork().Persions.ToList());
}
/// <summary>
/// 工作单元嵌套时,当前工作单IUnitOfWorkHandle和DbContext的实际情况
/// </summary>
[Fact]
public void ShouldAcrossMutiFunction()
{
using (var uowHandle = _unitOfWorkManager.Begin<PersionDbContext>())
{
var outerDbContext = uowHandle.GetActiveUnitOfWork();
uowHandle.ShouldBeOfType<UnitOfWorkHandle<PersionDbContext>>();
using (var innerUowHandle = _unitOfWorkManager.BeginNew<PersionDbContext>())
{
var innerDbContext = innerUowHandle.GetActiveUnitOfWork();
innerUowHandle.GetActiveUnitOfWork().ShouldNotBe(outerDbContext);
innerUowHandle.ShouldBeOfType<UnitOfWorkHandle<PersionDbContext>>();
innerUowHandle.ShouldNotBe(uowHandle);
using (var innerInnerUowHandle = _unitOfWorkManager.BeginNew<PersionDbContext>())
{
innerInnerUowHandle.ShouldBeOfType<UnitOfWorkHandle<PersionDbContext>>();
innerInnerUowHandle.GetActiveUnitOfWork().ShouldNotBe(outerDbContext);
innerInnerUowHandle.ShouldNotBe(uowHandle);
}
innerUowHandle.GetActiveUnitOfWork().ShouldBe(innerDbContext);
}
using (var innerUowHandle = _unitOfWorkManager.Begin<PersionDbContext>())
{
innerUowHandle.ShouldBeOfType<InnerUnitOfWorkHandle<PersionDbContext>>();
innerUowHandle.GetActiveUnitOfWork().ShouldBe(outerDbContext);
}
uowHandle.GetActiveUnitOfWork().ShouldBe(outerDbContext);
}
}
/// <summary>
/// 使用数据库事务
/// </summary>
/// <param name="isCommit">是否提交数据</param>
/// <returns></returns>
[Theory]
[InlineData(true)]
[InlineData(false)]
public async Task ShouldCommitTransaction(bool isCommit)
{
using (var uowHandle = _unitOfWorkManager.Begin<PersionDbContext>())
{
var lists = uowHandle.GetActiveUnitOfWork().Persions.ToList();
lists.ForEach(u => uowHandle.GetActiveUnitOfWork().Persions.Remove(u));
uowHandle.SaveChange();
}
List<string> names = new List<string> { "张三", "李四", "王老五" };
using (var uowHandle = _unitOfWorkManager.Begin<PersionDbContext>())
{
using (var transaction = uowHandle.GetActiveUnitOfWork().Database.BeginTransaction(System.Data.IsolationLevel.ReadCommitted))
{
for (int i = 0; i < names.Count; i++)
{
uowHandle.GetActiveUnitOfWork().Persions.Add(new Persion() { Name = names[i] });
//事务期间的SaveChange不会提交到数据库
await uowHandle.SaveChangeAsync();
}
if (isCommit)
{
transaction.Commit();
}
else
{
transaction.Rollback();
}
}
}
using (var uowHandle = _unitOfWorkManager.Begin<PersionDbContext>())
{
uowHandle.GetActiveUnitOfWork().Persions.Count().ShouldBe(isCommit ? 3 : 0);
}
}
}
}
4. 封装-仓储
4.1. 分析
- 关于仓储类的封装主要是为了更方便的使用工作单元;
- 面向我们经常使用的操作: Select, Insert, Update, Delete 四个方向进行封装。
4.2. 设计
4.2.1. 类图
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. 时序图
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. 源码
IRepository<TDbContext, TEntity>
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using EntityFramework.UnitOfWorks;
namespace EntityFramework.Repositories
public interface IRepository<TDbContext, TEntity> where TEntity : class where TDbContext : DbContext
{
IUnitOfWorkManager UnitOfWorkManager { get; }
TDbContext CurrentDbContext { get; }
IUnitOfWorkHandle<TDbContext> BeginUnitOfWork();
IUnitOfWorkHandle<TDbContext> BeginNewUnitOfWork();
#region Select/Get/Query
IQueryable<TEntity> GetAll();
IQueryable<TEntity> GetAllIncluding(params Expression<Func<TEntity, object>>[] propertySelectors);
List<TEntity> GetAllList();
Task<List<TEntity>> GetAllListAsync();
List<TEntity> GetAllList(Expression<Func<TEntity, bool>> predicate);
Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate);
T Query<T>(Func<IQueryable<TEntity>, T> queryMethod);
TEntity Single(Expression<Func<TEntity, bool>> predicate);
Task<TEntity> SingleAsync(Expression<Func<TEntity, bool>> predicate);
TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate);
Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate);
#endregion
TEntity Insert(TEntity entity);
Task<TEntity> InsertAsync(TEntity entity);
TEntity Update(TEntity entity);
Task<TEntity> UpdateAsync(TEntity entity);
void Delete(TEntity entity);
Task DeleteAsync(TEntity entity);
}
}
EFRepository<TDbContext, TEntity>
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using EntityFramework.UnitOfWorks;
using EntityFramework.UnitOfWorks.Impl;
namespace EntityFramework.Repositories.Impl
{
public class EFRepository<TDbContext, TEntity> : IRepository<TDbContext, TEntity> where TEntity : class where TDbContext : DbContext
{
private readonly IUnitOfWorkManager _unitOfWorkManager;
public EFRepository(IUnitOfWorkManager unitOfWorkManager)
{
_unitOfWorkManager = unitOfWorkManager;
}
public virtual TDbContext CurrentDbContext => AsyncLocalCurrentUnitOfWorkHandleProvider<TDbContext>.Current?.GetActiveUnitOfWork();
public IUnitOfWorkManager UnitOfWorkManager => _unitOfWorkManager;
public IUnitOfWorkHandle<TDbContext> BeginUnitOfWork()
{
return _unitOfWorkManager.Begin<TDbContext>();
}
public IUnitOfWorkHandle<TDbContext> BeginNewUnitOfWork()
{
return _unitOfWorkManager.BeginNew<TDbContext>();
}
public TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate)
{
using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
return uowHander.GetActiveUnitOfWork().Set<TEntity>().FirstOrDefault(predicate);
}
public async Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate)
{
using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
return await uowHander.GetActiveUnitOfWork().Set<TEntity>().FirstOrDefaultAsync(predicate);
}
public IQueryable<TEntity> GetAll()
{
var context = CurrentDbContext;
if (context != null)
{
return context.Set<TEntity>().AsQueryable();
}
throw new ArgumentNullException(nameof(CurrentDbContext));
}
public IQueryable<TEntity> GetAllIncluding(params Expression<Func<TEntity, object>>[] propertySelectors)
{
var context = CurrentDbContext;
if (context != null)
{
var query = context.Set<TEntity>().AsQueryable();
if (propertySelectors != null)
{
foreach (var propertySelector in propertySelectors)
{
query = query.Include(propertySelector);
}
}
return query;
}
throw new ArgumentNullException(nameof(CurrentDbContext));
}
public List<TEntity> GetAllList()
{
using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
return uowHander.GetActiveUnitOfWork().Set<TEntity>().ToList();
}
public List<TEntity> GetAllList(Expression<Func<TEntity, bool>> predicate)
{
using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
return uowHander.GetActiveUnitOfWork().Set<TEntity>().Where(predicate).ToList();
}
public async Task<List<TEntity>> GetAllListAsync()
{
using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
return await uowHander.GetActiveUnitOfWork().Set<TEntity>().ToListAsync();
}
public async Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate)
{
using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
return await uowHander.GetActiveUnitOfWork().Set<TEntity>().Where(predicate).ToListAsync();
}
public T Query<T>(Func<IQueryable<TEntity>, T> queryMethod)
{
using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
return queryMethod(GetAll());
}
public TEntity Single(Expression<Func<TEntity, bool>> predicate)
{
using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
return uowHander.GetActiveUnitOfWork().Set<TEntity>().SingleOrDefault(predicate);
}
public async Task<TEntity> SingleAsync(Expression<Func<TEntity, bool>> predicate)
{
using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
return await uowHander.GetActiveUnitOfWork().Set<TEntity>().SingleOrDefaultAsync(predicate);
}
public TEntity Insert(TEntity entity)
{
using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
entity = uowHander.GetActiveUnitOfWork().Set<TEntity>().Add(entity).Entity;
uowHander.SaveChange();
return entity;
}
public async Task<TEntity> InsertAsync(TEntity entity)
{
using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
entity = uowHander.GetActiveUnitOfWork().Set<TEntity>().Add(entity).Entity;
await uowHander.SaveChangeAsync();
return entity;
}
public TEntity Update(TEntity entity)
{
using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
var context = uowHander.GetActiveUnitOfWork();
AttachIfNot(context, entity);
context.Entry(entity).State = EntityState.Modified;
uowHander.SaveChange();
return entity;
}
public async Task<TEntity> UpdateAsync(TEntity entity)
{
using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
var context = uowHander.GetActiveUnitOfWork();
AttachIfNot(context, entity);
context.Entry(entity).State = EntityState.Modified;
await uowHander.SaveChangeAsync();
return entity;
}
public void Delete(TEntity entity)
{
using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
var context = uowHander.GetActiveUnitOfWork();
AttachIfNot(context, entity);
context.Set<TEntity>().Remove(entity);
uowHander.SaveChange();
}
public async Task DeleteAsync(TEntity entity)
{
using var uowHander = _unitOfWorkManager.Begin<TDbContext>();
var context = uowHander.GetActiveUnitOfWork();
AttachIfNot(context, entity);
context.Set<TEntity>().Remove(entity);
await uowHander.SaveChangeAsync();
}
protected virtual void AttachIfNot(DbContext dbContext, TEntity entity)
{
var entry = dbContext.ChangeTracker.Entries().FirstOrDefault(ent => ent.Entity == entity);
if (entry != null)
{
return;
}
dbContext.Set<TEntity>().Attach(entity);
}
}
}
5. 总结
本片文章目的是分享自己将DbContext的使用封装为工作单元的心得,希望给大家一点启发。
欢迎大家留言讨论,如果文章有错误欢迎指正。
EntityFrameworkCore之工作单元的封装的更多相关文章
- 手工搭建基于ABP的框架 - 工作单元以及事务管理
一个业务功能往往不只由一次数据库请求(或者服务调用)实现.为了功能的完整性,我们希望如果该功能执行一半时出错,则撤销前面已执行的改动.在数据库层面上,事务管理实现了这种完整性需求.在ABP中,一个完整 ...
- 5.在MVC中使用泛型仓储模式和工作单元来进行增删查改
原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...
- .NET应用架构设计—工作单元模式(摆脱过程式代码的重要思想,代替DDD实现轻量级业务)
阅读目录: 1.背景介绍 2.过程式代码的真正困境 3.工作单元模式的简单示例 4.总结 1.背景介绍 一直都在谈论面向对象开发,但是开发企业应用系统时,使用面向对象开发最大的问题就是在于,多个对象之 ...
- [ABP]浅谈工作单元 在整个 ABP 框架当中的应用
ABP在其内部实现了工作单元模式,统一地进行事务与连接管理. 其核心就是通过 Castle 的 Dynamic Proxy 进行动态代理,在组件注册的时候进行拦截器注入,拦截到实现了 Unit Of ...
- [Abp 源码分析]六、工作单元的实现
0.简介 在 Abp 框架内部实现了工作单元,在这里讲解一下,什么是工作单元? Unit Of Work(工作单元)模式用来维护一个由已经被业务事物修改(增加.删除或更新)的业务对象组成的列表.Uni ...
- 浅谈工作单元 在整个 ABP 框架当中的应用
ABP在其内部实现了工作单元模式,统一地进行事务与连接管理. 其核心就是通过 Castle 的 Dynamic Proxy 进行动态代理,在组件注册的时候进行拦截器注入,拦截到实现了 Unit Of ...
- EsayUI + MVC + ADO.NET(工作单元)
关联的设计 关联本身不是一个模式,但它在领域建模的过程中非常重要,所以需要在探讨各种模式之前,先讨论一下对象之间的关联该如何设计.我觉得对象的关联的设计可以遵循如下的一些原则: 关联尽量少,对象之间的 ...
- 仓储(Repository)和工作单元模式(UnitOfWork)
仓储和工作单元模式 仓储模式 为什么要用仓储模式 通常不建议在业务逻辑层直接访问数据库.因为这样可能会导致如下结果: 重复的代码 编程错误的可能性更高 业务数据的弱类型 更难集中处理数据,比如缓存 无 ...
- 在MVC中使用泛型仓储模式和工作单元来进行增删查改
原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...
随机推荐
- HDU 3341 Lost's revenge (AC自动机 + DP + 变进制/hash)题解
题意:给你些分数串,给你一个主串,主串每出现一个分数串加一分,要你重新排列主串,最多几分 思路:显然这里开$40^4$去状压内存不够.但是我们自己想想会发现根本不用开那么大,因为很多状态是废状压,不是 ...
- sdut2879 枚举起点DP
这个题和乌龟棋之类的DP差不多要学会缩减状态 就是,,我们只需枚举当前这个人是谁,选什么颜色,A用了多少,B用了多少 C用了多少我们就不用枚举了,知道选了多少人,A,B用了多少,你还不知C用了多少么, ...
- JavaScript for, for...in, for...of, for-await...of difference All In One
JavaScript for, for...in, for...of, for-await...of difference All In One for for...in for...of for-a ...
- ODM & mongoose
ODM & mongoose ODM (object data modeling) https://mongoosejs.com/ MongoDB NoSQL xgqfrms 2012-202 ...
- LeetCode 高效刷题路径
LeetCode 高效刷题路径 Hot 100 https://leetcode.com/problemset/hot-100/ https://leetcode-cn.com/problemset/ ...
- RocketMq灰皮书(一)------选型&RocketMQ名词
RocketMq灰皮书(一)------选型&RocketMQ名词 一. MQ选型对比 目前业内常用的MQ框架有一下几种: Kafka RabbitMQ RocketMQ 除此之外,还有Act ...
- React Portal - 弹出层的优秀解决方案
对于需要使用弹出层的需求 ,Portal可以说是提供了一种完美的解决方案.相比于React Native中的实现更多的使用Modal或者绝对定位,Portal实在是简易友好得多. 场景 对话框,确认提 ...
- Java并发包源码学习系列:线程池ScheduledThreadPoolExecutor源码解析
目录 ScheduledThreadPoolExecutor概述 类图结构 ScheduledExecutorService ScheduledFutureTask FutureTask schedu ...
- Redis常用数据类型及其存储结构(源码篇)
一.SDS 1,SDS源码解读 sds (Simple Dynamic String),Simple的意思是简单,Dynamic即动态,意味着其具有动态增加空间的能力,扩容不需要使用者关心.Strin ...
- css选择器,过滤筛选
$('.required:not(.final_price)').each(function() { if (!$(this).val()) { error_count ++; if ($(this) ...