From 790ed478dae852929f8d6f74635a36870c0a296c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 22 Sep 2017 10:39:01 +0300 Subject: [PATCH 01/21] Refactored and improved UOW. Added simple UOW middleware. Created a unit test for UOW. --- .../Mvc/Uow/AbpUnitOfWorkMiddleware.cs | 25 +++++++++++ .../Volo/Abp/Uow/AmbientUnitOfWork.cs | 8 ++-- src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs | 7 +++ src/Volo.Abp/Volo/Abp/Uow/IBasicUnitOfWork.cs | 2 + .../Volo/Abp/Uow/IDatabaseApiContainer.cs | 2 +- src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs | 41 ++++++++++-------- .../Volo/Abp/Uow/UnitOfWorkManager.cs | 19 +------- .../Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs | 43 +++++++++++++++++++ 8 files changed, 108 insertions(+), 39 deletions(-) create mode 100644 src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs create mode 100644 test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs new file mode 100644 index 0000000000..0a19330cd4 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Volo.Abp.Uow; + +namespace Volo.Abp.AspNetCore.Mvc.Uow +{ + class AbpUnitOfWorkMiddleware + { + private readonly RequestDelegate _next; + + public AbpUnitOfWorkMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task Invoke(HttpContext httpContext, IUnitOfWorkManager unitOfWorkManager) + { + using (var uow = unitOfWorkManager.Begin()) + { + await _next(httpContext); + await uow.CompleteAsync(); + } + } + } +} diff --git a/src/Volo.Abp/Volo/Abp/Uow/AmbientUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/AmbientUnitOfWork.cs index 6c01daf350..7b3a45b20c 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/AmbientUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/AmbientUnitOfWork.cs @@ -6,18 +6,18 @@ namespace Volo.Abp.Uow [ExposeServices(typeof(IAmbientUnitOfWork), typeof(IUnitOfWorkAccessor))] public class AmbientUnitOfWork : IAmbientUnitOfWork, ISingletonDependency { - public IUnitOfWork UnitOfWork => _currentUowInfo.Value; + public IUnitOfWork UnitOfWork => _currentUow.Value; - private readonly AsyncLocal _currentUowInfo; + private readonly AsyncLocal _currentUow; public AmbientUnitOfWork() { - _currentUowInfo = new AsyncLocal(); + _currentUow = new AsyncLocal(); } public void SetUnitOfWork(IUnitOfWork unitOfWork) { - _currentUowInfo.Value = unitOfWork; + _currentUow.Value = unitOfWork; } } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs index 47dc909581..c20d106e4e 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs @@ -7,6 +7,8 @@ namespace Volo.Abp.Uow { internal class ChildUnitOfWork : IUnitOfWork { + public Guid Id => _parent.Id; + public event EventHandler Completed; public event EventHandler Failed; public event EventHandler Disposed; @@ -60,5 +62,10 @@ namespace Volo.Abp.Uow { } + + public override string ToString() + { + return $"[UnitOfWork {Id}]"; + } } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/IBasicUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/IBasicUnitOfWork.cs index 60038cae2b..11d26a7640 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IBasicUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IBasicUnitOfWork.cs @@ -7,6 +7,8 @@ namespace Volo.Abp.Uow //TODO: Find a better naming :( public interface IBasicUnitOfWork : IDisposable { + Guid Id { get; } + event EventHandler Completed; event EventHandler Failed; diff --git a/src/Volo.Abp/Volo/Abp/Uow/IDatabaseApiContainer.cs b/src/Volo.Abp/Volo/Abp/Uow/IDatabaseApiContainer.cs index 536823fd57..c24fe41797 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IDatabaseApiContainer.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IDatabaseApiContainer.cs @@ -10,6 +10,6 @@ namespace Volo.Abp.Uow IDatabaseApi FindDatabaseApi([NotNull] string id); [NotNull] - IDatabaseApi GetOrAddDatabaseApi(string id, Func factory); + IDatabaseApi GetOrAddDatabaseApi([NotNull] string id, [NotNull] Func factory); } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs index a94faba0d7..2207f94fcc 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs @@ -8,6 +8,8 @@ namespace Volo.Abp.Uow { public class UnitOfWork : IUnitOfWork, ITransientDependency { + public Guid Id { get; } = Guid.NewGuid(); + public event EventHandler Completed; public event EventHandler Failed; public event EventHandler Disposed; @@ -27,23 +29,6 @@ namespace Volo.Abp.Uow _databaseApis = new Dictionary(); } - public void Dispose() - { - if (_isDisposed) - { - return; - } - - _isDisposed = true; - - if (!_isCompleted || _exception != null) - { - OnFailed(_exception); - } - - OnDisposed(); - } - public void SaveChanges() { foreach (var databaseApi in _databaseApis.Values) @@ -122,6 +107,23 @@ namespace Volo.Abp.Uow Disposed.InvokeSafely(this); } + public void Dispose() + { + if (_isDisposed) + { + return; + } + + _isDisposed = true; + + if (!_isCompleted || _exception != null) + { + OnFailed(_exception); + } + + OnDisposed(); + } + private void PreventMultipleComplete() { if (_isCompleted) @@ -131,5 +133,10 @@ namespace Volo.Abp.Uow _isCompleted = true; } + + public override string ToString() + { + return $"[UnitOfWork {Id}]"; + } } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs index 534b1c0db8..46c1ec9c1d 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs @@ -1,5 +1,4 @@ using System; -using System.Diagnostics; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; @@ -41,27 +40,13 @@ namespace Volo.Abp.Uow _ambientUnitOfWork.SetUnitOfWork(unitOfWork); - Debug.Assert( - _ambientUnitOfWork.UnitOfWork != null, - "_ambientUnitOfWork.UnitOfWork can not be null since it's set by _ambientUnitOfWork.SetUnitOfWork method!" - ); - - unitOfWork.Completed += (sender, args) => - { - _ambientUnitOfWork.SetUnitOfWork(parentUow); - }; - - unitOfWork.Failed += (sender, args) => - { - _ambientUnitOfWork.SetUnitOfWork(parentUow); - }; - unitOfWork.Disposed += (sender, args) => { + _ambientUnitOfWork.SetUnitOfWork(parentUow); scope.Dispose(); }; - return _ambientUnitOfWork.UnitOfWork; + return unitOfWork; } } } \ No newline at end of file diff --git a/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs b/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs new file mode 100644 index 0000000000..160c390186 --- /dev/null +++ b/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs @@ -0,0 +1,43 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Modularity; +using Volo.Abp.TestBase; +using Xunit; + +namespace Volo.Abp.Uow +{ + public class UnitOfWork_Ambient_Scope_Tests : AbpIntegratedTest + { + private readonly IUnitOfWorkManager _unitOfWorkManager; + + public UnitOfWork_Ambient_Scope_Tests() + { + _unitOfWorkManager = ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task UnitOfWorkManager_Current_Should_Set_Correctly() + { + _unitOfWorkManager.Current.ShouldBeNull(); + + using (var uow1 = _unitOfWorkManager.Begin()) + { + _unitOfWorkManager.Current.ShouldNotBeNull(); + _unitOfWorkManager.Current.ShouldBe(uow1); + + using (var uow2 = _unitOfWorkManager.Begin()) + { + _unitOfWorkManager.Current.ShouldNotBeNull(); + _unitOfWorkManager.Current.Id.ShouldBe(uow1.Id); + + await uow2.CompleteAsync(); + } + + await uow1.CompleteAsync(); + } + + _unitOfWorkManager.Current.ShouldBeNull(); + } + } +} From 13d83674eeabb5964835c4f6a65c433de46fd15b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 22 Sep 2017 11:16:55 +0300 Subject: [PATCH 02/21] #23 implemented nested unit of works. --- src/AbpDesk/Web_PlugIns/AbpDesk.MongoBlog.dll | Bin 15872 -> 15872 bytes .../Mvc/Uow/AbpUnitOfWorkMiddleware.cs | 2 +- .../Volo/Abp/Uow/IUnitOfWorkManager.cs | 17 ++++++- .../Volo/Abp/Uow/UnitOfWorkManager.cs | 6 ++- .../Volo/Abp/Uow/UnitOfWorkStartOptions.cs | 7 +++ .../Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs | 3 ++ .../Volo/Abp/Uow/UnitOfWork_Nested_Tests.cs | 46 ++++++++++++++++++ 7 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs create mode 100644 test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Nested_Tests.cs diff --git a/src/AbpDesk/Web_PlugIns/AbpDesk.MongoBlog.dll b/src/AbpDesk/Web_PlugIns/AbpDesk.MongoBlog.dll index c5f958350e2d0841e41273300e364db3bd42ceb9..14344b9ee16c8c7bb31dccbc2fdee66d6b647980 100644 GIT binary patch delta 2164 zcmZvee{dA#8OOiR`(AeUF1Ndvz5FC0xtL5Y7egQ;K`;;!O_XRNp-I40rb0n5LkUbk zYp6r+WWZLyHoneSnz55~fDR*L2d^ECPMxG`T3R~=gt1nOs5or~D`RV`fH-~j&CT?W zx;OjT=lgx$_u0Mo-FJ7Bk0c-Y(f0$R-~H)JBL_;TY2?@%x}=EWST9=>V5;&^x= z7~wvvaLV*>U6qFlatE9e%14!1!gXK7-Q=OjQ5~TYYE=E9isFx)Q)VevgwGqL*cbE* z?FjmXYQwLn@lZVXxAq5IFgN^wW#Adhr{Su1L)FnE&M#QJW&Y8q!Fccqs=*hkAbalO zBD;+T&i_SlkGy6a|}IHtXQDCb&XOwP8)5_5)Z{5#WoNY1D-yO?HFqg_0ANV+KF z8ya{mc;0%HRkc>r!L*oBJM4seK;~fktmju!&)P+6mpKOVP{NA=c%NRBF;=BojTF8S zT*Cudqm@612J&%}R(lY_A?h zTBfcz^P_QRCI7uG=33-pIyitSx?MH8iQ!w_FL}l`i0`5aO40OiaEeB>)h`a4zoSFy z7wwQ37Zc9#Znh3udzqiuyXb^C5!x>tUQ~yLhfciCT*a*8h4q50Lll$DV!X$!z~7lS zYTkrTIBwMOZJKu>D^Q1S%xB(P(j6HaIIM9s!_*39Ed=GyOY(N@+ z3#6dfN#RwT4eY_ocqeOIJLB4!((#mzFX;IF4E^>W>X^u{K;%18$K@INPK%CPbar^1 z7D>%9%}LD*nh<`WG0jQM3!0F=AJc5sT&J0wbpp?Fb|GHk8UKi`QJ%=+Th`#m6HqC~ zBtO6%pU3ROg?t#*)}y9xePBJud{vC8&vQ1r_s|v^q@#3>&QqJ{Q(L_XdQy#eQ5sc8 zyp^j)#h$Rgo`jFgHY)nQ#=Gz}-o*cVsx2BX>a~F}!(U(gqT+akIU7Hu_C^!=G3rJG z_43}14jRH~8pSq|o{rXqb9x8*cdp#DX4~MF&h-Q9H{U;)o+*=NmMwSbgHD^p{lyRLc);FFEd|$O=)O*G^t;c7#aec`2Jp5eQ`r%Qw{0>LMyqtE+2Q zl?P(*I!*4JN_d@FEK>8Vm?Vdfj+y5@$cgfh$t9Do^9iQ>mYq4NdLt${aI6T4Qk24x zA#(iMOVpdyJq7JtC@0nF#<*V}?+;Sc*QzPeq&pwaD~#&;;T|LFHGkwy-Xtu%>M!_?9LPb delta 2088 zcmZvde{fXQ7018lzL)pj{&;UUFIf_jP1u+;KX6HjO(K-2EYQ)d1d=A9V(kFJ2#l>* zK%Ho6c5zfPAZ_MSMzonB)Yf5=*0Jkv)Pahzsak|-r_>?>rRiXu!A`3N(zHGIX8-Bb zH}gLCobUOuckX?6Z>BHP_rsm$k(e_+xI0c|gNMFJZ%LxMf_N9OKz6n!&6OSEGuk3=G?KJ5x7`RCv@DmgF3@%H5K#Rf zu#?ARgzc{FTQ}deahvM!Tp2KFu-4Os(Mll7TJ?=R>18~=%Kj&E*r}r)IqrN|blBZP z79K@M(89f@;#2Z)ps6Jk@v+4SMapo$bA#h!gZDG148J#Sa4K;CXDlDL+6O%!F9xDq z(~0+NALnFMu%!4KUYus3){b-Em#~6-OuKSCA!)o5Gb zpnN)5S3KgL=T^nxO^$^jNAV+1o)6X+4S3J6^9TD~Yc7TZduT3B%ZgBim=4RfP-$hG zH)XxX>*(d8dq4L?~>`Qa@;PBzTeWNaSVW5q6FO3N+b@}6$PLTR3Hfz$nDsGa43h)wAwws*-QJ71S*+y5(2xkF zC#$J9_Gh0X+I{sy|hq_1)iWbUDc~I_hU-DKv^2=54=u;T3RQbvfrd- zc$!-;=P6oPrj~)eC!Y5{(v$dvUe=jOj9P|B;-F=VUZHZT#6CAHPUxzvc-X5I1LBXA z6gAjP*NAS62HM4K7$-x7@k?6A_OD%A>=#2`{{t*N=Imgebnl}>;@RL%;qj>%7CzeX zD`qFNkx$NZMk9*w8nYaynbr6U^J>kn;BRa%()Lo#n<30bbRo(77S=P@qmOwf2AJD8 ze=#1y-hOj2cHtd!DIUQ4=6d9C!OTLklEoW1VLpVzI8`vLm0_(M)%HhbOhP!&cIHXN38N7?RV48zVimiMG&B z=p_A#mWWkyn_nIKf!Hh5(b7-`Kaa~1e`V+6Vq;i&{}U0{eSvoD3$&sC*W}Gbb+Z?` zzyWLK8Y*V{CVR%dRqiTkD6OC_Tthwlrje#z{Fa`?cX$H1-xM{5bH#~CE4ruYmA!Y& zKek+MzOwQD4-S1h7q2U!hJpnA%&-%rn4c!UI20ukF+UriN|?dC;3z?^sh>?T*;}7n zP+@XWyAATQU-H{khirqBFv#Q4vt#}}9HnrxB4)vWCX+!RAS>s>IU*u{p0^N+nVAU)!)s>62UM9J= VwI=5+xMGNSbFQ%M5)IC`{|jxi$Pxem diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs index 0a19330cd4..a915df25ae 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs @@ -4,7 +4,7 @@ using Volo.Abp.Uow; namespace Volo.Abp.AspNetCore.Mvc.Uow { - class AbpUnitOfWorkMiddleware + public class AbpUnitOfWorkMiddleware { private readonly RequestDelegate _next; diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs index e03e069535..338b51750f 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs @@ -8,6 +8,21 @@ namespace Volo.Abp.Uow IUnitOfWork Current { get; } [NotNull] - IBasicUnitOfWork Begin(); + IBasicUnitOfWork Begin([NotNull] UnitOfWorkStartOptions options); + } + + public static class UnitOfWorkManagerExtensions + { + [NotNull] + public static IBasicUnitOfWork Begin(this IUnitOfWorkManager unitOfWorkManager) + { + return unitOfWorkManager.Begin(new UnitOfWorkStartOptions()); + } + + [NotNull] + public static IBasicUnitOfWork BeginNew(this IUnitOfWorkManager unitOfWorkManager) + { + return unitOfWorkManager.Begin(new UnitOfWorkStartOptions {RequiresNew = true}); + } } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs index 46c1ec9c1d..73c1a0ca8f 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs @@ -17,9 +17,11 @@ namespace Volo.Abp.Uow _ambientUnitOfWork = ambientUnitOfWork; } - public IBasicUnitOfWork Begin() + public IBasicUnitOfWork Begin(UnitOfWorkStartOptions options) { - if (_ambientUnitOfWork.UnitOfWork != null) + Check.NotNull(options, nameof(options)); + + if (!options.RequiresNew && _ambientUnitOfWork.UnitOfWork != null) { return new ChildUnitOfWork(_ambientUnitOfWork.UnitOfWork); } diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs new file mode 100644 index 0000000000..daf0742861 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs @@ -0,0 +1,7 @@ +namespace Volo.Abp.Uow +{ + public class UnitOfWorkStartOptions + { + public bool RequiresNew { get; set; } + } +} \ No newline at end of file diff --git a/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs b/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs index 160c390186..c0452c67e8 100644 --- a/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs +++ b/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs @@ -34,6 +34,9 @@ namespace Volo.Abp.Uow await uow2.CompleteAsync(); } + _unitOfWorkManager.Current.ShouldNotBeNull(); + _unitOfWorkManager.Current.ShouldBe(uow1); + await uow1.CompleteAsync(); } diff --git a/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Nested_Tests.cs b/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Nested_Tests.cs new file mode 100644 index 0000000000..5c4b568b4a --- /dev/null +++ b/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Nested_Tests.cs @@ -0,0 +1,46 @@ +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Modularity; +using Volo.Abp.TestBase; +using Xunit; + +namespace Volo.Abp.Uow +{ + public class UnitOfWork_Nested_Tests : AbpIntegratedTest + { + private readonly IUnitOfWorkManager _unitOfWorkManager; + + public UnitOfWork_Nested_Tests() + { + _unitOfWorkManager = ServiceProvider.GetRequiredService(); + } + + [Fact] + public async Task Should_Create_Nested_UnitOfWorks() + { + _unitOfWorkManager.Current.ShouldBeNull(); + + using (var uow1 = _unitOfWorkManager.Begin()) + { + _unitOfWorkManager.Current.ShouldNotBeNull(); + _unitOfWorkManager.Current.ShouldBe(uow1); + + using (var uow2 = _unitOfWorkManager.BeginNew()) + { + _unitOfWorkManager.Current.ShouldNotBeNull(); + _unitOfWorkManager.Current.Id.ShouldNotBe(uow1.Id); + + await uow2.CompleteAsync(); + } + + _unitOfWorkManager.Current.ShouldNotBeNull(); + _unitOfWorkManager.Current.ShouldBe(uow1); + + await uow1.CompleteAsync(); + } + + _unitOfWorkManager.Current.ShouldBeNull(); + } + } +} From 449dd678e0cc381302c08b630ac4af1fec72d88a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 22 Sep 2017 11:35:42 +0300 Subject: [PATCH 03/21] Added most simple UOW middleware for asp.net core mvc. --- ...pNetCoreMvcApplicationBuilderExtensions.cs | 12 ++++++++++++ .../Volo/Abp/AspNetCore/Mvc/AbpController.cs | 19 ++++++++++++++++++- src/Volo.Abp/Volo/Abp/AbpServiceBase.cs | 4 ++-- .../App/AbpAspNetCoreMvcTestModule.cs | 2 ++ .../App/UnitOfWorkTestController.cs | 19 +++++++++++++++++++ .../Mvc/Uow/UnitOfWorkMiddleware_Tests.cs | 14 ++++++++++++++ 6 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Builder/AbpAspNetCoreMvcApplicationBuilderExtensions.cs create mode 100644 test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/UnitOfWorkTestController.cs create mode 100644 test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs diff --git a/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Builder/AbpAspNetCoreMvcApplicationBuilderExtensions.cs b/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Builder/AbpAspNetCoreMvcApplicationBuilderExtensions.cs new file mode 100644 index 0000000000..34c669da52 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Microsoft/AspNetCore/Builder/AbpAspNetCoreMvcApplicationBuilderExtensions.cs @@ -0,0 +1,12 @@ +using Volo.Abp.AspNetCore.Mvc.Uow; + +namespace Microsoft.AspNetCore.Builder +{ + public static class AbpAspNetCoreMvcApplicationBuilderExtensions + { + public static IApplicationBuilder UseUnitOfWork(this IApplicationBuilder app) + { + return app.UseMiddleware(); + } + } +} diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs index d3d49950cf..47901a01c3 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpController.cs @@ -1,10 +1,27 @@ -using Microsoft.AspNetCore.Mvc; +using System; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Volo.Abp.DependencyInjection; +using Volo.Abp.Guids; +using Volo.Abp.ObjectMapping; +using Volo.Abp.Uow; namespace Volo.Abp.AspNetCore.Mvc { public abstract class AbpController : Controller, ITransientDependency { + public IUnitOfWorkManager UnitOfWorkManager { get; set; } + public IObjectMapper ObjectMapper { get; set; } + + public IGuidGenerator GuidGenerator { get; set; } + + public ILoggerFactory LoggerFactory { get; set; } + + protected IUnitOfWork CurrentUnitOfWork => UnitOfWorkManager?.Current; + + protected ILogger Logger => _lazyLogger.Value; + private Lazy _lazyLogger => new Lazy(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true); } } diff --git a/src/Volo.Abp/Volo/Abp/AbpServiceBase.cs b/src/Volo.Abp/Volo/Abp/AbpServiceBase.cs index 097e60aea4..7911c7a366 100644 --- a/src/Volo.Abp/Volo/Abp/AbpServiceBase.cs +++ b/src/Volo.Abp/Volo/Abp/AbpServiceBase.cs @@ -15,10 +15,10 @@ namespace Volo.Abp public IGuidGenerator GuidGenerator { get; set; } - protected IUnitOfWork CurrentUnitOfWork => UnitOfWorkManager?.Current; - public ILoggerFactory LoggerFactory { get; set; } + protected IUnitOfWork CurrentUnitOfWork => UnitOfWorkManager?.Current; + protected ILogger Logger => _lazyLogger.Value; private Lazy _lazyLogger => new Lazy(() => LoggerFactory?.CreateLogger(GetType().FullName) ?? NullLogger.Instance, true); diff --git a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AbpAspNetCoreMvcTestModule.cs b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AbpAspNetCoreMvcTestModule.cs index bec293205e..e95ed9d90d 100644 --- a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AbpAspNetCoreMvcTestModule.cs +++ b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/AbpAspNetCoreMvcTestModule.cs @@ -34,6 +34,8 @@ namespace Volo.Abp.AspNetCore.App public override void OnApplicationInitialization(ApplicationInitializationContext context) { var app = context.GetApplicationBuilder(); + + app.UseUnitOfWork(); app.UseMvcWithDefaultRoute(); } } diff --git a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/UnitOfWorkTestController.cs b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/UnitOfWorkTestController.cs new file mode 100644 index 0000000000..d83eccd2d8 --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/UnitOfWorkTestController.cs @@ -0,0 +1,19 @@ +using Microsoft.AspNetCore.Mvc; +using Shouldly; +using Volo.Abp.AspNetCore.Mvc; + +namespace Volo.Abp.AspNetCore.App +{ + [Route("api/unitofwork-test")] + public class UnitOfWorkTestController : AbpController + { + [HttpGet] + [Route("ActionRequiresUow")] + public ActionResult ActionRequiresUow() + { + CurrentUnitOfWork.ShouldNotBeNull(); + + return Content("OK"); + } + } +} diff --git a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs new file mode 100644 index 0000000000..b97c1300ba --- /dev/null +++ b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs @@ -0,0 +1,14 @@ +using System.Threading.Tasks; +using Xunit; + +namespace Volo.Abp.AspNetCore.Mvc.Uow +{ + public class UnitOfWorkMiddleware_Tests : AspNetCoreMvcTestBase + { + [Fact] + public async Task ActionRequiresUow() + { + await GetResponseAsStringAsync("/api/unitofwork-test/ActionRequiresUow"); + } + } +} From f4394f9993f16e9108a794d2cf7553e0105a6877 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 22 Sep 2017 16:29:48 +0300 Subject: [PATCH 04/21] Created Unit Of Work reservation system. --- .../AspNetCore/Mvc/AbpMvcOptionsExtensions.cs | 3 +- .../Mvc/Uow/AbpUnitOfWorkMiddleware.cs | 2 +- .../AspNetCore/Mvc/Uow/AbpUowActionFilter.cs | 25 +++++++++ src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs | 22 ++++++++ src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs | 12 ++++- .../Volo/Abp/Uow/IUnitOfWorkManager.cs | 14 +---- src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs | 11 ++++ .../Volo/Abp/Uow/UnitOfWorkExtensions.cs | 14 +++++ .../Volo/Abp/Uow/UnitOfWorkManager.cs | 52 +++++++++++++++++-- .../Abp/Uow/UnitOfWorkManagerExtensions.cs | 32 ++++++++++++ .../Volo/Abp/Uow/UnitOfWorkStartOptions.cs | 2 + .../Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs | 31 +++++++++++ 12 files changed, 199 insertions(+), 21 deletions(-) create mode 100644 src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs create mode 100644 src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkExtensions.cs create mode 100644 src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs index 53ae0da753..93517776b6 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpMvcOptionsExtensions.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc.ExceptionHandling; +using Volo.Abp.AspNetCore.Mvc.Uow; using Volo.Abp.AspNetCore.Mvc.Validation; namespace Volo.Abp.AspNetCore.Mvc @@ -24,7 +25,7 @@ namespace Volo.Abp.AspNetCore.Mvc //options.Filters.AddService(typeof(AbpAuthorizationFilter)); //options.Filters.AddService(typeof(AbpAuditActionFilter)); options.Filters.AddService(typeof(AbpValidationActionFilter)); - //options.Filters.AddService(typeof(AbpUowActionFilter)); + options.Filters.AddService(typeof(AbpUowActionFilter)); options.Filters.AddService(typeof(AbpExceptionFilter)); //options.Filters.AddService(typeof(AbpResultFilter)); } diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs index a915df25ae..d2aa6f9e53 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs @@ -15,7 +15,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow public async Task Invoke(HttpContext httpContext, IUnitOfWorkManager unitOfWorkManager) { - using (var uow = unitOfWorkManager.Begin()) + using (var uow = unitOfWorkManager.Reserve(AbpUowActionFilter.UnitOfWorkReservationName)) { await _next(httpContext); await uow.CompleteAsync(); diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs new file mode 100644 index 0000000000..dcf7d3caa8 --- /dev/null +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs @@ -0,0 +1,25 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Filters; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Uow; + +namespace Volo.Abp.AspNetCore.Mvc.Uow +{ + public class AbpUowActionFilter : IAsyncActionFilter, ITransientDependency + { + public const string UnitOfWorkReservationName = "_AbpActionUnitOfWork"; + + private readonly IUnitOfWorkManager _unitOfWorkManager; + + public AbpUowActionFilter(IUnitOfWorkManager unitOfWorkManager) + { + _unitOfWorkManager = unitOfWorkManager; + } + + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + _unitOfWorkManager.BeginReserved(UnitOfWorkReservationName); + await next(); + } + } +} diff --git a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs index c20d106e4e..db4ae98b9c 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs @@ -9,6 +9,28 @@ namespace Volo.Abp.Uow { public Guid Id => _parent.Id; + public IUnitOfWork Outer + { + get => _parent.Outer; + } + + public bool IsReserved + { + get => _parent.IsReserved; + set => _parent.IsReserved = value; + } + + public string ReservationName + { + get => _parent.ReservationName; + set => _parent.ReservationName = value; + } + + public void SetOuter(IUnitOfWork outer) + { + _parent.SetOuter(outer); + } + public event EventHandler Completed; public event EventHandler Failed; public event EventHandler Disposed; diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs index 01d6baeb2f..52451ae55d 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs @@ -1,7 +1,15 @@ -namespace Volo.Abp.Uow +using JetBrains.Annotations; + +namespace Volo.Abp.Uow { public interface IUnitOfWork : IBasicUnitOfWork, IDatabaseApiContainer { - + IUnitOfWork Outer { get; } + + bool IsReserved { get; set; } + + string ReservationName { get; set; } + + void SetOuter([CanBeNull] IUnitOfWork outer); } } diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs index 338b51750f..6d26cccb4e 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs @@ -9,20 +9,8 @@ namespace Volo.Abp.Uow [NotNull] IBasicUnitOfWork Begin([NotNull] UnitOfWorkStartOptions options); - } - - public static class UnitOfWorkManagerExtensions - { - [NotNull] - public static IBasicUnitOfWork Begin(this IUnitOfWorkManager unitOfWorkManager) - { - return unitOfWorkManager.Begin(new UnitOfWorkStartOptions()); - } [NotNull] - public static IBasicUnitOfWork BeginNew(this IUnitOfWorkManager unitOfWorkManager) - { - return unitOfWorkManager.Begin(new UnitOfWorkStartOptions {RequiresNew = true}); - } + IBasicUnitOfWork BeginReserved([NotNull] string reservationName); } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs index 2207f94fcc..961129528e 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs @@ -10,6 +10,17 @@ namespace Volo.Abp.Uow { public Guid Id { get; } = Guid.NewGuid(); + public IUnitOfWork Outer { get; private set; } + + public bool IsReserved { get; set; } + + public string ReservationName { get; set; } + + public void SetOuter(IUnitOfWork outer) + { + Outer = outer; + } + public event EventHandler Completed; public event EventHandler Failed; public event EventHandler Disposed; diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkExtensions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkExtensions.cs new file mode 100644 index 0000000000..5c01f54a99 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkExtensions.cs @@ -0,0 +1,14 @@ +using JetBrains.Annotations; + +namespace Volo.Abp.Uow +{ + public static class UnitOfWorkExtensions + { + public static bool IsReservedFor([NotNull] this IUnitOfWork unitOfWork, string reservationName) + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + return unitOfWork.IsReserved && unitOfWork.ReservationName == reservationName; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs index 73c1a0ca8f..a5a0d18880 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs @@ -6,7 +6,7 @@ namespace Volo.Abp.Uow { public class UnitOfWorkManager : IUnitOfWorkManager, ISingletonDependency { - public IUnitOfWork Current => _ambientUnitOfWork.UnitOfWork; + public IUnitOfWork Current => GetCurrentUnitOfWork(); private readonly IServiceProvider _serviceProvider; private readonly IAmbientUnitOfWork _ambientUnitOfWork; @@ -21,18 +21,28 @@ namespace Volo.Abp.Uow { Check.NotNull(options, nameof(options)); - if (!options.RequiresNew && _ambientUnitOfWork.UnitOfWork != null) + if (!options.RequiresNew && _ambientUnitOfWork.UnitOfWork != null && !_ambientUnitOfWork.UnitOfWork.IsReserved) { return new ChildUnitOfWork(_ambientUnitOfWork.UnitOfWork); } - var parentUow = _ambientUnitOfWork.UnitOfWork; + if (_ambientUnitOfWork.UnitOfWork != null) + { + //Requires new because there is already a current UOW but it's reserved + options.RequiresNew = true; + } + + var outerUow = _ambientUnitOfWork.UnitOfWork; var scope = _serviceProvider.CreateScope(); IUnitOfWork unitOfWork; try { unitOfWork = scope.ServiceProvider.GetRequiredService(); + + unitOfWork.SetOuter(outerUow); + unitOfWork.IsReserved = options.ReservationName != null; + unitOfWork.ReservationName = options.ReservationName; } catch { @@ -44,11 +54,45 @@ namespace Volo.Abp.Uow unitOfWork.Disposed += (sender, args) => { - _ambientUnitOfWork.SetUnitOfWork(parentUow); + _ambientUnitOfWork.SetUnitOfWork(outerUow); scope.Dispose(); }; return unitOfWork; } + + public IBasicUnitOfWork BeginReserved(string reservationName) + { + Check.NotNull(reservationName, nameof(reservationName)); + + var uow = _ambientUnitOfWork.UnitOfWork; + + //Find reserved unit of work starting from current and going to outers + while (uow != null && !uow.IsReservedFor(reservationName)) + { + uow = uow.Outer; + } + + if (uow == null) + { + throw new AbpException($"Could not find a reserved unit of work with reservation name: {reservationName}"); + } + + uow.IsReserved = false; + return uow; + } + + private IUnitOfWork GetCurrentUnitOfWork() + { + var uow = _ambientUnitOfWork.UnitOfWork; + + //Skip reserved unit of work + while (uow != null && uow.IsReserved) + { + uow = uow.Outer; + } + + return uow; + } } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs new file mode 100644 index 0000000000..0a6897e6ee --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs @@ -0,0 +1,32 @@ +using JetBrains.Annotations; + +namespace Volo.Abp.Uow +{ + public static class UnitOfWorkManagerExtensions + { + [NotNull] + public static IBasicUnitOfWork Begin([NotNull] this IUnitOfWorkManager unitOfWorkManager) + { + Check.NotNull(unitOfWorkManager, nameof(unitOfWorkManager)); + + return unitOfWorkManager.Begin(new UnitOfWorkStartOptions()); + } + + [NotNull] + public static IBasicUnitOfWork BeginNew([NotNull] this IUnitOfWorkManager unitOfWorkManager) + { + Check.NotNull(unitOfWorkManager, nameof(unitOfWorkManager)); + + return unitOfWorkManager.Begin(new UnitOfWorkStartOptions {RequiresNew = true}); + } + + [NotNull] + public static IBasicUnitOfWork Reserve([NotNull] this IUnitOfWorkManager unitOfWorkManager, [NotNull] string reservationName) + { + Check.NotNull(unitOfWorkManager, nameof(unitOfWorkManager)); + Check.NotNull(reservationName, nameof(reservationName)); + + return unitOfWorkManager.Begin(new UnitOfWorkStartOptions { ReservationName = reservationName }); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs index daf0742861..7331902190 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs @@ -3,5 +3,7 @@ public class UnitOfWorkStartOptions { public bool RequiresNew { get; set; } + + public string ReservationName { get; set; } } } \ No newline at end of file diff --git a/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs b/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs index c0452c67e8..d26080d03d 100644 --- a/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs +++ b/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs @@ -42,5 +42,36 @@ namespace Volo.Abp.Uow _unitOfWorkManager.Current.ShouldBeNull(); } + + [Fact] + public async Task UnitOfWorkManager_Reservation_Test() + { + _unitOfWorkManager.Current.ShouldBeNull(); + + using (var uow1 = _unitOfWorkManager.Reserve("Reservation1")) + { + _unitOfWorkManager.Current.ShouldBeNull(); + + using (var uow2 = _unitOfWorkManager.Begin()) + { + _unitOfWorkManager.Current.ShouldNotBeNull(); + _unitOfWorkManager.Current.Id.ShouldNotBe(uow1.Id); + + await uow2.CompleteAsync(); + } + + _unitOfWorkManager.Current.ShouldBeNull(); + + var reserverUow = _unitOfWorkManager.BeginReserved("Reservation1"); + + _unitOfWorkManager.Current.ShouldNotBeNull(); + _unitOfWorkManager.Current.Id.ShouldBe(reserverUow.Id); + _unitOfWorkManager.Current.Id.ShouldBe(uow1.Id); + + await uow1.CompleteAsync(); + } + + _unitOfWorkManager.Current.ShouldBeNull(); + } } } From af0ccfc89752a68cb87bb4c637e0db0fec7da216 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 22 Sep 2017 17:07:49 +0300 Subject: [PATCH 05/21] UnitOfWorkManager refactored. --- .../Volo/Abp/Uow/UnitOfWorkManager.cs | 63 ++++++++++--------- 1 file changed, 34 insertions(+), 29 deletions(-) diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs index a5a0d18880..4d28668fa9 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs @@ -32,33 +32,7 @@ namespace Volo.Abp.Uow options.RequiresNew = true; } - var outerUow = _ambientUnitOfWork.UnitOfWork; - - var scope = _serviceProvider.CreateScope(); - IUnitOfWork unitOfWork; - try - { - unitOfWork = scope.ServiceProvider.GetRequiredService(); - - unitOfWork.SetOuter(outerUow); - unitOfWork.IsReserved = options.ReservationName != null; - unitOfWork.ReservationName = options.ReservationName; - } - catch - { - scope.Dispose(); - throw; - } - - _ambientUnitOfWork.SetUnitOfWork(unitOfWork); - - unitOfWork.Disposed += (sender, args) => - { - _ambientUnitOfWork.SetUnitOfWork(outerUow); - scope.Dispose(); - }; - - return unitOfWork; + return CreateUnitOfWork(options); } public IBasicUnitOfWork BeginReserved(string reservationName) @@ -66,7 +40,7 @@ namespace Volo.Abp.Uow Check.NotNull(reservationName, nameof(reservationName)); var uow = _ambientUnitOfWork.UnitOfWork; - + //Find reserved unit of work starting from current and going to outers while (uow != null && !uow.IsReservedFor(reservationName)) { @@ -85,7 +59,7 @@ namespace Volo.Abp.Uow private IUnitOfWork GetCurrentUnitOfWork() { var uow = _ambientUnitOfWork.UnitOfWork; - + //Skip reserved unit of work while (uow != null && uow.IsReserved) { @@ -94,5 +68,36 @@ namespace Volo.Abp.Uow return uow; } + + private IUnitOfWork CreateUnitOfWork(UnitOfWorkStartOptions options) + { + var scope = _serviceProvider.CreateScope(); + + try + { + var outerUow = _ambientUnitOfWork.UnitOfWork; + + var unitOfWork = scope.ServiceProvider.GetRequiredService(); + + unitOfWork.SetOuter(outerUow); + unitOfWork.IsReserved = options.ReservationName != null; + unitOfWork.ReservationName = options.ReservationName; + + _ambientUnitOfWork.SetUnitOfWork(unitOfWork); + + unitOfWork.Disposed += (sender, args) => + { + _ambientUnitOfWork.SetUnitOfWork(outerUow); + scope.Dispose(); + }; + + return unitOfWork; + } + catch + { + scope.Dispose(); + throw; + } + } } } \ No newline at end of file From 3839edbab970075351aced26e085c92dbd00f0c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Fri, 22 Sep 2017 17:18:11 +0300 Subject: [PATCH 06/21] Rename id to key on unitOfWork.GetOrAddDatabaseApi method. --- src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs | 8 ++++---- src/Volo.Abp/Volo/Abp/Uow/IDatabaseApiContainer.cs | 4 ++-- src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs | 10 +++++----- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs index db4ae98b9c..508017f39c 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs @@ -70,14 +70,14 @@ namespace Volo.Abp.Uow return Task.CompletedTask; } - public IDatabaseApi FindDatabaseApi(string id) + public IDatabaseApi FindDatabaseApi(string key) { - return _parent.FindDatabaseApi(id); + return _parent.FindDatabaseApi(key); } - public IDatabaseApi GetOrAddDatabaseApi(string id, Func factory) + public IDatabaseApi GetOrAddDatabaseApi(string key, Func factory) { - return _parent.GetOrAddDatabaseApi(id, factory); + return _parent.GetOrAddDatabaseApi(key, factory); } public void Dispose() diff --git a/src/Volo.Abp/Volo/Abp/Uow/IDatabaseApiContainer.cs b/src/Volo.Abp/Volo/Abp/Uow/IDatabaseApiContainer.cs index c24fe41797..d896574a9a 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IDatabaseApiContainer.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IDatabaseApiContainer.cs @@ -7,9 +7,9 @@ namespace Volo.Abp.Uow public interface IDatabaseApiContainer : IServiceProviderAccessor { [CanBeNull] - IDatabaseApi FindDatabaseApi([NotNull] string id); + IDatabaseApi FindDatabaseApi([NotNull] string key); [NotNull] - IDatabaseApi GetOrAddDatabaseApi([NotNull] string id, [NotNull] Func factory); + IDatabaseApi GetOrAddDatabaseApi([NotNull] string key, [NotNull] Func factory); } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs index 961129528e..5ed1014293 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs @@ -90,17 +90,17 @@ namespace Volo.Abp.Uow } } - public IDatabaseApi FindDatabaseApi(string id) + public IDatabaseApi FindDatabaseApi(string key) { - return _databaseApis.GetOrDefault(id); + return _databaseApis.GetOrDefault(key); } - public IDatabaseApi GetOrAddDatabaseApi(string id, Func factory) + public IDatabaseApi GetOrAddDatabaseApi(string key, Func factory) { - Check.NotNull(id, nameof(id)); + Check.NotNull(key, nameof(key)); Check.NotNull(factory, nameof(factory)); - return _databaseApis.GetOrAdd(id, factory); + return _databaseApis.GetOrAdd(key, factory); } protected virtual void OnCompleted() From 11170732018491f97fbfe0e23ed8f4202679ed7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 25 Sep 2017 13:32:08 +0300 Subject: [PATCH 07/21] #23: UOW Action filter should be able to work without middleware too. --- .../Mvc/Uow/AbpUnitOfWorkMiddleware.cs | 2 +- .../AspNetCore/Mvc/Uow/AbpUowActionFilter.cs | 24 +++++++++++++++++-- .../Volo/Abp/Uow/IUnitOfWorkManager.cs | 5 ++-- .../Volo/Abp/Uow/UnitOfWorkManager.cs | 14 ++++++++--- .../Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs | 3 +-- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs index d2aa6f9e53..dffa833f2b 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUnitOfWorkMiddleware.cs @@ -18,7 +18,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow using (var uow = unitOfWorkManager.Reserve(AbpUowActionFilter.UnitOfWorkReservationName)) { await _next(httpContext); - await uow.CompleteAsync(); + await uow.CompleteAsync(httpContext.RequestAborted); } } } diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs index dcf7d3caa8..84d85ba08a 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs @@ -18,8 +18,28 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) { - _unitOfWorkManager.BeginReserved(UnitOfWorkReservationName); - await next(); + if (!context.ActionDescriptor.IsControllerAction()) + { + await next(); + return; + } + + if (_unitOfWorkManager.TryBeginReserved(UnitOfWorkReservationName)) + { + await next(); + return; + } + + //TODO: Check if disabled. Get and apply attributes to control UOW. + + using (var uow = _unitOfWorkManager.Begin()) + { + var result = await next(); + if (result.Exception == null || result.ExceptionHandled) + { + await uow.CompleteAsync(context.HttpContext.RequestAborted); + } + } } } } diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs index 6d26cccb4e..1cd71e557e 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs @@ -10,7 +10,8 @@ namespace Volo.Abp.Uow [NotNull] IBasicUnitOfWork Begin([NotNull] UnitOfWorkStartOptions options); - [NotNull] - IBasicUnitOfWork BeginReserved([NotNull] string reservationName); + void BeginReserved([NotNull] string reservationName); + + bool TryBeginReserved([NotNull] string reservationName); } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs index 4d28668fa9..9d0e1d153a 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs @@ -35,7 +35,15 @@ namespace Volo.Abp.Uow return CreateUnitOfWork(options); } - public IBasicUnitOfWork BeginReserved(string reservationName) + public void BeginReserved(string reservationName) + { + if (!TryBeginReserved(reservationName)) + { + throw new AbpException($"Could not find a reserved unit of work with reservation name: {reservationName}"); + } + } + + public bool TryBeginReserved(string reservationName) { Check.NotNull(reservationName, nameof(reservationName)); @@ -49,11 +57,11 @@ namespace Volo.Abp.Uow if (uow == null) { - throw new AbpException($"Could not find a reserved unit of work with reservation name: {reservationName}"); + return false; } uow.IsReserved = false; - return uow; + return true; } private IUnitOfWork GetCurrentUnitOfWork() diff --git a/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs b/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs index d26080d03d..3aa6e15b81 100644 --- a/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs +++ b/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Ambient_Scope_Tests.cs @@ -62,10 +62,9 @@ namespace Volo.Abp.Uow _unitOfWorkManager.Current.ShouldBeNull(); - var reserverUow = _unitOfWorkManager.BeginReserved("Reservation1"); + _unitOfWorkManager.BeginReserved("Reservation1"); _unitOfWorkManager.Current.ShouldNotBeNull(); - _unitOfWorkManager.Current.Id.ShouldBe(reserverUow.Id); _unitOfWorkManager.Current.Id.ShouldBe(uow1.Id); await uow1.CompleteAsync(); From f12c0ccf6c63b7eac3b53fc0c1d5c48717ef254a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 25 Sep 2017 15:42:54 +0300 Subject: [PATCH 08/21] Improvements on UOW. --- .../AspNetCore/Mvc/Uow/AbpUowActionFilter.cs | 16 ++++-- .../DbContextOptionsFactory.cs | 25 +++++++--- .../DbContextOptionsFactoryContext.cs | 27 ++++++++++ .../UnitOfWorkDbContextProvider.cs | 22 +++++--- src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs | 12 +++-- src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs | 4 ++ .../Volo/Abp/Uow/IUnitOfWorkManager.cs | 4 +- src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs | 7 +++ .../Volo/Abp/Uow/UnitOfWorkAttribute.cs | 43 ++++++++++++++++ src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkHelper.cs | 18 +++++++ .../Volo/Abp/Uow/UnitOfWorkManager.cs | 8 +-- .../Abp/Uow/UnitOfWorkManagerExtensions.cs | 16 ++++++ .../Volo/Abp/Uow/UnitOfWorkStartOptions.cs | 50 ++++++++++++++++++- 13 files changed, 225 insertions(+), 27 deletions(-) create mode 100644 src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactoryContext.cs diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs index 84d85ba08a..5996724a20 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs @@ -24,15 +24,25 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow return; } - if (_unitOfWorkManager.TryBeginReserved(UnitOfWorkReservationName)) + var unitOfWorkAttr = UnitOfWorkHelper.GetUnitOfWorkAttributeOrNull(context.ActionDescriptor.GetMethodInfo()); + + if (unitOfWorkAttr?.IsDisabled == true) { await next(); return; } - //TODO: Check if disabled. Get and apply attributes to control UOW. + var options = new UnitOfWorkStartOptions(); + + unitOfWorkAttr?.SetOptions(options); + + if (_unitOfWorkManager.TryBeginReserved(UnitOfWorkReservationName, options)) + { + await next(); + return; + } - using (var uow = _unitOfWorkManager.Begin()) + using (var uow = _unitOfWorkManager.Begin(options)) { var result = await next(); if (result.Exception == null || result.ExceptionHandled) diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs index 29a9572977..187c56bc21 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs @@ -12,13 +12,13 @@ namespace Volo.Abp.EntityFrameworkCore.DependencyInjection public static DbContextOptions Create(IServiceProvider serviceProvider) where TDbContext : AbpDbContext { - var connectionStringName = ConnectionStringNameAttribute.GetConnStringName(); - using (var scope = serviceProvider.CreateScope()) { + var creationContext = GetCreationContext(scope.ServiceProvider); + var context = new AbpDbContextConfigurationContext( - GetConnectionString(scope, connectionStringName), - connectionStringName, + creationContext.ConnectionString, + creationContext.ConnectionStringName, scope.ServiceProvider ); @@ -47,9 +47,22 @@ namespace Volo.Abp.EntityFrameworkCore.DependencyInjection return scope.ServiceProvider.GetRequiredService>().Value; } - private static string GetConnectionString(IServiceScope scope, string connectionStringName) + private static DbContextOptionsFactoryContext GetCreationContext(IServiceProvider serviceProvider) + where TDbContext : AbpDbContext { - return scope.ServiceProvider.GetRequiredService().Resolve(connectionStringName); + var context = DbContextOptionsFactoryContext.Current; + if (context != null) + { + return context; + } + + var connectionStringName = ConnectionStringNameAttribute.GetConnStringName(); + var connectionString = serviceProvider.GetRequiredService().Resolve(connectionStringName); + + return new DbContextOptionsFactoryContext( + connectionStringName, + connectionString + ); } } } \ No newline at end of file diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactoryContext.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactoryContext.cs new file mode 100644 index 0000000000..936d685bc1 --- /dev/null +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactoryContext.cs @@ -0,0 +1,27 @@ +using System; +using System.Threading; + +namespace Volo.Abp.EntityFrameworkCore.DependencyInjection +{ + internal class DbContextOptionsFactoryContext + { + public static DbContextOptionsFactoryContext Current => _current.Value; + private static readonly AsyncLocal _current = new AsyncLocal(); + + public string ConnectionStringName { get; } + + public string ConnectionString { get; } + + public DbContextOptionsFactoryContext(string connectionStringName, string connectionString) + { + ConnectionStringName = connectionStringName; + ConnectionString = connectionString; + } + + public static IDisposable Use(DbContextOptionsFactoryContext context) + { + _current.Value = context; + return new DisposeAction(() => _current.Value = null); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs index 2aae39d871..bfa43617b5 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.DependencyInjection; namespace Volo.Abp.Uow.EntityFrameworkCore { @@ -26,16 +27,21 @@ namespace Volo.Abp.Uow.EntityFrameworkCore throw new AbpException("A DbContext can only be created inside a unit of work!"); } - var connectionString = _connectionStringResolver.Resolve(); + var connectionStringName = ConnectionStringNameAttribute.GetConnStringName(); + var connectionString = _connectionStringResolver.Resolve(connectionStringName); + var dbContextKey = $"{typeof(TDbContext).FullName}_{connectionString}"; - var databaseApi = unitOfWork.GetOrAddDatabaseApi( - dbContextKey, - () => new DbContextDatabaseApi( - unitOfWork.ServiceProvider.GetRequiredService() - )); - - return ((DbContextDatabaseApi)databaseApi).DbContext; + using (DbContextOptionsFactoryContext.Use(new DbContextOptionsFactoryContext(connectionStringName, connectionString))) + { + var databaseApi = unitOfWork.GetOrAddDatabaseApi( + dbContextKey, + () => new DbContextDatabaseApi( + unitOfWork.ServiceProvider.GetRequiredService() + )); + + return ((DbContextDatabaseApi)databaseApi).DbContext; + } } } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs index 508017f39c..15e03332f6 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs @@ -9,10 +9,9 @@ namespace Volo.Abp.Uow { public Guid Id => _parent.Id; - public IUnitOfWork Outer - { - get => _parent.Outer; - } + public UnitOfWorkStartOptions Options => _parent.Options; + + public IUnitOfWork Outer => _parent.Outer; public bool IsReserved { @@ -31,6 +30,11 @@ namespace Volo.Abp.Uow _parent.SetOuter(outer); } + public void SetOptions(UnitOfWorkStartOptions options) + { + _parent.SetOptions(options); + } + public event EventHandler Completed; public event EventHandler Failed; public event EventHandler Disposed; diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs index 52451ae55d..442c03c075 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs @@ -4,6 +4,8 @@ namespace Volo.Abp.Uow { public interface IUnitOfWork : IBasicUnitOfWork, IDatabaseApiContainer { + UnitOfWorkStartOptions Options { get; } + IUnitOfWork Outer { get; } bool IsReserved { get; set; } @@ -11,5 +13,7 @@ namespace Volo.Abp.Uow string ReservationName { get; set; } void SetOuter([CanBeNull] IUnitOfWork outer); + + void SetOptions(UnitOfWorkStartOptions options); } } diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs index 1cd71e557e..493a5b1c78 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs @@ -10,8 +10,8 @@ namespace Volo.Abp.Uow [NotNull] IBasicUnitOfWork Begin([NotNull] UnitOfWorkStartOptions options); - void BeginReserved([NotNull] string reservationName); + void BeginReserved([NotNull] string reservationName, [NotNull] UnitOfWorkStartOptions options); - bool TryBeginReserved([NotNull] string reservationName); + bool TryBeginReserved([NotNull] string reservationName, [NotNull] UnitOfWorkStartOptions options); } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs index 5ed1014293..2409fbd4ba 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs @@ -10,6 +10,8 @@ namespace Volo.Abp.Uow { public Guid Id { get; } = Guid.NewGuid(); + public UnitOfWorkStartOptions Options { get; private set; } + public IUnitOfWork Outer { get; private set; } public bool IsReserved { get; set; } @@ -21,6 +23,11 @@ namespace Volo.Abp.Uow Outer = outer; } + public void SetOptions(UnitOfWorkStartOptions options) + { + Options = options; + } + public event EventHandler Completed; public event EventHandler Failed; public event EventHandler Disposed; diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkAttribute.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkAttribute.cs index 1425d7c263..3b3f68bbba 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkAttribute.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkAttribute.cs @@ -1,4 +1,5 @@ using System; +using System.Data; namespace Volo.Abp.Uow { @@ -11,5 +12,47 @@ namespace Volo.Abp.Uow [AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Interface)] public class UnitOfWorkAttribute : Attribute { + /// + /// Is this UOW transactional? + /// Uses default value if not supplied. + /// + public bool? IsTransactional { get; set; } + + /// + /// Timeout of UOW As milliseconds. + /// Uses default value if not supplied. + /// + public TimeSpan? Timeout { get; set; } + + /// + /// If this UOW is transactional, this option indicated the isolation level of the transaction. + /// Uses default value if not supplied. + /// + public IsolationLevel? IsolationLevel { get; set; } + + /// + /// Used to prevent starting a unit of work for the method. + /// If there is already a started unit of work, this property is ignored. + /// Default: false. + /// + public bool IsDisabled { get; set; } + + public virtual void SetOptions(UnitOfWorkStartOptions options) + { + if (IsTransactional.HasValue) + { + options.IsTransactional = IsTransactional; + } + + if (Timeout.HasValue) + { + options.Timeout = Timeout; + } + + if (IsolationLevel.HasValue) + { + options.IsolationLevel = IsolationLevel; + } + } } } diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkHelper.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkHelper.cs index fe8d91faef..4f1a50f429 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkHelper.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkHelper.cs @@ -54,6 +54,24 @@ namespace Volo.Abp.Uow return false; } + public static UnitOfWorkAttribute GetUnitOfWorkAttributeOrNull(MethodInfo methodInfo) + { + var attrs = methodInfo.GetCustomAttributes(true).OfType().ToArray(); + if (attrs.Length > 0) + { + return attrs[0]; + } + + attrs = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType().ToArray(); + if (attrs.Length > 0) + { + return attrs[0]; + } + + return null; + } + + private static bool AnyMethodHasUnitOfWorkAttribute(TypeInfo implementationType) { return implementationType diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs index 9d0e1d153a..334c07a369 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs @@ -35,15 +35,15 @@ namespace Volo.Abp.Uow return CreateUnitOfWork(options); } - public void BeginReserved(string reservationName) + public void BeginReserved(string reservationName, UnitOfWorkStartOptions options) { - if (!TryBeginReserved(reservationName)) + if (!TryBeginReserved(reservationName, options)) { throw new AbpException($"Could not find a reserved unit of work with reservation name: {reservationName}"); } } - public bool TryBeginReserved(string reservationName) + public bool TryBeginReserved(string reservationName, UnitOfWorkStartOptions options) { Check.NotNull(reservationName, nameof(reservationName)); @@ -61,6 +61,7 @@ namespace Volo.Abp.Uow } uow.IsReserved = false; + uow.SetOptions(options); return true; } @@ -90,6 +91,7 @@ namespace Volo.Abp.Uow unitOfWork.SetOuter(outerUow); unitOfWork.IsReserved = options.ReservationName != null; unitOfWork.ReservationName = options.ReservationName; + unitOfWork.SetOptions(options); _ambientUnitOfWork.SetUnitOfWork(unitOfWork); diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs index 0a6897e6ee..5be61ae8ae 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs @@ -28,5 +28,21 @@ namespace Volo.Abp.Uow return unitOfWorkManager.Begin(new UnitOfWorkStartOptions { ReservationName = reservationName }); } + + public static void BeginReserved([NotNull] this IUnitOfWorkManager unitOfWorkManager, [NotNull] string reservationName) + { + Check.NotNull(unitOfWorkManager, nameof(unitOfWorkManager)); + Check.NotNull(reservationName, nameof(reservationName)); + + unitOfWorkManager.BeginReserved(reservationName, new UnitOfWorkStartOptions()); + } + + public static void TryBeginReserved([NotNull] this IUnitOfWorkManager unitOfWorkManager, [NotNull] string reservationName) + { + Check.NotNull(unitOfWorkManager, nameof(unitOfWorkManager)); + Check.NotNull(reservationName, nameof(reservationName)); + + unitOfWorkManager.TryBeginReserved(reservationName, new UnitOfWorkStartOptions()); + } } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs index 7331902190..ba398250e9 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs @@ -1,9 +1,57 @@ -namespace Volo.Abp.Uow +using System; +using System.Data; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Uow { public class UnitOfWorkStartOptions { + //Revise this since Begin/BeginReserved accepts different options and that can make confusion! + public bool RequiresNew { get; set; } public string ReservationName { get; set; } + + public bool? IsTransactional { get; set; } + + public IsolationLevel? IsolationLevel { get; set; } + + public TimeSpan? Timeout { get; set; } + } + + /// + /// Global (default) unit of work options + /// + public class UnitOfWorkOptions + { + public IUnitOfWorkTransactionBehavior IsTransactional { get; set; } + + public IsolationLevel? IsolationLevel { get; set; } + } + + public interface IUnitOfWorkTransactionBehavior + { + UnitOfWorkStartOptions Set(UnitOfWorkTransactionBehaviorContext context); + } + + public class ConventionalUnitOfWorkTransactionBehavior : IUnitOfWorkTransactionBehavior + { + public UnitOfWorkStartOptions Set(UnitOfWorkTransactionBehaviorContext context) + { + throw new NotImplementedException(); + } + } + + public class UnitOfWorkTransactionBehaviorContext : IServiceProviderAccessor + { + public UnitOfWorkStartOptions Options { get; } + + public IServiceProvider ServiceProvider { get; } + + public UnitOfWorkTransactionBehaviorContext(UnitOfWorkStartOptions options, IServiceProvider serviceProvider) + { + Options = options; + ServiceProvider = serviceProvider; + } } } \ No newline at end of file From 816823ad542493421cbe1fc073aa229e2578522f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 25 Sep 2017 15:55:20 +0300 Subject: [PATCH 09/21] Refactor. --- .../DbContextCreationContext.cs | 28 +++++++++++++++++++ .../DbContextOptionsFactory.cs | 6 ++-- .../DbContextOptionsFactoryContext.cs | 27 ------------------ .../UnitOfWorkDbContextProvider.cs | 24 ++++++++++------ 4 files changed, 46 insertions(+), 39 deletions(-) create mode 100644 src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextCreationContext.cs delete mode 100644 src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactoryContext.cs diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextCreationContext.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextCreationContext.cs new file mode 100644 index 0000000000..b8a713c8aa --- /dev/null +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextCreationContext.cs @@ -0,0 +1,28 @@ +using System; +using System.Threading; + +namespace Volo.Abp.EntityFrameworkCore.DependencyInjection +{ + internal class DbContextCreationContext + { + public static DbContextCreationContext Current => _current.Value; + private static readonly AsyncLocal _current = new AsyncLocal(); + + public string ConnectionStringName { get; } + + public string ConnectionString { get; } + + public DbContextCreationContext(string connectionStringName, string connectionString) + { + ConnectionStringName = connectionStringName; + ConnectionString = connectionString; + } + + public static IDisposable Use(DbContextCreationContext context) + { + var previousValue = Current; + _current.Value = context; + return new DisposeAction(() => _current.Value = previousValue); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs index 187c56bc21..047c10ba6c 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs @@ -47,10 +47,10 @@ namespace Volo.Abp.EntityFrameworkCore.DependencyInjection return scope.ServiceProvider.GetRequiredService>().Value; } - private static DbContextOptionsFactoryContext GetCreationContext(IServiceProvider serviceProvider) + private static DbContextCreationContext GetCreationContext(IServiceProvider serviceProvider) where TDbContext : AbpDbContext { - var context = DbContextOptionsFactoryContext.Current; + var context = DbContextCreationContext.Current; if (context != null) { return context; @@ -59,7 +59,7 @@ namespace Volo.Abp.EntityFrameworkCore.DependencyInjection var connectionStringName = ConnectionStringNameAttribute.GetConnStringName(); var connectionString = serviceProvider.GetRequiredService().Resolve(connectionStringName); - return new DbContextOptionsFactoryContext( + return new DbContextCreationContext( connectionStringName, connectionString ); diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactoryContext.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactoryContext.cs deleted file mode 100644 index 936d685bc1..0000000000 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactoryContext.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Threading; - -namespace Volo.Abp.EntityFrameworkCore.DependencyInjection -{ - internal class DbContextOptionsFactoryContext - { - public static DbContextOptionsFactoryContext Current => _current.Value; - private static readonly AsyncLocal _current = new AsyncLocal(); - - public string ConnectionStringName { get; } - - public string ConnectionString { get; } - - public DbContextOptionsFactoryContext(string connectionStringName, string connectionString) - { - ConnectionStringName = connectionStringName; - ConnectionString = connectionString; - } - - public static IDisposable Use(DbContextOptionsFactoryContext context) - { - _current.Value = context; - return new DisposeAction(() => _current.Value = null); - } - } -} \ No newline at end of file diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs index bfa43617b5..717a68ebe7 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs @@ -29,18 +29,24 @@ namespace Volo.Abp.Uow.EntityFrameworkCore var connectionStringName = ConnectionStringNameAttribute.GetConnStringName(); var connectionString = _connectionStringResolver.Resolve(connectionStringName); - + var dbContextKey = $"{typeof(TDbContext).FullName}_{connectionString}"; - using (DbContextOptionsFactoryContext.Use(new DbContextOptionsFactoryContext(connectionStringName, connectionString))) - { - var databaseApi = unitOfWork.GetOrAddDatabaseApi( - dbContextKey, - () => new DbContextDatabaseApi( - unitOfWork.ServiceProvider.GetRequiredService() - )); + var databaseApi = unitOfWork.GetOrAddDatabaseApi( + dbContextKey, + () => new DbContextDatabaseApi( + CreateDbContext(unitOfWork, connectionStringName, connectionString) + )); + + return ((DbContextDatabaseApi)databaseApi).DbContext; + } - return ((DbContextDatabaseApi)databaseApi).DbContext; + private static TDbContext CreateDbContext(IUnitOfWork unitOfWork, string connectionStringName, string connectionString) + { + var creationContext = new DbContextCreationContext(connectionStringName, connectionString); + using (DbContextCreationContext.Use(creationContext)) + { + return unitOfWork.ServiceProvider.GetRequiredService(); } } } From 637f5331992aa12388c4e6ad171e3058366b0dd3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 25 Sep 2017 17:24:37 +0300 Subject: [PATCH 10/21] Implemented basic transaction API. --- .../Volo.Abp.EntityFrameworkCore.csproj | 1 + .../DatabaseFacadeExtensions.cs | 14 ++++++ .../DbContextExtensions.cs | 14 ++++++ .../DbContextCreationContext.cs | 5 +- .../DbContextOptionsFactory.cs | 50 +++++++++---------- .../DbContextEfCoreTransactionStrategy.cs | 46 +++++++++++++++++ .../EfCoreTransactionApi.cs | 48 ++++++++++++++++++ .../IEfCoreTransactionApi.cs | 15 ++++++ .../IEfCoreTransactionStrategy.cs | 11 ++++ .../UnitOfWorkDbContextProvider.cs | 31 ++++++++++-- src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs | 12 ++++- src/Volo.Abp/Volo/Abp/Uow/ITransactionApi.cs | 13 +++++ .../Volo/Abp/Uow/ITransactionApiContainer.cs | 12 +++++ src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs | 2 +- src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs | 40 +++++++++++++++ 15 files changed, 282 insertions(+), 32 deletions(-) create mode 100644 src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DatabaseFacadeExtensions.cs create mode 100644 src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DbContextExtensions.cs create mode 100644 src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/DbContextEfCoreTransactionStrategy.cs create mode 100644 src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs create mode 100644 src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionApi.cs create mode 100644 src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionStrategy.cs create mode 100644 src/Volo.Abp/Volo/Abp/Uow/ITransactionApi.cs create mode 100644 src/Volo.Abp/Volo/Abp/Uow/ITransactionApiContainer.cs diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj b/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj index 252c61d826..6f419752a4 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj +++ b/src/Volo.Abp.EntityFrameworkCore/Volo.Abp.EntityFrameworkCore.csproj @@ -17,6 +17,7 @@ + diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DatabaseFacadeExtensions.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DatabaseFacadeExtensions.cs new file mode 100644 index 0000000000..f663f43a0d --- /dev/null +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DatabaseFacadeExtensions.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.DependencyInjection; + +namespace Volo.Abp.EntityFrameworkCore +{ + public static class DatabaseFacadeExtensions + { + public static bool IsRelational(this DatabaseFacade database) + { + return database.GetInfrastructure().GetService() != null; + } + } +} diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DbContextExtensions.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DbContextExtensions.cs new file mode 100644 index 0000000000..3366186349 --- /dev/null +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DbContextExtensions.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Volo.Abp.EntityFrameworkCore +{ + internal static class DbContextExtensions + { + public static bool HasRelationalTransactionManager(this DbContext dbContext) + { + return dbContext.Database.GetService() is IRelationalTransactionManager; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextCreationContext.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextCreationContext.cs index b8a713c8aa..2b65b199a1 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextCreationContext.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextCreationContext.cs @@ -1,9 +1,10 @@ using System; +using System.Data.Common; using System.Threading; namespace Volo.Abp.EntityFrameworkCore.DependencyInjection { - internal class DbContextCreationContext + public class DbContextCreationContext { public static DbContextCreationContext Current => _current.Value; private static readonly AsyncLocal _current = new AsyncLocal(); @@ -12,6 +13,8 @@ namespace Volo.Abp.EntityFrameworkCore.DependencyInjection public string ConnectionString { get; } + public DbConnection ExistingConnection { get; set; } + public DbContextCreationContext(string connectionStringName, string connectionString) { ConnectionStringName = connectionStringName; diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs index 047c10ba6c..c4c5ef40c4 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/DependencyInjection/DbContextOptionsFactory.cs @@ -12,39 +12,37 @@ namespace Volo.Abp.EntityFrameworkCore.DependencyInjection public static DbContextOptions Create(IServiceProvider serviceProvider) where TDbContext : AbpDbContext { - using (var scope = serviceProvider.CreateScope()) - { - var creationContext = GetCreationContext(scope.ServiceProvider); - - var context = new AbpDbContextConfigurationContext( - creationContext.ConnectionString, - creationContext.ConnectionStringName, - scope.ServiceProvider - ); + var creationContext = GetCreationContext(serviceProvider); - var dbContextOptions = GetDbContextOptions(scope); + var context = new AbpDbContextConfigurationContext( + creationContext.ConnectionString, + creationContext.ConnectionStringName, + serviceProvider + ); - var configureAction = dbContextOptions.ConfigureActions.GetOrDefault(typeof(TDbContext)); - if (configureAction != null) - { - ((Action>)configureAction).Invoke(context); - } - else if (dbContextOptions.DefaultConfigureAction != null) - { - dbContextOptions.DefaultConfigureAction.Invoke(context); - } - else - { - throw new AbpException($"No configuration found for {typeof(DbContext).AssemblyQualifiedName}! Use services.Configure(...) to configure it."); - } + var dbContextOptions = GetDbContextOptions(serviceProvider); - return context.DbContextOptions.Options; + var configureAction = dbContextOptions.ConfigureActions.GetOrDefault(typeof(TDbContext)); + if (configureAction != null) + { + ((Action>)configureAction).Invoke(context); + } + else if (dbContextOptions.DefaultConfigureAction != null) + { + dbContextOptions.DefaultConfigureAction.Invoke(context); + } + else + { + throw new AbpException($"No configuration found for {typeof(DbContext).AssemblyQualifiedName}! Use services.Configure(...) to configure it."); } + + return context.DbContextOptions.Options; } - private static AbpDbContextOptions GetDbContextOptions(IServiceScope scope) where TDbContext : AbpDbContext + private static AbpDbContextOptions GetDbContextOptions(IServiceProvider serviceProvider) + where TDbContext : AbpDbContext { - return scope.ServiceProvider.GetRequiredService>().Value; + return serviceProvider.GetRequiredService>().Value; } private static DbContextCreationContext GetCreationContext(IServiceProvider serviceProvider) diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/DbContextEfCoreTransactionStrategy.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/DbContextEfCoreTransactionStrategy.cs new file mode 100644 index 0000000000..b1da0f8a71 --- /dev/null +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/DbContextEfCoreTransactionStrategy.cs @@ -0,0 +1,46 @@ +using System.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.DependencyInjection; + +namespace Volo.Abp.Uow.EntityFrameworkCore +{ + public class DbContextEfCoreTransactionStrategy : IEfCoreTransactionStrategy, ITransientDependency + { + public TDbContext CreateDbContext(IUnitOfWork unitOfWork, DbContextCreationContext creationContext) where TDbContext : DbContext + { + var transactionApiKey = $"EntityFrameworkCore_{creationContext.ConnectionString}"; + var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as IEfCoreTransactionApi; + + TDbContext dbContext; + if (activeTransaction == null) + { + dbContext = unitOfWork.ServiceProvider.GetRequiredService(); + var dbtransaction = dbContext.Database.BeginTransaction((unitOfWork.Options.IsolationLevel ?? IsolationLevel.ReadUncommitted)); + activeTransaction = new EfCoreTransactionApi(dbtransaction, dbContext); + unitOfWork.AddTransactionApi(transactionApiKey, activeTransaction); + } + else + { + creationContext.ExistingConnection = activeTransaction.DbContextTransaction.GetDbTransaction().Connection; + dbContext = unitOfWork.ServiceProvider.GetRequiredService(); + + if (dbContext.HasRelationalTransactionManager()) + { + dbContext.Database.UseTransaction(activeTransaction.DbContextTransaction.GetDbTransaction()); + } + else + { + dbContext.Database.BeginTransaction(); + } + + activeTransaction.AttendedDbContexts.Add(dbContext); + } + + return dbContext; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs new file mode 100644 index 0000000000..4bedb5e0ea --- /dev/null +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs @@ -0,0 +1,48 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; +using Volo.Abp.EntityFrameworkCore; + +namespace Volo.Abp.Uow.EntityFrameworkCore +{ + public class EfCoreTransactionApi : IEfCoreTransactionApi + { + public IDbContextTransaction DbContextTransaction { get; } + public DbContext StarterDbContext { get; } + public List AttendedDbContexts { get; } + + public EfCoreTransactionApi(IDbContextTransaction dbContextTransaction, DbContext starterDbContext) + { + DbContextTransaction = dbContextTransaction; + StarterDbContext = starterDbContext; + AttendedDbContexts = new List(); + } + + public void Commit() + { + DbContextTransaction.Commit(); + + foreach (var dbContext in AttendedDbContexts) + { + if (dbContext.HasRelationalTransactionManager()) + { + continue; //Relational databases use the shared transaction + } + + dbContext.Database.CommitTransaction(); + } + } + + public Task CommitAsync() + { + Commit(); + return Task.CompletedTask; + } + + public void Dispose() + { + DbContextTransaction.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionApi.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionApi.cs new file mode 100644 index 0000000000..881b8d202b --- /dev/null +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionApi.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; + +namespace Volo.Abp.Uow.EntityFrameworkCore +{ + public interface IEfCoreTransactionApi : ITransactionApi + { + IDbContextTransaction DbContextTransaction { get; } + + DbContext StarterDbContext { get; } + + List AttendedDbContexts { get; } + } +} \ No newline at end of file diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionStrategy.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionStrategy.cs new file mode 100644 index 0000000000..9536e4cad8 --- /dev/null +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionStrategy.cs @@ -0,0 +1,11 @@ +using Microsoft.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.DependencyInjection; + +namespace Volo.Abp.Uow.EntityFrameworkCore +{ + public interface IEfCoreTransactionStrategy + { + TDbContext CreateDbContext(IUnitOfWork unitOfWork, DbContextCreationContext creationContext) + where TDbContext : DbContext; + } +} diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs index 717a68ebe7..b4f573b26d 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs @@ -1,3 +1,5 @@ +using System; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; @@ -5,18 +7,23 @@ using Volo.Abp.EntityFrameworkCore.DependencyInjection; namespace Volo.Abp.Uow.EntityFrameworkCore { + //TODO: Implement logic in DefaultDbContextResolver.Resolve in old ABP. + public class UnitOfWorkDbContextProvider : IDbContextProvider where TDbContext : AbpDbContext { private readonly IUnitOfWorkManager _unitOfWorkManager; private readonly IConnectionStringResolver _connectionStringResolver; + private readonly IEfCoreTransactionStrategy _transactionStrategy; public UnitOfWorkDbContextProvider( IUnitOfWorkManager unitOfWorkManager, - IConnectionStringResolver connectionStringResolver) + IConnectionStringResolver connectionStringResolver, + IEfCoreTransactionStrategy transactionStrategy) { _unitOfWorkManager = unitOfWorkManager; _connectionStringResolver = connectionStringResolver; + _transactionStrategy = transactionStrategy; } public TDbContext GetDbContext() @@ -41,12 +48,30 @@ namespace Volo.Abp.Uow.EntityFrameworkCore return ((DbContextDatabaseApi)databaseApi).DbContext; } - private static TDbContext CreateDbContext(IUnitOfWork unitOfWork, string connectionStringName, string connectionString) + private TDbContext CreateDbContext(IUnitOfWork unitOfWork, string connectionStringName, string connectionString) { var creationContext = new DbContextCreationContext(connectionStringName, connectionString); using (DbContextCreationContext.Use(creationContext)) { - return unitOfWork.ServiceProvider.GetRequiredService(); + TDbContext dbContext; + + if (unitOfWork.Options.IsTransactional == true) + { + dbContext = _transactionStrategy.CreateDbContext(unitOfWork, creationContext); + } + else + { + dbContext = unitOfWork.ServiceProvider.GetRequiredService(); + } + + if (unitOfWork.Options.Timeout.HasValue && + dbContext.Database.IsRelational() && + !dbContext.Database.GetCommandTimeout().HasValue) + { + dbContext.Database.SetCommandTimeout(unitOfWork.Options.Timeout.Value.TotalSeconds.To()); + } + + return dbContext; } } } diff --git a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs index 15e03332f6..8e848aae34 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs @@ -83,7 +83,17 @@ namespace Volo.Abp.Uow { return _parent.GetOrAddDatabaseApi(key, factory); } - + + public ITransactionApi FindTransactionApi(string key) + { + return _parent.FindTransactionApi(key); + } + + public void AddTransactionApi(string key, ITransactionApi api) + { + _parent.AddTransactionApi(key, api); + } + public void Dispose() { diff --git a/src/Volo.Abp/Volo/Abp/Uow/ITransactionApi.cs b/src/Volo.Abp/Volo/Abp/Uow/ITransactionApi.cs new file mode 100644 index 0000000000..1f6f9aa226 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Uow/ITransactionApi.cs @@ -0,0 +1,13 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Uow +{ + public interface ITransactionApi + { + void Commit(); + + Task CommitAsync(); + + void Dispose(); + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/ITransactionApiContainer.cs b/src/Volo.Abp/Volo/Abp/Uow/ITransactionApiContainer.cs new file mode 100644 index 0000000000..dde7ace71f --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Uow/ITransactionApiContainer.cs @@ -0,0 +1,12 @@ +using JetBrains.Annotations; + +namespace Volo.Abp.Uow +{ + public interface ITransactionApiContainer + { + [CanBeNull] + ITransactionApi FindTransactionApi([NotNull] string key); + + void AddTransactionApi([NotNull] string key, [NotNull] ITransactionApi api); + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs index 442c03c075..49352a3bd2 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs @@ -2,7 +2,7 @@ namespace Volo.Abp.Uow { - public interface IUnitOfWork : IBasicUnitOfWork, IDatabaseApiContainer + public interface IUnitOfWork : IBasicUnitOfWork, IDatabaseApiContainer, ITransactionApiContainer { UnitOfWorkStartOptions Options { get; } diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs index 2409fbd4ba..e0e6beaf02 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs @@ -35,6 +35,7 @@ namespace Volo.Abp.Uow public IServiceProvider ServiceProvider { get; } private readonly Dictionary _databaseApis; + private readonly Dictionary _transactionApis; private Exception _exception; private bool _isCompleted; @@ -45,6 +46,7 @@ namespace Volo.Abp.Uow ServiceProvider = serviceProvider; _databaseApis = new Dictionary(); + _transactionApis = new Dictionary(); } public void SaveChanges() @@ -72,6 +74,7 @@ namespace Volo.Abp.Uow try { SaveChanges(); + CommitTransactions(); OnCompleted(); } catch (Exception ex) @@ -88,6 +91,7 @@ namespace Volo.Abp.Uow try { await SaveChangesAsync(cancellationToken); + await CommitTransactionsAsync(); OnCompleted(); } catch (Exception ex) @@ -110,6 +114,26 @@ namespace Volo.Abp.Uow return _databaseApis.GetOrAdd(key, factory); } + public ITransactionApi FindTransactionApi(string key) + { + Check.NotNull(key, nameof(key)); + + return _transactionApis.GetOrDefault(key); + } + + public void AddTransactionApi(string key, ITransactionApi api) + { + Check.NotNull(key, nameof(key)); + Check.NotNull(api, nameof(api)); + + if (_transactionApis.ContainsKey(key)) + { + throw new AbpException("There is already a transaction API in this unit of work!"); + } + + _transactionApis.Add(key, api); + } + protected virtual void OnCompleted() { Completed.InvokeSafely(this); @@ -156,5 +180,21 @@ namespace Volo.Abp.Uow { return $"[UnitOfWork {Id}]"; } + + protected virtual void CommitTransactions() + { + foreach (var transaction in _transactionApis.Values) + { + transaction.Commit(); + } + } + + protected virtual async Task CommitTransactionsAsync() + { + foreach (var transaction in _transactionApis.Values) + { + await transaction.CommitAsync(); + } + } } } \ No newline at end of file From e35ebf0a6b7b9268b52649ae9e8257cbad47fdad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 25 Sep 2017 17:50:17 +0300 Subject: [PATCH 11/21] Refactor. --- .../Volo/Abp/Uow/UnitOfWorkManager.cs | 2 +- .../Volo/Abp/Uow/UnitOfWorkOptions.cs | 15 ++++++++ .../Volo/Abp/Uow/UnitOfWorkStartOptions.cs | 37 ------------------- .../Abp/Uow/UnitOfWorkTransactionBehavior.cs | 9 +++++ 4 files changed, 25 insertions(+), 38 deletions(-) create mode 100644 src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkOptions.cs create mode 100644 src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkTransactionBehavior.cs diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs index 334c07a369..c4ece92369 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs @@ -91,7 +91,7 @@ namespace Volo.Abp.Uow unitOfWork.SetOuter(outerUow); unitOfWork.IsReserved = options.ReservationName != null; unitOfWork.ReservationName = options.ReservationName; - unitOfWork.SetOptions(options); + unitOfWork.SetOptions(options); //TODO: Should not call this for reservation? _ambientUnitOfWork.SetUnitOfWork(unitOfWork); diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkOptions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkOptions.cs new file mode 100644 index 0000000000..e2f567720e --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkOptions.cs @@ -0,0 +1,15 @@ +using System.Data; + +namespace Volo.Abp.Uow +{ + /// + /// Global (default) unit of work options + /// + public class UnitOfWorkOptions + { + //TODO: Implement default options! + public UnitOfWorkTransactionBehavior TransactionBehavior { get; set; } + + public IsolationLevel? IsolationLevel { get; set; } + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs index ba398250e9..6a227389e1 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs @@ -1,6 +1,5 @@ using System; using System.Data; -using Volo.Abp.DependencyInjection; namespace Volo.Abp.Uow { @@ -18,40 +17,4 @@ namespace Volo.Abp.Uow public TimeSpan? Timeout { get; set; } } - - /// - /// Global (default) unit of work options - /// - public class UnitOfWorkOptions - { - public IUnitOfWorkTransactionBehavior IsTransactional { get; set; } - - public IsolationLevel? IsolationLevel { get; set; } - } - - public interface IUnitOfWorkTransactionBehavior - { - UnitOfWorkStartOptions Set(UnitOfWorkTransactionBehaviorContext context); - } - - public class ConventionalUnitOfWorkTransactionBehavior : IUnitOfWorkTransactionBehavior - { - public UnitOfWorkStartOptions Set(UnitOfWorkTransactionBehaviorContext context) - { - throw new NotImplementedException(); - } - } - - public class UnitOfWorkTransactionBehaviorContext : IServiceProviderAccessor - { - public UnitOfWorkStartOptions Options { get; } - - public IServiceProvider ServiceProvider { get; } - - public UnitOfWorkTransactionBehaviorContext(UnitOfWorkStartOptions options, IServiceProvider serviceProvider) - { - Options = options; - ServiceProvider = serviceProvider; - } - } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkTransactionBehavior.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkTransactionBehavior.cs new file mode 100644 index 0000000000..562c8fc063 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkTransactionBehavior.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Uow +{ + public enum UnitOfWorkTransactionBehavior + { + Auto, + Enabled, + Disabled + } +} \ No newline at end of file From 6c6b1b080a059dc2eefa7dc27efb8f1688b8a6fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 25 Sep 2017 20:55:34 +0300 Subject: [PATCH 12/21] Refactored UOW. --- src/AbpDesk/Web_PlugIns/AbpDesk.MongoBlog.dll | Bin 15872 -> 15872 bytes .../Volo/Abp/Uow/IUnitOfWorkManager.cs | 5 ++- .../Volo/Abp/Uow/UnitOfWorkManager.cs | 36 ++++++++++++------ .../Abp/Uow/UnitOfWorkManagerExtensions.cs | 21 +--------- .../Volo/Abp/Uow/UnitOfWorkStartOptions.cs | 6 --- .../Volo/Abp/Uow/UnitOfWork_Nested_Tests.cs | 2 +- 6 files changed, 32 insertions(+), 38 deletions(-) diff --git a/src/AbpDesk/Web_PlugIns/AbpDesk.MongoBlog.dll b/src/AbpDesk/Web_PlugIns/AbpDesk.MongoBlog.dll index 14344b9ee16c8c7bb31dccbc2fdee66d6b647980..484411d11ecce57e134ddd62f903e5a95be1a7ff 100644 GIT binary patch delta 1360 zcmZvcTTEP46o&t`_F-;tW`@CH&8RJfDD*-g4pgKUMjDhBY>`@vHriOIA(~i29~>c_ znUW94o0v2$YBep1^^%&@wqDlW%$wcG{^no* z{?|Td_MAD1oF=^19=*yerYK=D8Ao`7SZR$MeENhV6V=bdk~P4WHwm#QB}(*@d0fFT(xa--OZJ z+v0D+J>eb0t*}%z0u)q*AUvRUtd#4bZ+XW1s3Np4RhZr7FI9{&Q)aNjS1HwqU?Dg_9-mt@T zES=+zQ+ze$bAi8>%6Q6th8n5bYNZma^S4t3pHeqP=~c^x8ZH+;MEW?|DCSGj9DY^( z)X2v?M`#!vI7(5@cu&v<(-kwxVcg}jlob6X4H>7CKX%X17Mzluo8-7$act4|MKPZ9 z-!wOIi>6F;Eyi4KUW<=hxtwI3DdR~y$aA_4?{mt(nuqv1s^AiIQZ?_yn74)ZV20eB zhe_%Xea+706i@l%;tYEFgg5POn&1iF0Q=#=D91x+MutOZ5thpXd`6X{0Mo)S7KFvP zAzWklgnZ|6)EZrHxD76=9IdDjzKqv}`;ZX6fk9!HkRiACYBuQw5&#O7UT3Y-J?Rzk$<2ImOP^+Dy4wi ze$7wpQQ>jp9CmBx2hX_hJf{>p|K@iD+UQN{qEmE1M z#5o<>-ZH|ydAc&oTF4mTw)=Ps_wlCw?>XBFI+qnuE2`-QnT=?s7`~?CIFOzxs4ocq z5#E7A+ncwJkJd!o?|e9!e)#bR$>#-!5~)y%rIfU~o9X(sQkM0c!~HMMc0TX-`JV4N z@4Iu}_f2*syWV=uO22&c%vfI+(^ki)YBP_iQhE@vYJdT(lTHNGO3xFmoAzz#b>cN; z1}vW^@g=t=d({lD#4Z0NCirxE*v*i3gy_KT)1eeDMa&{ojv)H;8x{Eahc9f`D~SS2J<6J zv*f`_}|@6M{;Aw?H$h5{0QxJ=FKb{f97&=l_eYh6O?Fyjg& zC{am;91g6ZN*=c#rAAt1Jx%3U9%!d(oT391qZceUR&$l`KGNIKMsa_Vrg2lfL5=*b z_e~lE2aiyUr+n|zIx{P7l7n(WqSG%FVJ}?gZBB=b|`I2YxXR z@jH#1=rWAD-MkFP+|8eZq&KqU|Gx3q%G(MeI>f>GZleh%l!&G|S@ z9ir!KKM(VG;Gj7D-b2D0_5m8>F@G-y;Kc|hLU;n7a|oM-Rq_IVqN)(agfN1u!ZQ3U zTx$3*ZirrMbc10NTvioYutK;EJB95?2zQ}RxKHx+=)n=I9=-V5YQQV_&T2;nKUpa_ z;-nHdi;GqdKE-9%u(5`XGimgs(X&Q>pQFxSGnz;*L8KjJbZL&-i5VR;quY&68V(sw z7|v2cF9FsZGMq4+HH6ZBiDA9rcEjX?W1W=IYMhZ1|B>dYh+Xmpx?#yXTCRo^@OMD- zLwiJ+Hcq!kJ3n|&3D5G7V&~t&9l>X57rjI$=n`F_4czAJ3zZeU&4=}0#QaM1RkyTr zGW68uH237|$_#5EEzNCr@n-MhjsM?sn!=q$CDei_JtMsl%@oHprLiY-KHLy4xUl=9 z>$|^seq;YMFS(a|UQj4G97<89w&H@72`-O%t(IGdPB@Q8s|)Izi?6w2 UE%m97U%c~M(C*LlMfZFE1F1bS>;M1& diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs index 493a5b1c78..1fc871dd3e 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs @@ -8,7 +8,10 @@ namespace Volo.Abp.Uow IUnitOfWork Current { get; } [NotNull] - IBasicUnitOfWork Begin([NotNull] UnitOfWorkStartOptions options); + IBasicUnitOfWork Begin([NotNull] UnitOfWorkStartOptions options, bool requiresNew = false); + + [NotNull] + IBasicUnitOfWork Reserve([NotNull] string reservationName, bool requiresNew = false); void BeginReserved([NotNull] string reservationName, [NotNull] UnitOfWorkStartOptions options); diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs index c4ece92369..ccf3d6e8b1 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs @@ -1,4 +1,5 @@ using System; +using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; @@ -17,22 +18,38 @@ namespace Volo.Abp.Uow _ambientUnitOfWork = ambientUnitOfWork; } - public IBasicUnitOfWork Begin(UnitOfWorkStartOptions options) + public IBasicUnitOfWork Begin(UnitOfWorkStartOptions options, bool requiresNew = false) { Check.NotNull(options, nameof(options)); - if (!options.RequiresNew && _ambientUnitOfWork.UnitOfWork != null && !_ambientUnitOfWork.UnitOfWork.IsReserved) + if (!requiresNew && _ambientUnitOfWork.UnitOfWork != null && !_ambientUnitOfWork.UnitOfWork.IsReserved) { return new ChildUnitOfWork(_ambientUnitOfWork.UnitOfWork); } - if (_ambientUnitOfWork.UnitOfWork != null) + var unitOfWork = CreateNewUnitOfWork(); + unitOfWork.SetOptions(options); + + return unitOfWork; + } + + public IBasicUnitOfWork Reserve(string reservationName, bool requiresNew = false) + { + Check.NotNull(reservationName, nameof(reservationName)); + + if (!requiresNew && + _ambientUnitOfWork.UnitOfWork != null && + _ambientUnitOfWork.UnitOfWork.IsReservedFor(reservationName)) { - //Requires new because there is already a current UOW but it's reserved - options.RequiresNew = true; + return new ChildUnitOfWork(_ambientUnitOfWork.UnitOfWork); } - return CreateUnitOfWork(options); + var unitOfWork = CreateNewUnitOfWork(); + + unitOfWork.IsReserved = true; + unitOfWork.ReservationName = reservationName; + + return unitOfWork; } public void BeginReserved(string reservationName, UnitOfWorkStartOptions options) @@ -62,6 +79,7 @@ namespace Volo.Abp.Uow uow.IsReserved = false; uow.SetOptions(options); + return true; } @@ -78,10 +96,9 @@ namespace Volo.Abp.Uow return uow; } - private IUnitOfWork CreateUnitOfWork(UnitOfWorkStartOptions options) + private IUnitOfWork CreateNewUnitOfWork() { var scope = _serviceProvider.CreateScope(); - try { var outerUow = _ambientUnitOfWork.UnitOfWork; @@ -89,9 +106,6 @@ namespace Volo.Abp.Uow var unitOfWork = scope.ServiceProvider.GetRequiredService(); unitOfWork.SetOuter(outerUow); - unitOfWork.IsReserved = options.ReservationName != null; - unitOfWork.ReservationName = options.ReservationName; - unitOfWork.SetOptions(options); //TODO: Should not call this for reservation? _ambientUnitOfWork.SetUnitOfWork(unitOfWork); diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs index 5be61ae8ae..8e4aaae90d 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs @@ -5,28 +5,11 @@ namespace Volo.Abp.Uow public static class UnitOfWorkManagerExtensions { [NotNull] - public static IBasicUnitOfWork Begin([NotNull] this IUnitOfWorkManager unitOfWorkManager) + public static IBasicUnitOfWork Begin([NotNull] this IUnitOfWorkManager unitOfWorkManager, bool requiresNew = false) { Check.NotNull(unitOfWorkManager, nameof(unitOfWorkManager)); - return unitOfWorkManager.Begin(new UnitOfWorkStartOptions()); - } - - [NotNull] - public static IBasicUnitOfWork BeginNew([NotNull] this IUnitOfWorkManager unitOfWorkManager) - { - Check.NotNull(unitOfWorkManager, nameof(unitOfWorkManager)); - - return unitOfWorkManager.Begin(new UnitOfWorkStartOptions {RequiresNew = true}); - } - - [NotNull] - public static IBasicUnitOfWork Reserve([NotNull] this IUnitOfWorkManager unitOfWorkManager, [NotNull] string reservationName) - { - Check.NotNull(unitOfWorkManager, nameof(unitOfWorkManager)); - Check.NotNull(reservationName, nameof(reservationName)); - - return unitOfWorkManager.Begin(new UnitOfWorkStartOptions { ReservationName = reservationName }); + return unitOfWorkManager.Begin(new UnitOfWorkStartOptions(), requiresNew); } public static void BeginReserved([NotNull] this IUnitOfWorkManager unitOfWorkManager, [NotNull] string reservationName) diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs index 6a227389e1..b5799df7ff 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs @@ -5,12 +5,6 @@ namespace Volo.Abp.Uow { public class UnitOfWorkStartOptions { - //Revise this since Begin/BeginReserved accepts different options and that can make confusion! - - public bool RequiresNew { get; set; } - - public string ReservationName { get; set; } - public bool? IsTransactional { get; set; } public IsolationLevel? IsolationLevel { get; set; } diff --git a/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Nested_Tests.cs b/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Nested_Tests.cs index 5c4b568b4a..e3f2e9a0c0 100644 --- a/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Nested_Tests.cs +++ b/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Nested_Tests.cs @@ -26,7 +26,7 @@ namespace Volo.Abp.Uow _unitOfWorkManager.Current.ShouldNotBeNull(); _unitOfWorkManager.Current.ShouldBe(uow1); - using (var uow2 = _unitOfWorkManager.BeginNew()) + using (var uow2 = _unitOfWorkManager.Begin(requiresNew: true)) { _unitOfWorkManager.Current.ShouldNotBeNull(); _unitOfWorkManager.Current.Id.ShouldNotBe(uow1.Id); From 83be48eff5959e93c0505947f6f2030c78709cad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 25 Sep 2017 21:35:55 +0300 Subject: [PATCH 13/21] Improved uow. --- src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs | 2 +- src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs | 2 +- .../Volo/Abp/Uow/IUnitOfWorkStartOptions.cs | 14 ++++++++ src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs | 33 ++++++++++++------- .../Volo/Abp/Uow/UnitOfWorkAttribute.cs | 2 +- .../Volo/Abp/Uow/UnitOfWorkManager.cs | 5 +-- .../Volo/Abp/Uow/UnitOfWorkOptions.cs | 23 +++++++++++-- .../Volo/Abp/Uow/UnitOfWorkStartOptions.cs | 17 ++++++++-- .../Abp/Uow/UnitOfWorkTransactionBehavior.cs | 2 ++ 9 files changed, 79 insertions(+), 21 deletions(-) create mode 100644 src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkStartOptions.cs diff --git a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs index 8e848aae34..d5a6e82246 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs @@ -9,7 +9,7 @@ namespace Volo.Abp.Uow { public Guid Id => _parent.Id; - public UnitOfWorkStartOptions Options => _parent.Options; + public IUnitOfWorkStartOptions Options => _parent.Options; public IUnitOfWork Outer => _parent.Outer; diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs index 49352a3bd2..ba47e431d2 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs @@ -4,7 +4,7 @@ namespace Volo.Abp.Uow { public interface IUnitOfWork : IBasicUnitOfWork, IDatabaseApiContainer, ITransactionApiContainer { - UnitOfWorkStartOptions Options { get; } + IUnitOfWorkStartOptions Options { get; } IUnitOfWork Outer { get; } diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkStartOptions.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkStartOptions.cs new file mode 100644 index 0000000000..9fe3bc1084 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkStartOptions.cs @@ -0,0 +1,14 @@ +using System; +using System.Data; + +namespace Volo.Abp.Uow +{ + public interface IUnitOfWorkStartOptions + { + bool IsTransactional { get; } + + IsolationLevel? IsolationLevel { get; } + + TimeSpan? Timeout { get; } + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs index e0e6beaf02..173a8718df 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs @@ -2,6 +2,8 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; namespace Volo.Abp.Uow @@ -10,7 +12,7 @@ namespace Volo.Abp.Uow { public Guid Id { get; } = Guid.NewGuid(); - public UnitOfWorkStartOptions Options { get; private set; } + public IUnitOfWorkStartOptions Options { get; private set; } public IUnitOfWork Outer { get; private set; } @@ -18,16 +20,6 @@ namespace Volo.Abp.Uow public string ReservationName { get; set; } - public void SetOuter(IUnitOfWork outer) - { - Outer = outer; - } - - public void SetOptions(UnitOfWorkStartOptions options) - { - Options = options; - } - public event EventHandler Completed; public event EventHandler Failed; public event EventHandler Disposed; @@ -36,19 +28,36 @@ namespace Volo.Abp.Uow private readonly Dictionary _databaseApis; private readonly Dictionary _transactionApis; + private readonly UnitOfWorkOptions _defaultOptions; private Exception _exception; private bool _isCompleted; private bool _isDisposed; - public UnitOfWork(IServiceProvider serviceProvider) + public UnitOfWork(IServiceProvider serviceProvider, IOptions options) { ServiceProvider = serviceProvider; + _defaultOptions = options.Value; _databaseApis = new Dictionary(); _transactionApis = new Dictionary(); } + public void SetOptions(UnitOfWorkStartOptions options) + { + if (Options != null) + { + throw new AbpException("Options must be set only once!"); + } + + Options = _defaultOptions.Normalize(options.Clone()); + } + + public void SetOuter(IUnitOfWork outer) + { + Outer = outer; + } + public void SaveChanges() { foreach (var databaseApi in _databaseApis.Values) diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkAttribute.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkAttribute.cs index 3b3f68bbba..7c58fccfc0 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkAttribute.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkAttribute.cs @@ -41,7 +41,7 @@ namespace Volo.Abp.Uow { if (IsTransactional.HasValue) { - options.IsTransactional = IsTransactional; + options.IsTransactional = IsTransactional.Value; } if (Timeout.HasValue) diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs index ccf3d6e8b1..50bb080583 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs @@ -1,5 +1,4 @@ using System; -using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.DependencyInjection; @@ -12,7 +11,9 @@ namespace Volo.Abp.Uow private readonly IServiceProvider _serviceProvider; private readonly IAmbientUnitOfWork _ambientUnitOfWork; - public UnitOfWorkManager(IServiceProvider serviceProvider, IAmbientUnitOfWork ambientUnitOfWork) + public UnitOfWorkManager( + IServiceProvider serviceProvider, + IAmbientUnitOfWork ambientUnitOfWork) { _serviceProvider = serviceProvider; _ambientUnitOfWork = ambientUnitOfWork; diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkOptions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkOptions.cs index e2f567720e..c073c7ad6a 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkOptions.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkOptions.cs @@ -1,15 +1,34 @@ -using System.Data; +using System; +using System.Data; namespace Volo.Abp.Uow { + //TODO: Implement default options! + /// /// Global (default) unit of work options /// public class UnitOfWorkOptions { - //TODO: Implement default options! public UnitOfWorkTransactionBehavior TransactionBehavior { get; set; } public IsolationLevel? IsolationLevel { get; set; } + + public TimeSpan? Timeout { get; set; } + + internal UnitOfWorkStartOptions Normalize(UnitOfWorkStartOptions options) + { + if (options.IsolationLevel == null) + { + options.IsolationLevel = IsolationLevel; + } + + if (options.Timeout == null) + { + options.Timeout = Timeout; + } + + return options; + } } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs index b5799df7ff..ccde083786 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs @@ -3,12 +3,25 @@ using System.Data; namespace Volo.Abp.Uow { - public class UnitOfWorkStartOptions + public class UnitOfWorkStartOptions : IUnitOfWorkStartOptions { - public bool? IsTransactional { get; set; } + /// + /// Default: false. + /// + public bool IsTransactional { get; set; } public IsolationLevel? IsolationLevel { get; set; } public TimeSpan? Timeout { get; set; } + + public UnitOfWorkStartOptions Clone() + { + return new UnitOfWorkStartOptions + { + IsTransactional = IsTransactional, + IsolationLevel = IsolationLevel, + Timeout = Timeout + }; + } } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkTransactionBehavior.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkTransactionBehavior.cs index 562c8fc063..cabcfd0999 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkTransactionBehavior.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkTransactionBehavior.cs @@ -3,7 +3,9 @@ public enum UnitOfWorkTransactionBehavior { Auto, + Enabled, + Disabled } } \ No newline at end of file From df010cc4fea106859623281a61e99462f342248e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 25 Sep 2017 21:41:46 +0300 Subject: [PATCH 14/21] Refactored. --- src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs | 10 ++++++++ .../Volo/Abp/Uow/IDatabaseApiContainer.cs | 2 ++ .../Volo/Abp/Uow/ITransactionApiContainer.cs | 6 ++++- src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs | 24 +++++++++++++++++-- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs index d5a6e82246..3683bbd7c4 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs @@ -79,6 +79,11 @@ namespace Volo.Abp.Uow return _parent.FindDatabaseApi(key); } + public void AddDatabaseApi(string key, IDatabaseApi api) + { + _parent.AddDatabaseApi(key, api); + } + public IDatabaseApi GetOrAddDatabaseApi(string key, Func factory) { return _parent.GetOrAddDatabaseApi(key, factory); @@ -94,6 +99,11 @@ namespace Volo.Abp.Uow _parent.AddTransactionApi(key, api); } + public ITransactionApi GetOrAddTransactionApi(string key, Func factory) + { + return _parent.GetOrAddTransactionApi(key, factory); + } + public void Dispose() { diff --git a/src/Volo.Abp/Volo/Abp/Uow/IDatabaseApiContainer.cs b/src/Volo.Abp/Volo/Abp/Uow/IDatabaseApiContainer.cs index d896574a9a..5ee4ff5f41 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IDatabaseApiContainer.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IDatabaseApiContainer.cs @@ -9,6 +9,8 @@ namespace Volo.Abp.Uow [CanBeNull] IDatabaseApi FindDatabaseApi([NotNull] string key); + void AddDatabaseApi([NotNull] string key, [NotNull] IDatabaseApi api); + [NotNull] IDatabaseApi GetOrAddDatabaseApi([NotNull] string key, [NotNull] Func factory); } diff --git a/src/Volo.Abp/Volo/Abp/Uow/ITransactionApiContainer.cs b/src/Volo.Abp/Volo/Abp/Uow/ITransactionApiContainer.cs index dde7ace71f..b3dd050dc5 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/ITransactionApiContainer.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/ITransactionApiContainer.cs @@ -1,4 +1,5 @@ -using JetBrains.Annotations; +using System; +using JetBrains.Annotations; namespace Volo.Abp.Uow { @@ -8,5 +9,8 @@ namespace Volo.Abp.Uow ITransactionApi FindTransactionApi([NotNull] string key); void AddTransactionApi([NotNull] string key, [NotNull] ITransactionApi api); + + [NotNull] + ITransactionApi GetOrAddTransactionApi([NotNull] string key, [NotNull] Func factory); } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs index 173a8718df..810a94ec91 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using JetBrains.Annotations; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; @@ -115,6 +114,19 @@ namespace Volo.Abp.Uow return _databaseApis.GetOrDefault(key); } + public void AddDatabaseApi(string key, IDatabaseApi api) + { + Check.NotNull(key, nameof(key)); + Check.NotNull(api, nameof(api)); + + if (_databaseApis.ContainsKey(key)) + { + throw new AbpException("There is already a database API in this unit of work with given key: " + key); + } + + _databaseApis.Add(key, api); + } + public IDatabaseApi GetOrAddDatabaseApi(string key, Func factory) { Check.NotNull(key, nameof(key)); @@ -137,12 +149,20 @@ namespace Volo.Abp.Uow if (_transactionApis.ContainsKey(key)) { - throw new AbpException("There is already a transaction API in this unit of work!"); + throw new AbpException("There is already a transaction API in this unit of work with given key: " + key); } _transactionApis.Add(key, api); } + public ITransactionApi GetOrAddTransactionApi(string key, Func factory) + { + Check.NotNull(key, nameof(key)); + Check.NotNull(factory, nameof(factory)); + + return _transactionApis.GetOrAdd(key, factory); + } + protected virtual void OnCompleted() { Completed.InvokeSafely(this); From aad83726c7efcbc09ca733dc2d9a5395f5ef3957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Mon, 25 Sep 2017 22:02:59 +0300 Subject: [PATCH 15/21] Refactored. --- .../DbContextEfCoreTransactionStrategy.cs | 46 ------------ ...extDatabaseApi.cs => EfCoreDatabaseApi.cs} | 4 +- .../EfCoreTransactionApi.cs | 2 +- .../IEfCoreTransactionApi.cs | 15 ---- .../IEfCoreTransactionStrategy.cs | 11 --- .../UnitOfWorkDbContextProvider.cs | 72 ++++++++++++++----- 6 files changed, 59 insertions(+), 91 deletions(-) delete mode 100644 src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/DbContextEfCoreTransactionStrategy.cs rename src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/{DbContextDatabaseApi.cs => EfCoreDatabaseApi.cs} (79%) delete mode 100644 src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionApi.cs delete mode 100644 src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionStrategy.cs diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/DbContextEfCoreTransactionStrategy.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/DbContextEfCoreTransactionStrategy.cs deleted file mode 100644 index b1da0f8a71..0000000000 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/DbContextEfCoreTransactionStrategy.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Data; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.DependencyInjection; -using Volo.Abp.EntityFrameworkCore; -using Volo.Abp.EntityFrameworkCore.DependencyInjection; - -namespace Volo.Abp.Uow.EntityFrameworkCore -{ - public class DbContextEfCoreTransactionStrategy : IEfCoreTransactionStrategy, ITransientDependency - { - public TDbContext CreateDbContext(IUnitOfWork unitOfWork, DbContextCreationContext creationContext) where TDbContext : DbContext - { - var transactionApiKey = $"EntityFrameworkCore_{creationContext.ConnectionString}"; - var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as IEfCoreTransactionApi; - - TDbContext dbContext; - if (activeTransaction == null) - { - dbContext = unitOfWork.ServiceProvider.GetRequiredService(); - var dbtransaction = dbContext.Database.BeginTransaction((unitOfWork.Options.IsolationLevel ?? IsolationLevel.ReadUncommitted)); - activeTransaction = new EfCoreTransactionApi(dbtransaction, dbContext); - unitOfWork.AddTransactionApi(transactionApiKey, activeTransaction); - } - else - { - creationContext.ExistingConnection = activeTransaction.DbContextTransaction.GetDbTransaction().Connection; - dbContext = unitOfWork.ServiceProvider.GetRequiredService(); - - if (dbContext.HasRelationalTransactionManager()) - { - dbContext.Database.UseTransaction(activeTransaction.DbContextTransaction.GetDbTransaction()); - } - else - { - dbContext.Database.BeginTransaction(); - } - - activeTransaction.AttendedDbContexts.Add(dbContext); - } - - return dbContext; - } - } -} \ No newline at end of file diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/DbContextDatabaseApi.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreDatabaseApi.cs similarity index 79% rename from src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/DbContextDatabaseApi.cs rename to src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreDatabaseApi.cs index 41fb5b8610..b73b237000 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/DbContextDatabaseApi.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreDatabaseApi.cs @@ -4,12 +4,12 @@ using Volo.Abp.EntityFrameworkCore; namespace Volo.Abp.Uow.EntityFrameworkCore { - public class DbContextDatabaseApi : IDatabaseApi, ISupportsSavingChanges + public class EfCoreDatabaseApi : IDatabaseApi, ISupportsSavingChanges where TDbContext : AbpDbContext { public TDbContext DbContext { get; } - public DbContextDatabaseApi(TDbContext dbContext) + public EfCoreDatabaseApi(TDbContext dbContext) { DbContext = dbContext; } diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs index 4bedb5e0ea..f5016f7bda 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs @@ -6,7 +6,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Volo.Abp.Uow.EntityFrameworkCore { - public class EfCoreTransactionApi : IEfCoreTransactionApi + public class EfCoreTransactionApi : ITransactionApi { public IDbContextTransaction DbContextTransaction { get; } public DbContext StarterDbContext { get; } diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionApi.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionApi.cs deleted file mode 100644 index 881b8d202b..0000000000 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionApi.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System.Collections.Generic; -using Microsoft.EntityFrameworkCore; -using Microsoft.EntityFrameworkCore.Storage; - -namespace Volo.Abp.Uow.EntityFrameworkCore -{ - public interface IEfCoreTransactionApi : ITransactionApi - { - IDbContextTransaction DbContextTransaction { get; } - - DbContext StarterDbContext { get; } - - List AttendedDbContexts { get; } - } -} \ No newline at end of file diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionStrategy.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionStrategy.cs deleted file mode 100644 index 9536e4cad8..0000000000 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/IEfCoreTransactionStrategy.cs +++ /dev/null @@ -1,11 +0,0 @@ -using Microsoft.EntityFrameworkCore; -using Volo.Abp.EntityFrameworkCore.DependencyInjection; - -namespace Volo.Abp.Uow.EntityFrameworkCore -{ - public interface IEfCoreTransactionStrategy - { - TDbContext CreateDbContext(IUnitOfWork unitOfWork, DbContextCreationContext creationContext) - where TDbContext : DbContext; - } -} diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs index b4f573b26d..596fc72917 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/UnitOfWorkDbContextProvider.cs @@ -1,5 +1,6 @@ using System; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Data; using Volo.Abp.EntityFrameworkCore; @@ -14,16 +15,13 @@ namespace Volo.Abp.Uow.EntityFrameworkCore { private readonly IUnitOfWorkManager _unitOfWorkManager; private readonly IConnectionStringResolver _connectionStringResolver; - private readonly IEfCoreTransactionStrategy _transactionStrategy; public UnitOfWorkDbContextProvider( IUnitOfWorkManager unitOfWorkManager, - IConnectionStringResolver connectionStringResolver, - IEfCoreTransactionStrategy transactionStrategy) + IConnectionStringResolver connectionStringResolver) { _unitOfWorkManager = unitOfWorkManager; _connectionStringResolver = connectionStringResolver; - _transactionStrategy = transactionStrategy; } public TDbContext GetDbContext() @@ -41,11 +39,11 @@ namespace Volo.Abp.Uow.EntityFrameworkCore var databaseApi = unitOfWork.GetOrAddDatabaseApi( dbContextKey, - () => new DbContextDatabaseApi( + () => new EfCoreDatabaseApi( CreateDbContext(unitOfWork, connectionStringName, connectionString) )); - return ((DbContextDatabaseApi)databaseApi).DbContext; + return ((EfCoreDatabaseApi)databaseApi).DbContext; } private TDbContext CreateDbContext(IUnitOfWork unitOfWork, string connectionStringName, string connectionString) @@ -53,16 +51,7 @@ namespace Volo.Abp.Uow.EntityFrameworkCore var creationContext = new DbContextCreationContext(connectionStringName, connectionString); using (DbContextCreationContext.Use(creationContext)) { - TDbContext dbContext; - - if (unitOfWork.Options.IsTransactional == true) - { - dbContext = _transactionStrategy.CreateDbContext(unitOfWork, creationContext); - } - else - { - dbContext = unitOfWork.ServiceProvider.GetRequiredService(); - } + var dbContext = CreateDbContext(unitOfWork); if (unitOfWork.Options.Timeout.HasValue && dbContext.Database.IsRelational() && @@ -74,5 +63,56 @@ namespace Volo.Abp.Uow.EntityFrameworkCore return dbContext; } } + + private TDbContext CreateDbContext(IUnitOfWork unitOfWork) + { + return unitOfWork.Options.IsTransactional + ? CreateDbContextWithTransaction(unitOfWork) + : unitOfWork.ServiceProvider.GetRequiredService(); + } + + public TDbContext CreateDbContextWithTransaction(IUnitOfWork unitOfWork) + { + var transactionApiKey = $"EntityFrameworkCore_{DbContextCreationContext.Current.ConnectionString}"; + var activeTransaction = unitOfWork.FindTransactionApi(transactionApiKey) as EfCoreTransactionApi; + + if (activeTransaction == null) + { + var dbContext = unitOfWork.ServiceProvider.GetRequiredService(); + + var dbtransaction = unitOfWork.Options.IsolationLevel.HasValue + ? dbContext.Database.BeginTransaction(unitOfWork.Options.IsolationLevel.Value) + : dbContext.Database.BeginTransaction(); + + unitOfWork.AddTransactionApi( + transactionApiKey, + new EfCoreTransactionApi( + dbtransaction, + dbContext + ) + ); + + return dbContext; + } + else + { + DbContextCreationContext.Current.ExistingConnection = activeTransaction.DbContextTransaction.GetDbTransaction().Connection; + + var dbContext = unitOfWork.ServiceProvider.GetRequiredService(); + + if (dbContext.HasRelationalTransactionManager()) + { + dbContext.Database.UseTransaction(activeTransaction.DbContextTransaction.GetDbTransaction()); + } + else + { + dbContext.Database.BeginTransaction(); //TODO: Why not using the new created transaction? + } + + activeTransaction.AttendedDbContexts.Add(dbContext); + + return dbContext; + } + } } } \ No newline at end of file From 9be60fc4c3a4db5eb66ae5b116a37235389a4dd2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 26 Sep 2017 10:04:12 +0300 Subject: [PATCH 16/21] Refactored. --- .../AspNetCore/Mvc/Uow/AbpUowActionFilter.cs | 2 +- src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs | 6 ++-- src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs | 4 +-- .../Volo/Abp/Uow/IUnitOfWorkManager.cs | 6 ++-- ...kStartOptions.cs => IUnitOfWorkOptions.cs} | 2 +- src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs | 12 ++++--- .../Volo/Abp/Uow/UnitOfWorkAttribute.cs | 2 +- .../Volo/Abp/Uow/UnitOfWorkDefaultOptions.cs | 34 +++++++++++++++++++ .../Volo/Abp/Uow/UnitOfWorkManager.cs | 10 +++--- .../Abp/Uow/UnitOfWorkManagerExtensions.cs | 6 ++-- .../Volo/Abp/Uow/UnitOfWorkOptions.cs | 29 ++++++---------- .../Volo/Abp/Uow/UnitOfWorkStartOptions.cs | 27 --------------- 12 files changed, 71 insertions(+), 69 deletions(-) rename src/Volo.Abp/Volo/Abp/Uow/{IUnitOfWorkStartOptions.cs => IUnitOfWorkOptions.cs} (81%) create mode 100644 src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkDefaultOptions.cs delete mode 100644 src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs index 5996724a20..5b005facee 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs @@ -32,7 +32,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow return; } - var options = new UnitOfWorkStartOptions(); + var options = new UnitOfWorkOptions(); unitOfWorkAttr?.SetOptions(options); diff --git a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs index 3683bbd7c4..3c054bbd5d 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs @@ -9,7 +9,7 @@ namespace Volo.Abp.Uow { public Guid Id => _parent.Id; - public IUnitOfWorkStartOptions Options => _parent.Options; + public IUnitOfWorkOptions Options => _parent.Options; public IUnitOfWork Outer => _parent.Outer; @@ -30,9 +30,9 @@ namespace Volo.Abp.Uow _parent.SetOuter(outer); } - public void SetOptions(UnitOfWorkStartOptions options) + public void Initialize(UnitOfWorkOptions options) { - _parent.SetOptions(options); + _parent.Initialize(options); } public event EventHandler Completed; diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs index ba47e431d2..acee44e91d 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs @@ -4,7 +4,7 @@ namespace Volo.Abp.Uow { public interface IUnitOfWork : IBasicUnitOfWork, IDatabaseApiContainer, ITransactionApiContainer { - IUnitOfWorkStartOptions Options { get; } + IUnitOfWorkOptions Options { get; } IUnitOfWork Outer { get; } @@ -14,6 +14,6 @@ namespace Volo.Abp.Uow void SetOuter([CanBeNull] IUnitOfWork outer); - void SetOptions(UnitOfWorkStartOptions options); + void Initialize([NotNull] UnitOfWorkOptions options); } } diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs index 1fc871dd3e..d1f8c534fb 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs @@ -8,13 +8,13 @@ namespace Volo.Abp.Uow IUnitOfWork Current { get; } [NotNull] - IBasicUnitOfWork Begin([NotNull] UnitOfWorkStartOptions options, bool requiresNew = false); + IBasicUnitOfWork Begin([NotNull] UnitOfWorkOptions options, bool requiresNew = false); [NotNull] IBasicUnitOfWork Reserve([NotNull] string reservationName, bool requiresNew = false); - void BeginReserved([NotNull] string reservationName, [NotNull] UnitOfWorkStartOptions options); + void BeginReserved([NotNull] string reservationName, [NotNull] UnitOfWorkOptions options); - bool TryBeginReserved([NotNull] string reservationName, [NotNull] UnitOfWorkStartOptions options); + bool TryBeginReserved([NotNull] string reservationName, [NotNull] UnitOfWorkOptions options); } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkStartOptions.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkOptions.cs similarity index 81% rename from src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkStartOptions.cs rename to src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkOptions.cs index 9fe3bc1084..b79b7112b8 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkStartOptions.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkOptions.cs @@ -3,7 +3,7 @@ using System.Data; namespace Volo.Abp.Uow { - public interface IUnitOfWorkStartOptions + public interface IUnitOfWorkOptions { bool IsTransactional { get; } diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs index 810a94ec91..d6441ab01f 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs @@ -11,7 +11,7 @@ namespace Volo.Abp.Uow { public Guid Id { get; } = Guid.NewGuid(); - public IUnitOfWorkStartOptions Options { get; private set; } + public IUnitOfWorkOptions Options { get; private set; } public IUnitOfWork Outer { get; private set; } @@ -27,13 +27,13 @@ namespace Volo.Abp.Uow private readonly Dictionary _databaseApis; private readonly Dictionary _transactionApis; - private readonly UnitOfWorkOptions _defaultOptions; + private readonly UnitOfWorkDefaultOptions _defaultOptions; private Exception _exception; private bool _isCompleted; private bool _isDisposed; - public UnitOfWork(IServiceProvider serviceProvider, IOptions options) + public UnitOfWork(IServiceProvider serviceProvider, IOptions options) { ServiceProvider = serviceProvider; _defaultOptions = options.Value; @@ -42,11 +42,13 @@ namespace Volo.Abp.Uow _transactionApis = new Dictionary(); } - public void SetOptions(UnitOfWorkStartOptions options) + public void Initialize(UnitOfWorkOptions options) { + Check.NotNull(options, nameof(options)); + if (Options != null) { - throw new AbpException("Options must be set only once!"); + throw new AbpException("This unit of work is already initialized before!"); } Options = _defaultOptions.Normalize(options.Clone()); diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkAttribute.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkAttribute.cs index 7c58fccfc0..351eefd859 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkAttribute.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkAttribute.cs @@ -37,7 +37,7 @@ namespace Volo.Abp.Uow /// public bool IsDisabled { get; set; } - public virtual void SetOptions(UnitOfWorkStartOptions options) + public virtual void SetOptions(UnitOfWorkOptions options) { if (IsTransactional.HasValue) { diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkDefaultOptions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkDefaultOptions.cs new file mode 100644 index 0000000000..7296eba767 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkDefaultOptions.cs @@ -0,0 +1,34 @@ +using System; +using System.Data; + +namespace Volo.Abp.Uow +{ + //TODO: Implement default options! + + /// + /// Global (default) unit of work options + /// + public class UnitOfWorkDefaultOptions + { + public UnitOfWorkTransactionBehavior TransactionBehavior { get; set; } + + public IsolationLevel? IsolationLevel { get; set; } + + public TimeSpan? Timeout { get; set; } + + internal UnitOfWorkOptions Normalize(UnitOfWorkOptions options) + { + if (options.IsolationLevel == null) + { + options.IsolationLevel = IsolationLevel; + } + + if (options.Timeout == null) + { + options.Timeout = Timeout; + } + + return options; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs index 50bb080583..077481ffa1 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs @@ -19,7 +19,7 @@ namespace Volo.Abp.Uow _ambientUnitOfWork = ambientUnitOfWork; } - public IBasicUnitOfWork Begin(UnitOfWorkStartOptions options, bool requiresNew = false) + public IBasicUnitOfWork Begin(UnitOfWorkOptions options, bool requiresNew = false) { Check.NotNull(options, nameof(options)); @@ -29,7 +29,7 @@ namespace Volo.Abp.Uow } var unitOfWork = CreateNewUnitOfWork(); - unitOfWork.SetOptions(options); + unitOfWork.Initialize(options); return unitOfWork; } @@ -53,7 +53,7 @@ namespace Volo.Abp.Uow return unitOfWork; } - public void BeginReserved(string reservationName, UnitOfWorkStartOptions options) + public void BeginReserved(string reservationName, UnitOfWorkOptions options) { if (!TryBeginReserved(reservationName, options)) { @@ -61,7 +61,7 @@ namespace Volo.Abp.Uow } } - public bool TryBeginReserved(string reservationName, UnitOfWorkStartOptions options) + public bool TryBeginReserved(string reservationName, UnitOfWorkOptions options) { Check.NotNull(reservationName, nameof(reservationName)); @@ -79,7 +79,7 @@ namespace Volo.Abp.Uow } uow.IsReserved = false; - uow.SetOptions(options); + uow.Initialize(options); return true; } diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs index 8e4aaae90d..3a9f949017 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs @@ -9,7 +9,7 @@ namespace Volo.Abp.Uow { Check.NotNull(unitOfWorkManager, nameof(unitOfWorkManager)); - return unitOfWorkManager.Begin(new UnitOfWorkStartOptions(), requiresNew); + return unitOfWorkManager.Begin(new UnitOfWorkOptions(), requiresNew); } public static void BeginReserved([NotNull] this IUnitOfWorkManager unitOfWorkManager, [NotNull] string reservationName) @@ -17,7 +17,7 @@ namespace Volo.Abp.Uow Check.NotNull(unitOfWorkManager, nameof(unitOfWorkManager)); Check.NotNull(reservationName, nameof(reservationName)); - unitOfWorkManager.BeginReserved(reservationName, new UnitOfWorkStartOptions()); + unitOfWorkManager.BeginReserved(reservationName, new UnitOfWorkOptions()); } public static void TryBeginReserved([NotNull] this IUnitOfWorkManager unitOfWorkManager, [NotNull] string reservationName) @@ -25,7 +25,7 @@ namespace Volo.Abp.Uow Check.NotNull(unitOfWorkManager, nameof(unitOfWorkManager)); Check.NotNull(reservationName, nameof(reservationName)); - unitOfWorkManager.TryBeginReserved(reservationName, new UnitOfWorkStartOptions()); + unitOfWorkManager.TryBeginReserved(reservationName, new UnitOfWorkOptions()); } } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkOptions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkOptions.cs index c073c7ad6a..3d433f3738 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkOptions.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkOptions.cs @@ -3,32 +3,25 @@ using System.Data; namespace Volo.Abp.Uow { - //TODO: Implement default options! - - /// - /// Global (default) unit of work options - /// - public class UnitOfWorkOptions + public class UnitOfWorkOptions : IUnitOfWorkOptions { - public UnitOfWorkTransactionBehavior TransactionBehavior { get; set; } + /// + /// Default: false. + /// + public bool IsTransactional { get; set; } public IsolationLevel? IsolationLevel { get; set; } public TimeSpan? Timeout { get; set; } - internal UnitOfWorkStartOptions Normalize(UnitOfWorkStartOptions options) + public UnitOfWorkOptions Clone() { - if (options.IsolationLevel == null) + return new UnitOfWorkOptions { - options.IsolationLevel = IsolationLevel; - } - - if (options.Timeout == null) - { - options.Timeout = Timeout; - } - - return options; + IsTransactional = IsTransactional, + IsolationLevel = IsolationLevel, + Timeout = Timeout + }; } } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs deleted file mode 100644 index ccde083786..0000000000 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkStartOptions.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Data; - -namespace Volo.Abp.Uow -{ - public class UnitOfWorkStartOptions : IUnitOfWorkStartOptions - { - /// - /// Default: false. - /// - public bool IsTransactional { get; set; } - - public IsolationLevel? IsolationLevel { get; set; } - - public TimeSpan? Timeout { get; set; } - - public UnitOfWorkStartOptions Clone() - { - return new UnitOfWorkStartOptions - { - IsTransactional = IsTransactional, - IsolationLevel = IsolationLevel, - Timeout = Timeout - }; - } - } -} \ No newline at end of file From 51f450b2072e96ce923d476f6bb1a19188136951 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 26 Sep 2017 10:31:36 +0300 Subject: [PATCH 17/21] Set options in UOW interceptor and action filter. --- .../AspNetCore/Mvc/Uow/AbpUowActionFilter.cs | 29 ++++++++++++---- .../Volo/Abp/Uow/UnitOfWorkDefaultOptions.cs | 16 +++++++++ src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkHelper.cs | 15 ++++++--- .../Volo/Abp/Uow/UnitOfWorkInterceptor.cs | 33 +++++++++++++++---- .../AbpIdentityApplicationTestModule.cs | 6 ++++ 5 files changed, 82 insertions(+), 17 deletions(-) diff --git a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs index 5b005facee..7a98a7729e 100644 --- a/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs +++ b/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/Uow/AbpUowActionFilter.cs @@ -1,5 +1,8 @@ -using System.Threading.Tasks; +using System; +using System.Net.Http; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.Uow; @@ -10,10 +13,12 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow public const string UnitOfWorkReservationName = "_AbpActionUnitOfWork"; private readonly IUnitOfWorkManager _unitOfWorkManager; + private readonly UnitOfWorkDefaultOptions _defaultOptions; - public AbpUowActionFilter(IUnitOfWorkManager unitOfWorkManager) + public AbpUowActionFilter(IUnitOfWorkManager unitOfWorkManager, IOptions options) { _unitOfWorkManager = unitOfWorkManager; + _defaultOptions = options.Value; } public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) @@ -24,7 +29,8 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow return; } - var unitOfWorkAttr = UnitOfWorkHelper.GetUnitOfWorkAttributeOrNull(context.ActionDescriptor.GetMethodInfo()); + var methodInfo = context.ActionDescriptor.GetMethodInfo(); + var unitOfWorkAttr = UnitOfWorkHelper.GetUnitOfWorkAttributeOrNull(methodInfo); if (unitOfWorkAttr?.IsDisabled == true) { @@ -32,9 +38,7 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow return; } - var options = new UnitOfWorkOptions(); - - unitOfWorkAttr?.SetOptions(options); + var options = CreateOptions(context, unitOfWorkAttr); if (_unitOfWorkManager.TryBeginReserved(UnitOfWorkReservationName, options)) { @@ -51,5 +55,18 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow } } } + + private UnitOfWorkOptions CreateOptions(ActionExecutingContext context, UnitOfWorkAttribute unitOfWorkAttr) + { + var options = new UnitOfWorkOptions(); + + unitOfWorkAttr?.SetOptions(options); + + options.IsTransactional = _defaultOptions.CalculateIsTransactional( + autoValue: !string.Equals(context.HttpContext.Request.Method, HttpMethod.Get.Method, StringComparison.OrdinalIgnoreCase) + ); + + return options; + } } } diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkDefaultOptions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkDefaultOptions.cs index 7296eba767..cb7288549d 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkDefaultOptions.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkDefaultOptions.cs @@ -1,5 +1,6 @@ using System; using System.Data; +using System.Reflection; namespace Volo.Abp.Uow { @@ -30,5 +31,20 @@ namespace Volo.Abp.Uow return options; } + + internal bool CalculateIsTransactional(bool autoValue) + { + switch (TransactionBehavior) + { + case UnitOfWorkTransactionBehavior.Enabled: + return true; + case UnitOfWorkTransactionBehavior.Disabled: + return false; + case UnitOfWorkTransactionBehavior.Auto: + return autoValue; + default: + throw new AbpException("Not implemented TransactionBehavior value: " + TransactionBehavior); + } + } } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkHelper.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkHelper.cs index 4f1a50f429..47d42aa322 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkHelper.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkHelper.cs @@ -1,5 +1,6 @@ using System.Linq; using System.Reflection; +using JetBrains.Annotations; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; @@ -25,13 +26,16 @@ namespace Volo.Abp.Uow return false; } - public static bool IsUnitOfWorkMethod(MethodInfo methodInfo) + public static bool IsUnitOfWorkMethod([NotNull] MethodInfo methodInfo, [CanBeNull] out UnitOfWorkAttribute unitOfWorkAttribute) { + Check.NotNull(methodInfo, nameof(methodInfo)); + //Method declaration var attrs = methodInfo.GetCustomAttributes(true).OfType().ToArray(); if (attrs.Any()) { - return true; + unitOfWorkAttribute = attrs.First(); + return !unitOfWorkAttribute.IsDisabled; } if (methodInfo.DeclaringType != null) @@ -40,17 +44,20 @@ namespace Volo.Abp.Uow attrs = methodInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(true).OfType().ToArray(); if (attrs.Any()) { - return true; + unitOfWorkAttribute = attrs.First(); + return !unitOfWorkAttribute.IsDisabled; } - //Conventional classes + //Conventional classes //TODO: Make this extendible to add new conventions! if (typeof(IApplicationService).GetTypeInfo().IsAssignableFrom(methodInfo.DeclaringType) || typeof(IRepository).GetTypeInfo().IsAssignableFrom(methodInfo.DeclaringType)) { + unitOfWorkAttribute = null; return true; } } + unitOfWorkAttribute = null; return false; } diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkInterceptor.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkInterceptor.cs index 1bf813905a..b95173edde 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkInterceptor.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkInterceptor.cs @@ -1,4 +1,8 @@ -using System.Threading.Tasks; +using System; +using System.Net.Http; +using System.Threading.Tasks; +using JetBrains.Annotations; +using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.DynamicProxy; @@ -7,40 +11,55 @@ namespace Volo.Abp.Uow public class UnitOfWorkInterceptor : AbpInterceptor, ITransientDependency { private readonly IUnitOfWorkManager _unitOfWorkManager; + private readonly UnitOfWorkDefaultOptions _defaultOptions; - public UnitOfWorkInterceptor(IUnitOfWorkManager unitOfWorkManager) + public UnitOfWorkInterceptor(IUnitOfWorkManager unitOfWorkManager, IOptions options) { _unitOfWorkManager = unitOfWorkManager; + _defaultOptions = options.Value; } public override void Intercept(IAbpMethodInvocation invocation) { - if (!UnitOfWorkHelper.IsUnitOfWorkMethod(invocation.Method)) + if (!UnitOfWorkHelper.IsUnitOfWorkMethod(invocation.Method, out var unitOfWorkAttribute)) { invocation.Proceed(); return; } - using (var uow = _unitOfWorkManager.Begin()) + using (var uow = _unitOfWorkManager.Begin(CreateOptions(invocation, unitOfWorkAttribute))) { invocation.Proceed(); uow.Complete(); } } - public override async Task InterceptAsync(IAbpMethodInvocation invocation) + public override async Task InterceptAsync(IAbpMethodInvocation invocation) { - if (!UnitOfWorkHelper.IsUnitOfWorkMethod(invocation.Method)) + if (!UnitOfWorkHelper.IsUnitOfWorkMethod(invocation.Method, out var unitOfWorkAttribute)) { await invocation.ProceedAsync(); return; } - using (var uow = _unitOfWorkManager.Begin()) + using (var uow = _unitOfWorkManager.Begin(CreateOptions(invocation, unitOfWorkAttribute))) { await invocation.ProceedAsync(); await uow.CompleteAsync(); } } + + private UnitOfWorkOptions CreateOptions(IAbpMethodInvocation invocation, [CanBeNull] UnitOfWorkAttribute unitOfWorkAttribute) + { + var options = new UnitOfWorkOptions(); + + unitOfWorkAttribute?.SetOptions(options); + + options.IsTransactional = _defaultOptions.CalculateIsTransactional( + autoValue: !invocation.Method.Name.StartsWith("Get", StringComparison.InvariantCultureIgnoreCase) + ); + + return options; + } } } diff --git a/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/AbpIdentityApplicationTestModule.cs b/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/AbpIdentityApplicationTestModule.cs index 6c3e5cd2e8..d560cd69b4 100644 --- a/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/AbpIdentityApplicationTestModule.cs +++ b/test/Volo.Abp.Identity.Application.Tests/Volo/Abp/Identity/AbpIdentityApplicationTestModule.cs @@ -5,6 +5,7 @@ using Volo.Abp.Autofac; using Volo.Abp.EntityFrameworkCore; using Volo.Abp.Identity.EntityFrameworkCore; using Volo.Abp.Modularity; +using Volo.Abp.Uow; namespace Volo.Abp.Identity { @@ -26,6 +27,11 @@ namespace Volo.Abp.Identity context.DbContextOptions.UseInMemoryDatabase(databaseName); }); }); + + services.Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; //EF in-memory database does not support transactions + }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) From 78df9ea118b60af5787a87a1a17a8f7e1a0cada2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 26 Sep 2017 11:44:12 +0300 Subject: [PATCH 18/21] Added unit test for transaction fo EF core. --- Volo.Abp.sln | 9 +++- src/Volo.Abp/Volo/Abp/AbpApplicationBase.cs | 28 ++++++++---- ...pApplicationWithExternalServiceProvider.cs | 8 +--- ...pApplicationWithInternalServiceProvider.cs | 8 +--- src/Volo.Abp/Volo/Abp/AbpKernelModule.cs | 8 ++++ src/Volo.Abp/Volo/Abp/Modularity/AbpModule.cs | 19 +++++++- .../DefaultModuleLifecycleContributer.cs | 21 ++++++++- .../IOnPostApplicationInitialization.cs | 9 ++++ .../IOnPreApplicationInitialization.cs | 9 ++++ .../Abp/Modularity/ModuleLifecycleOptions.cs | 14 ++++++ .../Volo/Abp/Modularity/ModuleManager.cs | 18 ++++++-- .../Volo.Abp.EntityFrameworkCore.Tests.csproj | 30 +++++++++++++ .../AbpEntityFrameworkCoreTestModule.cs | 42 ++++++++++++++++++ .../EntityFrameworkCoreTestBase.cs | 12 ++++++ .../Repositories/Basic_Repository_Tests.cs | 38 ++++++++++++++++ .../EntityFrameworkCore/Transaction_Tests.cs | 43 +++++++++++++++++++ .../EntityFrameworkCore/TestAppDbContext.cs | 17 ++++++++ 17 files changed, 303 insertions(+), 30 deletions(-) create mode 100644 src/Volo.Abp/Volo/Abp/Modularity/IOnPostApplicationInitialization.cs create mode 100644 src/Volo.Abp/Volo/Abp/Modularity/IOnPreApplicationInitialization.cs create mode 100644 src/Volo.Abp/Volo/Abp/Modularity/ModuleLifecycleOptions.cs create mode 100644 test/Volo.Abp.EntityFrameworkCore.Tests/Volo.Abp.EntityFrameworkCore.Tests.csproj create mode 100644 test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs create mode 100644 test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/EntityFrameworkCoreTestBase.cs create mode 100644 test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Basic_Repository_Tests.cs create mode 100644 test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Transaction_Tests.cs create mode 100644 test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs diff --git a/Volo.Abp.sln b/Volo.Abp.sln index 42fddd577f..ba6b08fe0b 100644 --- a/Volo.Abp.sln +++ b/Volo.Abp.sln @@ -136,7 +136,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.Applicati EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.Domain", "src\Volo.Abp.Identity.Domain\Volo.Abp.Identity.Domain.csproj", "{43D4005C-4F04-4128-937B-52BEAC5A113B}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Identity.Domain.Shared", "src\Volo.Abp.Identity.Domain.Shared\Volo.Abp.Identity.Domain.Shared.csproj", "{DF676F73-3FC9-46CE-909A-2D75E19982AD}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Identity.Domain.Shared", "src\Volo.Abp.Identity.Domain.Shared\Volo.Abp.Identity.Domain.Shared.csproj", "{DF676F73-3FC9-46CE-909A-2D75E19982AD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.EntityFrameworkCore.Tests", "test\Volo.Abp.EntityFrameworkCore.Tests\Volo.Abp.EntityFrameworkCore.Tests.csproj", "{3AF7C7F5-6513-47D4-8DD0-6E1AF14568D8}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -348,6 +350,10 @@ Global {DF676F73-3FC9-46CE-909A-2D75E19982AD}.Debug|Any CPU.Build.0 = Debug|Any CPU {DF676F73-3FC9-46CE-909A-2D75E19982AD}.Release|Any CPU.ActiveCfg = Release|Any CPU {DF676F73-3FC9-46CE-909A-2D75E19982AD}.Release|Any CPU.Build.0 = Release|Any CPU + {3AF7C7F5-6513-47D4-8DD0-6E1AF14568D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3AF7C7F5-6513-47D4-8DD0-6E1AF14568D8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3AF7C7F5-6513-47D4-8DD0-6E1AF14568D8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3AF7C7F5-6513-47D4-8DD0-6E1AF14568D8}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -413,6 +419,7 @@ Global {40E21A35-1C66-4E89-A16E-0475011F7EFD} = {146F561E-C7B8-4166-9383-47E1BC1A2E62} {43D4005C-4F04-4128-937B-52BEAC5A113B} = {1895A5C9-50D4-4568-9A3A-14657E615A5E} {DF676F73-3FC9-46CE-909A-2D75E19982AD} = {1895A5C9-50D4-4568-9A3A-14657E615A5E} + {3AF7C7F5-6513-47D4-8DD0-6E1AF14568D8} = {37087D1B-3693-4E96-983D-A69F210BDE53} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/src/Volo.Abp/Volo/Abp/AbpApplicationBase.cs b/src/Volo.Abp/Volo/Abp/AbpApplicationBase.cs index a969b2ab97..d64e415e7f 100644 --- a/src/Volo.Abp/Volo/Abp/AbpApplicationBase.cs +++ b/src/Volo.Abp/Volo/Abp/AbpApplicationBase.cs @@ -39,6 +39,18 @@ namespace Volo.Abp Modules = LoadModules(services, options); } + public virtual void Shutdown() + { + ServiceProvider + .GetRequiredService() + .ShutdownModules(new ApplicationShutdownContext()); + } + + public virtual void Dispose() + { + + } + private IReadOnlyList LoadModules(IServiceCollection services, AbpApplicationCreationOptions options) { return services @@ -50,16 +62,14 @@ namespace Volo.Abp ); } - public virtual void Shutdown() - { - ServiceProvider - .GetRequiredService() - .ShutdownModules(new ApplicationShutdownContext()); - } - - public virtual void Dispose() + protected virtual void InitializeModules() { - + using (var scope = ServiceProvider.CreateScope()) + { + ServiceProvider + .GetRequiredService() + .InitializeModules(new ApplicationInitializationContext(scope.ServiceProvider)); + } } } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/AbpApplicationWithExternalServiceProvider.cs b/src/Volo.Abp/Volo/Abp/AbpApplicationWithExternalServiceProvider.cs index 6656baaa5b..ea846809fa 100644 --- a/src/Volo.Abp/Volo/Abp/AbpApplicationWithExternalServiceProvider.cs +++ b/src/Volo.Abp/Volo/Abp/AbpApplicationWithExternalServiceProvider.cs @@ -1,7 +1,6 @@ using System; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Modularity; namespace Volo.Abp { @@ -25,12 +24,7 @@ namespace Volo.Abp ServiceProvider = serviceProvider; - using (var scope = ServiceProvider.CreateScope()) - { - ServiceProvider - .GetRequiredService() - .InitializeModules(new ApplicationInitializationContext(scope.ServiceProvider)); - } + InitializeModules(); } } } diff --git a/src/Volo.Abp/Volo/Abp/AbpApplicationWithInternalServiceProvider.cs b/src/Volo.Abp/Volo/Abp/AbpApplicationWithInternalServiceProvider.cs index 82c1718dd7..da27610c4a 100644 --- a/src/Volo.Abp/Volo/Abp/AbpApplicationWithInternalServiceProvider.cs +++ b/src/Volo.Abp/Volo/Abp/AbpApplicationWithInternalServiceProvider.cs @@ -1,7 +1,6 @@ using System; using JetBrains.Annotations; using Microsoft.Extensions.DependencyInjection; -using Volo.Abp.Modularity; namespace Volo.Abp { @@ -37,12 +36,7 @@ namespace Volo.Abp ServiceScope = Services.BuildServiceProviderFromFactory().CreateScope(); ServiceProvider = ServiceScope.ServiceProvider; - using (var scope = ServiceProvider.CreateScope()) - { - ServiceProvider - .GetRequiredService() - .InitializeModules(new ApplicationInitializationContext(scope.ServiceProvider)); - } + InitializeModules(); } public override void Dispose() diff --git a/src/Volo.Abp/Volo/Abp/AbpKernelModule.cs b/src/Volo.Abp/Volo/Abp/AbpKernelModule.cs index 702155a3e1..3f3e42aadf 100644 --- a/src/Volo.Abp/Volo/Abp/AbpKernelModule.cs +++ b/src/Volo.Abp/Volo/Abp/AbpKernelModule.cs @@ -37,6 +37,14 @@ namespace Volo.Abp services.AddAssemblyOf(); services.TryAddObjectAccessor(); + + services.Configure(options => + { + options.Contributers.Add(); + options.Contributers.Add(); + options.Contributers.Add(); + options.Contributers.Add(); + }); } public override void OnApplicationInitialization(ApplicationInitializationContext context) diff --git a/src/Volo.Abp/Volo/Abp/Modularity/AbpModule.cs b/src/Volo.Abp/Volo/Abp/Modularity/AbpModule.cs index fab912f73e..11f2cb3f34 100644 --- a/src/Volo.Abp/Volo/Abp/Modularity/AbpModule.cs +++ b/src/Volo.Abp/Volo/Abp/Modularity/AbpModule.cs @@ -4,7 +4,14 @@ using Microsoft.Extensions.DependencyInjection; namespace Volo.Abp.Modularity { - public abstract class AbpModule : IAbpModule, IOnApplicationInitialization, IOnApplicationShutdown, IPreConfigureServices, IPostConfigureServices + public abstract class AbpModule : + IAbpModule, + IOnPreApplicationInitialization, + IOnApplicationInitialization, + IOnPostApplicationInitialization, + IOnApplicationShutdown, + IPreConfigureServices, + IPostConfigureServices { public virtual void PreConfigureServices(IServiceCollection services) { @@ -21,11 +28,21 @@ namespace Volo.Abp.Modularity } + public virtual void OnPreApplicationInitialization(ApplicationInitializationContext context) + { + + } + public virtual void OnApplicationInitialization(ApplicationInitializationContext context) { } + public virtual void OnPostApplicationInitialization(ApplicationInitializationContext context) + { + + } + public virtual void OnApplicationShutdown(ApplicationShutdownContext context) { diff --git a/src/Volo.Abp/Volo/Abp/Modularity/DefaultModuleLifecycleContributer.cs b/src/Volo.Abp/Volo/Abp/Modularity/DefaultModuleLifecycleContributer.cs index 01d7181cbd..063fe0cf12 100644 --- a/src/Volo.Abp/Volo/Abp/Modularity/DefaultModuleLifecycleContributer.cs +++ b/src/Volo.Abp/Volo/Abp/Modularity/DefaultModuleLifecycleContributer.cs @@ -1,15 +1,34 @@ namespace Volo.Abp.Modularity { - public class DefaultModuleLifecycleContributer : ModuleLifecycleContributerBase + public class OnApplicationInitializationModuleLifecycleContributer : ModuleLifecycleContributerBase { public override void Initialize(ApplicationInitializationContext context, IAbpModule module) { (module as IOnApplicationInitialization)?.OnApplicationInitialization(context); } + } + public class OnApplicationShutdownModuleLifecycleContributer : ModuleLifecycleContributerBase + { public override void Shutdown(ApplicationShutdownContext context, IAbpModule module) { (module as IOnApplicationShutdown)?.OnApplicationShutdown(context); } } + + public class OnPreApplicationInitializationModuleLifecycleContributer : ModuleLifecycleContributerBase + { + public override void Initialize(ApplicationInitializationContext context, IAbpModule module) + { + (module as IOnPreApplicationInitialization)?.OnPreApplicationInitialization(context); + } + } + + public class OnPostApplicationInitializationModuleLifecycleContributer : ModuleLifecycleContributerBase + { + public override void Initialize(ApplicationInitializationContext context, IAbpModule module) + { + (module as IOnPostApplicationInitialization)?.OnPostApplicationInitialization(context); + } + } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Modularity/IOnPostApplicationInitialization.cs b/src/Volo.Abp/Volo/Abp/Modularity/IOnPostApplicationInitialization.cs new file mode 100644 index 0000000000..a63def315a --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Modularity/IOnPostApplicationInitialization.cs @@ -0,0 +1,9 @@ +using JetBrains.Annotations; + +namespace Volo.Abp.Modularity +{ + public interface IOnPostApplicationInitialization + { + void OnPostApplicationInitialization([NotNull] ApplicationInitializationContext context); + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Modularity/IOnPreApplicationInitialization.cs b/src/Volo.Abp/Volo/Abp/Modularity/IOnPreApplicationInitialization.cs new file mode 100644 index 0000000000..a6c30880cd --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Modularity/IOnPreApplicationInitialization.cs @@ -0,0 +1,9 @@ +using JetBrains.Annotations; + +namespace Volo.Abp.Modularity +{ + public interface IOnPreApplicationInitialization + { + void OnPreApplicationInitialization([NotNull] ApplicationInitializationContext context); + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Modularity/ModuleLifecycleOptions.cs b/src/Volo.Abp/Volo/Abp/Modularity/ModuleLifecycleOptions.cs new file mode 100644 index 0000000000..75f53357b4 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Modularity/ModuleLifecycleOptions.cs @@ -0,0 +1,14 @@ +using Volo.Abp.Collections; + +namespace Volo.Abp.Modularity +{ + public class ModuleLifecycleOptions + { + public ITypeList Contributers { get; } + + public ModuleLifecycleOptions() + { + Contributers = new TypeList(); + } + } +} diff --git a/src/Volo.Abp/Volo/Abp/Modularity/ModuleManager.cs b/src/Volo.Abp/Volo/Abp/Modularity/ModuleManager.cs index 5644693da1..005fe09cff 100644 --- a/src/Volo.Abp/Volo/Abp/Modularity/ModuleManager.cs +++ b/src/Volo.Abp/Volo/Abp/Modularity/ModuleManager.cs @@ -1,5 +1,9 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; namespace Volo.Abp.Modularity @@ -12,12 +16,18 @@ namespace Volo.Abp.Modularity public ModuleManager( IModuleContainer moduleContainer, - IEnumerable lifecycleContributers, - ILogger logger) + ILogger logger, + IOptions options, + IServiceProvider serviceProvider) { _moduleContainer = moduleContainer; - _lifecycleContributers = lifecycleContributers; _logger = logger; + + _lifecycleContributers = options.Value + .Contributers + .Select(serviceProvider.GetRequiredService) + .Cast() + .ToArray(); } public void InitializeModules(ApplicationInitializationContext context) diff --git a/test/Volo.Abp.EntityFrameworkCore.Tests/Volo.Abp.EntityFrameworkCore.Tests.csproj b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo.Abp.EntityFrameworkCore.Tests.csproj new file mode 100644 index 0000000000..4186fa8882 --- /dev/null +++ b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo.Abp.EntityFrameworkCore.Tests.csproj @@ -0,0 +1,30 @@ + + + + netcoreapp2.0 + Volo.Abp.EntityFrameworkCore.Tests + Volo.Abp.EntityFrameworkCore.Tests + true + false + false + false + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs new file mode 100644 index 0000000000..7f406993c9 --- /dev/null +++ b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs @@ -0,0 +1,42 @@ +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Autofac; +using Volo.Abp.Modularity; +using Volo.Abp.TestApp; +using Volo.Abp.TestApp.EntityFrameworkCore; + +namespace Volo.Abp.EntityFrameworkCore +{ + [DependsOn(typeof(AbpEntityFrameworkCoreModule))] + [DependsOn(typeof(TestAppModule))] + [DependsOn(typeof(AbpAutofacModule))] + public class AbpEntityFrameworkCoreTestModule : AbpModule + { + public override void ConfigureServices(IServiceCollection services) + { + services.AddAssemblyOf(); + + services.AddAbpDbContext(options => + { + options.WithDefaultRepositories(); + }); + + var inMemorySqlite = new SqliteConnection("Data Source=:memory:"); + inMemorySqlite.Open(); + + services.Configure(options => + { + options.Configure(context => + { + context.DbContextOptions.UseSqlite(inMemorySqlite); + }); + }); + } + + public override void OnPreApplicationInitialization(ApplicationInitializationContext context) + { + context.ServiceProvider.GetRequiredService().Database.EnsureCreated(); + } + } +} diff --git a/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/EntityFrameworkCoreTestBase.cs b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/EntityFrameworkCoreTestBase.cs new file mode 100644 index 0000000000..b773962a16 --- /dev/null +++ b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/EntityFrameworkCoreTestBase.cs @@ -0,0 +1,12 @@ +using Volo.Abp.TestBase; + +namespace Volo.Abp.EntityFrameworkCore +{ + public abstract class EntityFrameworkCoreTestBase : AbpIntegratedTest + { + protected override void SetAbpApplicationCreationOptions(AbpApplicationCreationOptions options) + { + options.UseAutofac(); + } + } +} diff --git a/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Basic_Repository_Tests.cs b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Basic_Repository_Tests.cs new file mode 100644 index 0000000000..ada013b3d9 --- /dev/null +++ b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Repositories/Basic_Repository_Tests.cs @@ -0,0 +1,38 @@ +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.TestApp.Domain; +using Xunit; + +namespace Volo.Abp.EntityFrameworkCore.Repositories +{ + public class Basic_Repository_Tests : EntityFrameworkCoreTestBase + { + private readonly IRepository _personRepository; + + public Basic_Repository_Tests() + { + _personRepository = ServiceProvider.GetRequiredService>(); + } + + [Fact] + public void GetList() + { + _personRepository.GetList().Any().ShouldBeTrue(); + } + + [Fact] + public async Task InsertAsync() + { + var personId = Guid.NewGuid(); + + await _personRepository.InsertAsync(new Person(personId, "Adam", 42)); + + var person = await _personRepository.FindAsync(personId); + person.ShouldNotBeNull(); + } + } +} diff --git a/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Transaction_Tests.cs b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Transaction_Tests.cs new file mode 100644 index 0000000000..5c18c9a7ab --- /dev/null +++ b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Transaction_Tests.cs @@ -0,0 +1,43 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Domain.Repositories; +using Volo.Abp.TestApp.Domain; +using Xunit; + +namespace Volo.Abp.EntityFrameworkCore +{ + public class Transaction_Tests : EntityFrameworkCoreTestBase + { + private readonly IRepository _personRepository; + + public Transaction_Tests() + { + _personRepository = ServiceProvider.GetRequiredService>(); + } + + [Fact] + public async Task Should_Rollback_Transaction_When_An_Exception_Is_Thrown() + { + var personId = Guid.NewGuid(); + const string exceptionMessage = "thrown to rollback the transaction!"; + + try + { + await WithUnitOfWorkAsync(async () => + { + await _personRepository.InsertAsync(new Person(personId, "Adam", 42)); + throw new Exception(exceptionMessage); + }); + } + catch (Exception e) when(e.Message == exceptionMessage) + { + + } + + var person = await _personRepository.FindAsync(personId); + person.ShouldBeNull(); + } + } +} diff --git a/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs new file mode 100644 index 0000000000..77373a84d5 --- /dev/null +++ b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs @@ -0,0 +1,17 @@ +using Microsoft.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.TestApp.Domain; + +namespace Volo.Abp.TestApp.EntityFrameworkCore +{ + public class TestAppDbContext : AbpDbContext + { + public DbSet People { get; set; } + + public TestAppDbContext(DbContextOptions options) + : base(options) + { + + } + } +} From ececa994b0f2082efe0a07af6e5734a04cb20400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 26 Sep 2017 13:55:56 +0300 Subject: [PATCH 19/21] Added unit tes to see auto transaction behaviour for get/post requests. --- .../Abp/AspNetCore/App/UnitOfWorkTestController.cs | 11 +++++++++++ .../AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs | 10 +++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/UnitOfWorkTestController.cs b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/UnitOfWorkTestController.cs index d83eccd2d8..4a465e16db 100644 --- a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/UnitOfWorkTestController.cs +++ b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/App/UnitOfWorkTestController.cs @@ -12,6 +12,17 @@ namespace Volo.Abp.AspNetCore.App public ActionResult ActionRequiresUow() { CurrentUnitOfWork.ShouldNotBeNull(); + CurrentUnitOfWork.Options.IsTransactional.ShouldBeFalse(); + + return Content("OK"); + } + + [HttpPost] + [Route("ActionRequiresUowPost")] + public ActionResult ActionRequiresUowPost() + { + CurrentUnitOfWork.ShouldNotBeNull(); + CurrentUnitOfWork.Options.IsTransactional.ShouldBeTrue(); return Content("OK"); } diff --git a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs index b97c1300ba..abd666e3f5 100644 --- a/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs +++ b/test/Volo.Abp.AspNetCore.Mvc.Tests/Volo/Abp/AspNetCore/Mvc/Uow/UnitOfWorkMiddleware_Tests.cs @@ -1,4 +1,5 @@ using System.Threading.Tasks; +using Shouldly; using Xunit; namespace Volo.Abp.AspNetCore.Mvc.Uow @@ -6,9 +7,16 @@ namespace Volo.Abp.AspNetCore.Mvc.Uow public class UnitOfWorkMiddleware_Tests : AspNetCoreMvcTestBase { [Fact] - public async Task ActionRequiresUow() + public async Task Get_Actions_Should_Not_Be_Transactional() { await GetResponseAsStringAsync("/api/unitofwork-test/ActionRequiresUow"); } + + [Fact] + public async Task Non_Get_Actions_Should_Be_Transactional() + { + var result = await Client.PostAsync("/api/unitofwork-test/ActionRequiresUowPost", null); + result.IsSuccessStatusCode.ShouldBeTrue(); + } } } From 1a6778b4c6fef2435300e9ec4a3bfd3711d566e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 26 Sep 2017 14:05:45 +0300 Subject: [PATCH 20/21] Removed IBasicUnitOfWork --- src/AbpDesk/Web_PlugIns/AbpDesk.MongoBlog.dll | Bin 15872 -> 15872 bytes src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs | 37 ++++++++---------- src/Volo.Abp/Volo/Abp/Uow/IBasicUnitOfWork.cs | 26 ------------ src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs | 29 ++++++++++++-- .../Volo/Abp/Uow/IUnitOfWorkManager.cs | 4 +- src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs | 10 +++++ .../Volo/Abp/Uow/UnitOfWorkFailedEventArgs.cs | 6 +-- .../Volo/Abp/Uow/UnitOfWorkManager.cs | 9 ++--- .../Abp/Uow/UnitOfWorkManagerExtensions.cs | 2 +- 9 files changed, 61 insertions(+), 62 deletions(-) delete mode 100644 src/Volo.Abp/Volo/Abp/Uow/IBasicUnitOfWork.cs diff --git a/src/AbpDesk/Web_PlugIns/AbpDesk.MongoBlog.dll b/src/AbpDesk/Web_PlugIns/AbpDesk.MongoBlog.dll index 484411d11ecce57e134ddd62f903e5a95be1a7ff..8d7194d4e637278377ed7c43e9664c6fd503388f 100644 GIT binary patch delta 1834 zcmYL~Yitx%6vzMP&h9*RcV@TS2lQcq7Ft?bp$fIMB~Yngg9uGQsi`ppMC$_tE2i>T z8nn{XAj|jwDbgAV0V|0tA3!vwq$xB-!Xrd0fdU$_NJIpaEvWI_Ta5GJx95G%%$&Qk zVeN*sTQ+&(&fOi+cQU9ny7x)C>JsI8%WHrVx4O8%t)*O=;Wkl((p?gX_KC3oVfYHf z#)x@(VPz1=u4!0Rzx=sZrQ=i@5ZBnYum;C7fe_oWXD_U(Pla99`M_KKWJYvhx-I|p5DSH5{Fgm#L0J?o)toO?bJM})p zhf(|@a?tMV_F!81liMo~u7H4oadq}DvL%dHNi-#5uWSJUz9e>Q#< zI&Ld1I`Wv?63CnVvvZ9M6lKe-ZtI2qAhtHd=)o6s^V1&|otW)M2S`ee|`TE^@J1;0}Bw(hp$r>GYy0RrB zW1OsUnM&L~JAKA6Ymt_M{2XcHIL=DJgG$>7A3kEAJmEn;)PxuLFcLbQ{~55|`A&*k z?gS|ARyh+R8;wxNgDa*CIj%_tYy&Uz?Q~hh1IY%dOds-?ArvW2Rjg2~VkYy6kltFw z#ftTcD-|0Rn-n{kY1qru(Hl`e{V3rb?#Cf*G=`KVfx4_5tYn7J!7N0JvIkJ?nS}td zoO#S;Mjevaz8qat;WQva$-R{9c@L-Q8~ronQQZ82pWTnzC1$%A#2`%z{)L-#%`m8l zBGPS%pNtWdAbi1mrsI@Ro;YaFrYb7-)X-Rrcb3v*?58zULC<(}OcnPtQ+X^MwbbBi zqSLscwNjOM-`qmm@wc;$Dnys{F_o*VMisdocg2^~#PU&!DoefC6)}3L5_`DxW9t4Y zF_rhGQJipoQ$yUQ!z#1@ow^|k@QLmhO+v=xiO=njIIXf;M4OW@qN1NBiLt1sVzB^w ztZGq*UNS@g2k2Rr2W-FS5C@$ITQ-@^%t70`hQ1J;vBuC6O12_dWcH*7A!Z_(fkR4~TQkk1miaa(YF@}9 zoyEKzS8AHl!e%j@-aph4+-s_6%7;yx{|MT=ylQRgALxiU<9vddBJq7F{{i7TAjUSI!8$%zUB#~dB! zM3NKcLGpJVNJoVsOB2bX>lae~)|TUr2*{VR%aqiEk^{;+$Jon_&mrmK^YF_GyYybe zhirZkx#)5ZaO7#@JG}rCI;{>Wz%4%Z2*Os8T80!w;^iXVn4MOdTj5X3n(N%rzkqsd>&04=G!JymapJXhu{t!B<-3$KwnuL|55f+Z&-9tNi zoO?BaZlxuV?JHsJ_Fe5CqfFD_87m`WsGus%gPr4~);>Dn&JJYUGs!!uTefrpiru(sLrG!s+r`nv2ztj$!~>!TA~%feix z$!>#z$L?EcT;lz5_`bx~c`j8a;{I%TfWg2=w>JL@*v`9WDDj7ODC*66e|lx-w3 zC&(HXXtLX5XG}S1&DBy-kSlE*$62X(NNF43!w&Yz8$Nf@GQ*2ReQ|x4>V;@sTGO9k#U^4IU8645ZU{G1Yl(2HKj2S{Fvk04%J%CcrbOey+ z)G=Q&=3xZem*6-}cUB@($w?aN*@`~;QvVeBG-UqBXZN#qp1DU1V1T9u|HgH?W*AgL zQR%kCfH8`~A{Z=SrZ^RpFOJ$ZR7<6v1{#kdXCY0*ep*Y_^sGlmxwxO1#$)Mdq848p z_2HVfnQFxja|>;U2yUZlk+43XN|n{3BDdp?_>9_E{+eRS(kwoT8cCXlkGb>{>ibPY zIqyx2=yR^BA#T!96AA!G8zAv+}cRMsZZ=@f{VI77u^JesLg%t5y` zTk!7z86to~^gPSI*nZI|jyh4cylu8KuiD=#e83r@@t0V zMfcC}Ioj)tsJXCs{;!fFZO-z{>4_98JSh4XhF*AK5 diff --git a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs index 3c054bbd5d..b476bc5996 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs @@ -13,27 +13,9 @@ namespace Volo.Abp.Uow public IUnitOfWork Outer => _parent.Outer; - public bool IsReserved - { - get => _parent.IsReserved; - set => _parent.IsReserved = value; - } - - public string ReservationName - { - get => _parent.ReservationName; - set => _parent.ReservationName = value; - } - - public void SetOuter(IUnitOfWork outer) - { - _parent.SetOuter(outer); - } + public bool IsReserved => _parent.IsReserved; - public void Initialize(UnitOfWorkOptions options) - { - _parent.Initialize(options); - } + public string ReservationName => _parent.ReservationName; public event EventHandler Completed; public event EventHandler Failed; @@ -54,6 +36,21 @@ namespace Volo.Abp.Uow _parent.Disposed += (sender, args) => { Disposed.InvokeSafely(sender, args); }; } + public void SetOuter(IUnitOfWork outer) + { + _parent.SetOuter(outer); + } + + public void Initialize(UnitOfWorkOptions options) + { + _parent.Initialize(options); + } + + public void Reserve(string reservationName) + { + _parent.Reserve(reservationName); + } + public void SaveChanges() { _parent.SaveChanges(); diff --git a/src/Volo.Abp/Volo/Abp/Uow/IBasicUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/IBasicUnitOfWork.cs deleted file mode 100644 index 11d26a7640..0000000000 --- a/src/Volo.Abp/Volo/Abp/Uow/IBasicUnitOfWork.cs +++ /dev/null @@ -1,26 +0,0 @@ -using System; -using System.Threading; -using System.Threading.Tasks; - -namespace Volo.Abp.Uow -{ - //TODO: Find a better naming :( - public interface IBasicUnitOfWork : IDisposable - { - Guid Id { get; } - - event EventHandler Completed; - - event EventHandler Failed; - - event EventHandler Disposed; - - void SaveChanges(); - - Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); - - void Complete(); - - Task CompleteAsync(CancellationToken cancellationToken = default(CancellationToken)); - } -} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs index acee44e91d..f3436023c9 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs @@ -1,19 +1,40 @@ -using JetBrains.Annotations; +using System; +using System.Threading; +using System.Threading.Tasks; +using JetBrains.Annotations; namespace Volo.Abp.Uow { - public interface IUnitOfWork : IBasicUnitOfWork, IDatabaseApiContainer, ITransactionApiContainer + public interface IUnitOfWork : IDatabaseApiContainer, ITransactionApiContainer, IDisposable { + Guid Id { get; } + + event EventHandler Completed; + + event EventHandler Failed; + + event EventHandler Disposed; + IUnitOfWorkOptions Options { get; } IUnitOfWork Outer { get; } - bool IsReserved { get; set; } + bool IsReserved { get; } - string ReservationName { get; set; } + string ReservationName { get; } void SetOuter([CanBeNull] IUnitOfWork outer); void Initialize([NotNull] UnitOfWorkOptions options); + + void Reserve([NotNull] string reservationName); + + void SaveChanges(); + + Task SaveChangesAsync(CancellationToken cancellationToken = default(CancellationToken)); + + void Complete(); + + Task CompleteAsync(CancellationToken cancellationToken = default(CancellationToken)); } } diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs index d1f8c534fb..264390fbbe 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWorkManager.cs @@ -8,10 +8,10 @@ namespace Volo.Abp.Uow IUnitOfWork Current { get; } [NotNull] - IBasicUnitOfWork Begin([NotNull] UnitOfWorkOptions options, bool requiresNew = false); + IUnitOfWork Begin([NotNull] UnitOfWorkOptions options, bool requiresNew = false); [NotNull] - IBasicUnitOfWork Reserve([NotNull] string reservationName, bool requiresNew = false); + IUnitOfWork Reserve([NotNull] string reservationName, bool requiresNew = false); void BeginReserved([NotNull] string reservationName, [NotNull] UnitOfWorkOptions options); diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs index d6441ab01f..b481bd5da4 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using JetBrains.Annotations; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; @@ -52,6 +53,15 @@ namespace Volo.Abp.Uow } Options = _defaultOptions.Normalize(options.Clone()); + IsReserved = false; + } + + public void Reserve(string reservationName) + { + Check.NotNull(reservationName, nameof(reservationName)); + + ReservationName = reservationName; + IsReserved = true; } public void SetOuter(IUnitOfWork outer) diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkFailedEventArgs.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkFailedEventArgs.cs index 870f125f4f..e17dfa4bc0 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkFailedEventArgs.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkFailedEventArgs.cs @@ -9,12 +9,12 @@ namespace Volo.Abp.Uow public class UnitOfWorkFailedEventArgs : EventArgs { /// - /// Exception that caused failure. This is set only if an error occured during . - /// Can be null if there is no exception, but is not called. + /// Exception that caused failure. This is set only if an error occured during . + /// Can be null if there is no exception, but is not called. /// Can be null if another exception occurred during the UOW. /// [CanBeNull] - public Exception Exception { get; private set; } + public Exception Exception { get; } /// /// Creates a new object. diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs index 077481ffa1..447a6c182e 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManager.cs @@ -19,7 +19,7 @@ namespace Volo.Abp.Uow _ambientUnitOfWork = ambientUnitOfWork; } - public IBasicUnitOfWork Begin(UnitOfWorkOptions options, bool requiresNew = false) + public IUnitOfWork Begin(UnitOfWorkOptions options, bool requiresNew = false) { Check.NotNull(options, nameof(options)); @@ -34,7 +34,7 @@ namespace Volo.Abp.Uow return unitOfWork; } - public IBasicUnitOfWork Reserve(string reservationName, bool requiresNew = false) + public IUnitOfWork Reserve(string reservationName, bool requiresNew = false) { Check.NotNull(reservationName, nameof(reservationName)); @@ -46,9 +46,7 @@ namespace Volo.Abp.Uow } var unitOfWork = CreateNewUnitOfWork(); - - unitOfWork.IsReserved = true; - unitOfWork.ReservationName = reservationName; + unitOfWork.Reserve(reservationName); return unitOfWork; } @@ -78,7 +76,6 @@ namespace Volo.Abp.Uow return false; } - uow.IsReserved = false; uow.Initialize(options); return true; diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs index 3a9f949017..c9f888f275 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkManagerExtensions.cs @@ -5,7 +5,7 @@ namespace Volo.Abp.Uow public static class UnitOfWorkManagerExtensions { [NotNull] - public static IBasicUnitOfWork Begin([NotNull] this IUnitOfWorkManager unitOfWorkManager, bool requiresNew = false) + public static IUnitOfWork Begin([NotNull] this IUnitOfWorkManager unitOfWorkManager, bool requiresNew = false) { Check.NotNull(unitOfWorkManager, nameof(unitOfWorkManager)); From 275a64380bf340da9795ea7296c9a8c6f1d63378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Tue, 26 Sep 2017 14:39:33 +0300 Subject: [PATCH 21/21] Added unit tests. --- .../EfCoreTransactionApi.cs | 14 +++- src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs | 20 +++-- .../Volo/Abp/Uow/ISupportsRollback.cs | 12 +++ src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs | 8 +- src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs | 73 +++++++++++++++++-- .../Volo/Abp/Uow/UnitOfWorkEventArgs.cs | 20 +++++ .../Volo/Abp/Uow/UnitOfWorkFailedEventArgs.cs | 12 ++- .../EntityFrameworkCore/Transaction_Tests.cs | 21 +++++- .../Volo/Abp/Uow/UnitOfWork_Events_Tests.cs | 31 ++++++++ 9 files changed, 191 insertions(+), 20 deletions(-) create mode 100644 src/Volo.Abp/Volo/Abp/Uow/ISupportsRollback.cs create mode 100644 src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkEventArgs.cs diff --git a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs index f5016f7bda..a715d8c150 100644 --- a/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs +++ b/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/Uow/EntityFrameworkCore/EfCoreTransactionApi.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Storage; @@ -6,7 +7,7 @@ using Volo.Abp.EntityFrameworkCore; namespace Volo.Abp.Uow.EntityFrameworkCore { - public class EfCoreTransactionApi : ITransactionApi + public class EfCoreTransactionApi : ITransactionApi, ISupportsRollback { public IDbContextTransaction DbContextTransaction { get; } public DbContext StarterDbContext { get; } @@ -44,5 +45,16 @@ namespace Volo.Abp.Uow.EntityFrameworkCore { DbContextTransaction.Dispose(); } + + public void Rollback() + { + DbContextTransaction.Rollback(); + } + + public Task RollbackAsync(CancellationToken cancellationToken) + { + DbContextTransaction.Rollback(); + return Task.CompletedTask; + } } } \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs index b476bc5996..4fc789c6ae 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/ChildUnitOfWork.cs @@ -17,9 +17,9 @@ namespace Volo.Abp.Uow public string ReservationName => _parent.ReservationName; - public event EventHandler Completed; + public event EventHandler Completed; public event EventHandler Failed; - public event EventHandler Disposed; + public event EventHandler Disposed; public IServiceProvider ServiceProvider => _parent.ServiceProvider; @@ -60,17 +60,27 @@ namespace Volo.Abp.Uow { return _parent.SaveChangesAsync(cancellationToken); } - + public void Complete() { - + } public Task CompleteAsync(CancellationToken cancellationToken = default(CancellationToken)) { return Task.CompletedTask; } - + + public void Rollback() + { + _parent.Rollback(); + } + + public Task RollbackAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + return _parent.RollbackAsync(cancellationToken); + } + public IDatabaseApi FindDatabaseApi(string key) { return _parent.FindDatabaseApi(key); diff --git a/src/Volo.Abp/Volo/Abp/Uow/ISupportsRollback.cs b/src/Volo.Abp/Volo/Abp/Uow/ISupportsRollback.cs new file mode 100644 index 0000000000..4c211d652d --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Uow/ISupportsRollback.cs @@ -0,0 +1,12 @@ +using System.Threading; +using System.Threading.Tasks; + +namespace Volo.Abp.Uow +{ + public interface ISupportsRollback + { + void Rollback(); + + Task RollbackAsync(CancellationToken cancellationToken); + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs index f3436023c9..79089cd8fc 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/IUnitOfWork.cs @@ -9,11 +9,11 @@ namespace Volo.Abp.Uow { Guid Id { get; } - event EventHandler Completed; + event EventHandler Completed; event EventHandler Failed; - event EventHandler Disposed; + event EventHandler Disposed; IUnitOfWorkOptions Options { get; } @@ -36,5 +36,9 @@ namespace Volo.Abp.Uow void Complete(); Task CompleteAsync(CancellationToken cancellationToken = default(CancellationToken)); + + void Rollback(); + + Task RollbackAsync(CancellationToken cancellationToken = default(CancellationToken)); } } diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs index b481bd5da4..aac08f3fa2 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWork.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using JetBrains.Annotations; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; @@ -20,9 +19,9 @@ namespace Volo.Abp.Uow public string ReservationName { get; set; } - public event EventHandler Completed; + public event EventHandler Completed; public event EventHandler Failed; - public event EventHandler Disposed; + public event EventHandler Disposed; public IServiceProvider ServiceProvider { get; } @@ -33,6 +32,7 @@ namespace Volo.Abp.Uow private Exception _exception; private bool _isCompleted; private bool _isDisposed; + private bool _isRolledback; public UnitOfWork(IServiceProvider serviceProvider, IOptions options) { @@ -90,7 +90,13 @@ namespace Volo.Abp.Uow public void Complete() { + if (_isRolledback) + { + return; + } + PreventMultipleComplete(); + try { SaveChanges(); @@ -106,6 +112,11 @@ namespace Volo.Abp.Uow public async Task CompleteAsync(CancellationToken cancellationToken = default(CancellationToken)) { + if (_isRolledback) + { + return; + } + PreventMultipleComplete(); try @@ -121,6 +132,52 @@ namespace Volo.Abp.Uow } } + public void Rollback() + { + if (_isRolledback) + { + return; + } + + _isRolledback = true; + + foreach (var databaseApi in _databaseApis.Values) + { + (databaseApi as ISupportsRollback)?.Rollback(); + } + + foreach (var transactionApi in _transactionApis.Values) + { + (transactionApi as ISupportsRollback)?.Rollback(); + } + } + + public async Task RollbackAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + if (_isRolledback) + { + return; + } + + _isRolledback = true; + + foreach (var databaseApi in _databaseApis.Values) + { + if (databaseApi is ISupportsRollback) + { + await (databaseApi as ISupportsRollback).RollbackAsync(cancellationToken); + } + } + + foreach (var transactionApi in _transactionApis.Values) + { + if (transactionApi is ISupportsRollback) + { + await (transactionApi as ISupportsRollback).RollbackAsync(cancellationToken); + } + } + } + public IDatabaseApi FindDatabaseApi(string key) { return _databaseApis.GetOrDefault(key); @@ -177,17 +234,17 @@ namespace Volo.Abp.Uow protected virtual void OnCompleted() { - Completed.InvokeSafely(this); + Completed.InvokeSafely(this, new UnitOfWorkEventArgs(this)); } - protected virtual void OnFailed(Exception exception) + protected virtual void OnFailed() { - Failed.InvokeSafely(this, new UnitOfWorkFailedEventArgs(exception)); + Failed.InvokeSafely(this, new UnitOfWorkFailedEventArgs(this, _exception, _isRolledback)); } protected virtual void OnDisposed() { - Disposed.InvokeSafely(this); + Disposed.InvokeSafely(this, new UnitOfWorkEventArgs(this)); } public void Dispose() @@ -201,7 +258,7 @@ namespace Volo.Abp.Uow if (!_isCompleted || _exception != null) { - OnFailed(_exception); + OnFailed(); } OnDisposed(); diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkEventArgs.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkEventArgs.cs new file mode 100644 index 0000000000..9ab7c953f9 --- /dev/null +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkEventArgs.cs @@ -0,0 +1,20 @@ +using System; +using JetBrains.Annotations; + +namespace Volo.Abp.Uow +{ + public class UnitOfWorkEventArgs : EventArgs + { + /// + /// Reference to the unit of work related to this event. + /// + public IUnitOfWork UnitOfWork { get; } + + public UnitOfWorkEventArgs([NotNull] IUnitOfWork unitOfWork) + { + Check.NotNull(unitOfWork, nameof(unitOfWork)); + + UnitOfWork = unitOfWork; + } + } +} \ No newline at end of file diff --git a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkFailedEventArgs.cs b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkFailedEventArgs.cs index e17dfa4bc0..4218a9693f 100644 --- a/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkFailedEventArgs.cs +++ b/src/Volo.Abp/Volo/Abp/Uow/UnitOfWorkFailedEventArgs.cs @@ -6,7 +6,7 @@ namespace Volo.Abp.Uow /// /// Used as event arguments on event. /// - public class UnitOfWorkFailedEventArgs : EventArgs + public class UnitOfWorkFailedEventArgs : UnitOfWorkEventArgs { /// /// Exception that caused failure. This is set only if an error occured during . @@ -16,13 +16,19 @@ namespace Volo.Abp.Uow [CanBeNull] public Exception Exception { get; } + /// + /// True, if the unit of work is manually rolled back. + /// + public bool IsRolledback { get; } + /// /// Creates a new object. /// - /// Exception that caused failure - public UnitOfWorkFailedEventArgs([CanBeNull] Exception exception) + public UnitOfWorkFailedEventArgs([NotNull] IUnitOfWork unitOfWork, [CanBeNull] Exception exception, bool isRolledback) + : base(unitOfWork) { Exception = exception; + IsRolledback = isRolledback; } } } diff --git a/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Transaction_Tests.cs b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Transaction_Tests.cs index 5c18c9a7ab..d13a964fc5 100644 --- a/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Transaction_Tests.cs +++ b/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Transaction_Tests.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Shouldly; using Volo.Abp.Domain.Repositories; using Volo.Abp.TestApp.Domain; +using Volo.Abp.Uow; using Xunit; namespace Volo.Abp.EntityFrameworkCore @@ -11,10 +12,12 @@ namespace Volo.Abp.EntityFrameworkCore public class Transaction_Tests : EntityFrameworkCoreTestBase { private readonly IRepository _personRepository; + private readonly IUnitOfWorkManager _unitOfWorkManager; public Transaction_Tests() { _personRepository = ServiceProvider.GetRequiredService>(); + _unitOfWorkManager = ServiceProvider.GetRequiredService(); } [Fact] @@ -31,7 +34,7 @@ namespace Volo.Abp.EntityFrameworkCore throw new Exception(exceptionMessage); }); } - catch (Exception e) when(e.Message == exceptionMessage) + catch (Exception e) when (e.Message == exceptionMessage) { } @@ -39,5 +42,21 @@ namespace Volo.Abp.EntityFrameworkCore var person = await _personRepository.FindAsync(personId); person.ShouldBeNull(); } + + [Fact] + public async Task Should_Rollback_Transaction_Manually() + { + var personId = Guid.NewGuid(); + + await WithUnitOfWorkAsync(async () => + { + _unitOfWorkManager.Current.ShouldNotBeNull(); + await _personRepository.InsertAsync(new Person(personId, "Adam", 42)); + await _unitOfWorkManager.Current.RollbackAsync(); + }); + + var person = await _personRepository.FindAsync(personId); + person.ShouldBeNull(); + } } } diff --git a/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Events_Tests.cs b/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Events_Tests.cs index 04c061d284..a9b18b9978 100644 --- a/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Events_Tests.cs +++ b/test/Volo.Abp.Tests/Volo/Abp/Uow/UnitOfWork_Events_Tests.cs @@ -108,5 +108,36 @@ namespace Volo.Abp.Uow failed.ShouldBeTrue(); disposed.ShouldBeTrue(); } + + [InlineData(true)] + [InlineData(false)] + [Theory] + public void Should_Trigger_Failed_If_Rolled_Back(bool callComplete) + { + var completed = false; + var failed = false; + var disposed = false; + + Assert.Throws(() => + { + using (var uow = _unitOfWorkManager.Begin()) + { + uow.Completed += (sender, args) => completed = true; + uow.Failed += (sender, args) => { failed = true; args.IsRolledback.ShouldBeTrue(); }; + uow.Disposed += (sender, args) => disposed = true; + + uow.Rollback(); + + if (callComplete) + { + uow.Complete(); + } + } + }).Message.ShouldBe("test exception"); + + completed.ShouldBeFalse(); + failed.ShouldBeTrue(); + disposed.ShouldBeTrue(); + } } }