2021-05-12 14:32:11
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
方法的過載
- 程式初始化時使用具有
- 將
- 任何顯式的並行執行多個執行緒的程式碼都應確保
參照:
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. 類圖
3.2.2. 時序圖
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. 類圖
4.2.2. 時序圖
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的使用封裝為工作單元的心得,希望給大家一點啟發。
歡迎大家留言討論,如果文章有錯誤歡迎指正。
相關文章