From 14816857e551bd60c2330f4b30531f9e3a463285 Mon Sep 17 00:00:00 2001 From: Engincan VESKE Date: Sun, 20 Mar 2022 17:43:57 +0300 Subject: [PATCH] Docs: Update Concurrency-Check.md --- docs/en/Concurrency-Check.md | 146 +++++++++++++++++++++- docs/en/images/optimistic-concurrency.png | Bin 0 -> 12813 bytes 2 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 docs/en/images/optimistic-concurrency.png diff --git a/docs/en/Concurrency-Check.md b/docs/en/Concurrency-Check.md index e22afe82b8..0258c56754 100644 --- a/docs/en/Concurrency-Check.md +++ b/docs/en/Concurrency-Check.md @@ -1,3 +1,147 @@ ## Concurrency Check -TODO \ No newline at end of file +### Introduction + +Concurrency Check (also known as **Concurrency Control**) refers to specific mechanisms used to ensure data consistency in presence of concurrent changes (multiple processes or users access or change the same data in a database at the same time). + +There are two commonly used concurrency control mechanisms/approaches; +* **Optimistic Concurrency Control**: Optimistic Concurrency Control allows multiple users to attempt to **update** the same record without informing the users that others are also attempting to **update** the record. + + * If a user successfully updates the record, the other users need to get the latest changes for the current record to be able to make changes. + * ABP's concurrency check system uses the **Optimistic Concurrency Control**. + +* **Pessimistic Concurrency Control**: Pessimistic Concurrency Control prevents simultaneous updates to records and uses a locking mechanism. For more information please see [here](https://www.martinfowler.com/eaaCatalog/pessimisticOfflineLock.html). + +### Usage + +#### `IHasConcurrencyStamp` Interface + +To enable **concurrency control** to your entity class, you should implement the `IHasConcurrencyStamp` interface, directly or indirectly. + +```csharp +public interface IHasConcurrencyStamp +{ + public string ConcurrencyStamp { get; set; } +} +``` + +* It is the base interface for **concurrency control** and has a just simple property named `ConcurrencyStamp`. +* While a new record **creating**, if the entity implements the `IHasConcurrencyStamp` interface, ABP Framework sets a unique value to the **ConcurrencyStamp** property automatically. +* While a record **updating**, ABP Framework compares the **ConcurrencyStamp** property of the entity with the provided **ConcurrencyStamp** value by the user and if the values are matched, updates the **ConcurrencyStamp** property with the new unique value automatically. If there is a mismatch, `AbpDbConcurrencyException` is thrown. + +**Example: Applying Concurrency Control for the Book Entity** + +Implement the `IHasConcurrencyStamp` interface for your entity: + +```csharp +public class Book : Entity, IHasConcurrencyStamp +{ + public string ConcurrencyStamp { get; set; } + + //... +} +``` + +Also, implement your output and update DTO classes from the `IHasConcurrencyStamp` interface: + +```csharp +public class BookDto : EntityDto, IHasConcurrencyStamp +{ + //... + + public string ConcurrencyStamp { get; set; } +} + +public class UpdateBookDto : IHasConcurrencyStamp +{ + //... + + public string ConcurrencyStamp { get; set; } +} +``` + +Set the **ConcurrencyStamp** input value to the entity in the **UpdateAsync** method of your application service, for that purpose you can use the `SetConcurrencyStampIfNotNull` method like as below: + +```csharp +public class BookAppService : ApplicationService, IBookAppService +{ + //... + + public virtual async Task UpdateAsync(Guid id, UpdateBookDto input) + { + var book = await BookRepository.GetAsync(id); + + book.SetConcurrencyStampIfNotNull(input.ConcurrencyStamp); + + //set other input values to the entity ... + + await BookRepository.UpdateAsync(book); + } +} +``` + +* After that, when multiple users try to update the same record at the same time, concurrency stamp mismatch occurs and `AbpDbConcurrencyException` is thrown. + +#### Base Classes + +[Aggregate Root](./Entities.md#aggregateroot-class) entity classes already implement the `IHasConcurrencyStamp` interface. So, if you are deriving from one of these base classes, you don't need to manually implement the `IHasConcurrencyStamp` interface: + +- `AggregateRoot`, `AggregateRoot` +- `CreationAuditedAggregateRoot`, `CreationAuditedAggregateRoot` +- `AuditedAggregateRoot`, `AuditedAggregateRoot` +- `FullAuditedAggregateRoot`, `FullAuditedAggregateRoot` + +**Example: Applying Concurrency Control for the Book Entity** + +You can inherit your entity from one of [the base classes](#base-classes): + +```csharp +public class Book : FullAuditedAggregateRoot +{ + //... +} +``` + +Then, you can implement your output and update DTO classes from the `IHasConcurrencyStamp` interface: + +```csharp +public class BookDto : EntityDto, IHasConcurrencyStamp +{ + //... + + public string ConcurrencyStamp { get; set; } +} + +public class UpdateBookDto : IHasConcurrencyStamp +{ + //... + + public string ConcurrencyStamp { get; set; } +} +``` + +Set the **ConcurrencyStamp** input value to the entity in the **UpdateAsync** method of your application service, for that purpose you can use the `SetConcurrencyStampIfNotNull` method like as below: + +```csharp +public class BookAppService : ApplicationService, IBookAppService +{ + //... + + public virtual async Task UpdateAsync(Guid id, UpdateBookDto input) + { + var book = await BookRepository.GetAsync(id); + + book.SetConcurrencyStampIfNotNull(input.ConcurrencyStamp); + + //set other input values to the entity ... + + await BookRepository.UpdateAsync(book); + } +} +``` + +After that, when multiple users try to update the same record at the same time, concurrency stamp mismatch occurs and `AbpDbConcurrencyException` is thrown. You can either handle the exception manually or let the ABP Framework handle it for you. + +ABP Framework shows a user-friendly error message like in the image below, if you don't handle the exception manually. + +![Optimistic Concurrency](./images/optimistic-concurrency.png) \ No newline at end of file diff --git a/docs/en/images/optimistic-concurrency.png b/docs/en/images/optimistic-concurrency.png new file mode 100644 index 0000000000000000000000000000000000000000..209bc824609fa6113062a89dc146eda4f332ad8d GIT binary patch literal 12813 zcmd^mXIPU@({B(v0yacZz)lko5s;1rK@gBGT|uN5DWQg$j^AQ1Zt z^=En@&=Ca?h(-G3ao`u*K=rr47mKH!+7nP2`q~QcaMbRx)?*N;B8F}M?J?l_l)Ji# zCkS-j>+r)e^`74s1d_;k@$9jIuO$h0BGABas$;`W@5YUb=Nvvh6w=^&b?%w&(TaP5 zSsy=!*O!e!L>tQL8wv}Hi;JBsjGdPWHybXUeSGopMK-OX>*{3a!?>3Z$cmBzw^LuX z9gVy1xkV3B`qO0h<--~i7O<2mkMs@XU;%+@4c@;mI0=y6La|<*2IwkNodJF>Q~Up> z#c#;2J_KEpASa1-kJqbC@yaJ4?UFsHYl-5bhJIJB=#8@Md3fBGT=iAh{MD)XZRBEn z!flk*#xs1zUb5|c4n74n~F=UzfbJiDhEw&_4B%#T_+GDgd*5|-fV8fSm zYw^06_6MzWA;v#T8usIQ-%7D#CqP-wfatsL${43c1nG_bywh!hGC-|5(^Y{c^%~gz3sAWc%HfyWU%wmFOkPX!@5o9Npe}yRCUAoG$~{$ z=qyj@2^9p#J@c$cGAfaZF|5h=f|*uxO&3;J#}t>3*${&=k_-Rm@X8rer zJYNj+5lq2Tp)ZuRTT{o_l@PSJHl8~Pl}~7Zq#4u*rwtkn9oe!V&^H!WnY&r}05f)yj|tF4yLXwsj|3!+HAgDS?NT zrCj0xI8$3uladluHcfWr_c1zAsDp%l!ZYlVtfE`%pmj-j6kIzsuY`;rOPV{cy_V&O z(>AP*$6}Lm@hyXQ?n}usCl!oaYH(yXD@v4kGpJAC{?g_gLs%_@4Aw^a1 zfR(kn6az%cbZ=qL%QmLVh#aJwbkjBhuzCps`2~*V55H&Dg$PHtuo`@zGvpteOhcUT z;To%hhFuVvaM@#4?D@JzGo&!2!^>7Q#m+2TL*d}eNWE7fl=aofS>F)hZu4_~%zuJ= z8a{k-5ju;>L&VXo&L(oxB)Olw%%mY2ua07qj(q5p(e2G{ul*?b>exBprRA16RSgIe z`W-X$f)JeTn1RKhRm-cy3q))P@8peJ%$o%kH$@CfpQ-`U5Wc<+GJM$~Q<=Ck@C@k8 z7__kIpV3ex7UiT?)(~K) z-{@2F?3ua;3IhaG^mW)lKT-BTQr`IbUR$V!646;6Z@D7btVdgjSv3zOK>Ca|ni*h@ z5VJ#Kv+@+>rNUxL^pH09kzHfVH8XG-D8L^?1$Ie{dou;P>pN0Ut1nbwBdVuX*6uXz zrM+95H$2(74O+Wo!#qCoWXZYY)m!*Z;j~fL51W)l=^i)2s}w{$f*a&>6PUxctkOnX zk@Nk|>g_6LTYS;o%KZY4)|X8$aiChx&=c-Lp1Q3w#MKlVa1j9peN(_u!1p=TKee94 zo&N|YFgy5MtMMyoI~&7%)`a%Agtr8}6hu0LALMfbnB2zoC{LiT~l$kR@}ZAA!@ z=YHeL9r~25EFO`I5CHiATOtfGlZ_8e-e5RL;FGJabCD<}iy@B>tT1Jr)i zK*c=1z+AC5@{pU;Y-J)L%g<(5%v-L#As%=qLfO>Ufa;#r1kMdpbwmoX)38y=RJN37q6Zt*E!Gye+YI=UKJ-NA)*b?`9C$>9LHW&t0 z+{yzh>zoiG=b!Eie_c|+77T!+G}nzv}y z9*?*rJRjj8_rnQ`TQg>fOxG7v3VKYIrX-1?-I3C1lRXrOBoHvwqbE zhzhqw(+9MV-OiYN{2_{GylyjIiSA^J-s?agOUd+mknuy;B)gLzoY9|%bb`B_x^JwC zE(&SLNS90VzjVpm4K(nnh4oqSq`9qlik;n&B5(N-?Zom%oc5oSA11YJKf~hMv{B*p zH$B`H4=B<9%qN2qt^h3P0X|sRLuNZguCs8Wi$pTt3sw%)F?w}lIV^MK1rFva6R1PzC4{vJ`6h36SogtWz^e zRh8}MuS(VEtEs)t?mQ)V2Uv_#e(xjkS|cCmnKZz)ZKFJR3=uLmPrxE_@!zxU=D&Q; z7BFp#K4*@fp@6RcJftWdIC-I`zbe+ipmO!yJpMUCtCw57IV?FrH?IQHcu*HWodD*@ z_1f1Koc#hD)=>&C`eAbvBkQXO`s#eB&%1+qBm)&^4J~=DD{ZF~Q$63S8IDQ(I~^a% zXZYeXoZ&Y+v!Ce9R{lL5F?={hUCvrRN0v3Md1w=qC(o$REJD=S0fpB7RH`AAkwVY*g^M>aduT#PGkd z7ybWTSk_LM8hw^Jz(x_dB9lMjg9(1M$n38P{TKdi*DmP8%b=(K;QY496BeUC;>FXR z==|G4FU^~Bd`M^|pyuivivP13X4e+<0-h9r8{i_31fQ`kZbY^~ zEvl7r;_M!<(8X}&>^gu>2H;SOe7U#)LaYHo+}saXo+$wu`uV(RO_e^2tcdX#BHVD` zt$X@uhlBp%(>KV8P3xDnKq7{guq9;I0PS2p6H?R&Vw)`H*E5U@HvfUXTE-9nJX+F50&( zw-T6UK)_{%;iu*IIJo>HGfKHG`+g+wnEQYRLIHC_5P0Re=q5w}-GIvif9K^1uI=6h zm#L_RK?CzY^9^0nZ>ooiSQWrpmF-gNS-&a)>SX%6LpWR0SpI_hn>9{wQQ)12PGydY z{z3&{UaX!Uz(!&J{0jtPfrVS7!bKjdhx$ldDM#u9D~16pRuC*2u!xY={D?(d<2c|2 zFF421*r9uQfPgv|nWP8V3e-iY!FzB$F~OVsa9r_i(*n4wb66oSbJDu7 z$`l09h0%|G-9>rD;tf&2NpJPGm2&JX1URnQ3;^iKyd2*)2oG|Ezq-`wE$J-<^x=R$ z&EaKipY}0dnG)j(?)@smO9hq%uE5F4rQ&r6LSg~vc?UMyjz0G8CDeSDI_ANqFQ8xE z_EJ&Hg5{Yq05on2v5IY4MV9}l;uM$=O1N9AT@YOW15E;3SF8R~FAl(x{b*o*mtoX5 zW`*a;-@~25i2M3#`Z}OBKpV_EuDwaW9Owcg;{_p0#$b-msr4zei|+}Yq1_=3hghV@ zd!^r6hW;{hKNi6GZ}4wY{8Q?I0q+SXS}YDZ7`Y}_@&`8@bwEb_ruf~{KcOGf_Fpgw zQTv5G(PJ+WT-Jh;vC`imd<<|iRDHjv#ryHU(316toR>jV=qFM}Vtj7XJj2A6y8Yh~ z*_`W&3fCL4Lhit*E4fSSv4#Xb-K7-hOlC!79)r86qYgs429g0pX84Ryh3L*;q(Vi$ z2hOV8$@H#)dWw5W8?Cc+Kc}GZBKcOq03I#<4?GIv^HiNR{Ul!Lxn@&Vc_ENc*9zm! zBK#M&)!ir-@03!qlse9C)OM=ZS#w(w8FAnE6$h#?>uvP-3&MC0n#EoI2Jp_!y6zB{De2XI(=WyK?vD}|HmC zo5?;zqx`;Sf5%@7cDfPExLcfZ$Jeyf{dAV%uSpVY*2YIE=}DMMD$EP*3w= zBev-JZquKyO#ocx*5`^fH8+niDjGm$o;QWsn}H0xfdKPHYG&O7yWW$28a{9D?*Br% z<{lMz2Y`gKS~>64TWaVAyBLqrGToXBC=a7DL8*nyOT#om6McP)E5sULRm0Y`^ZN!4 znZLFn^jE*QX6~w#(dq{ZSAetPZ#fo0>HGrgxcGd>k&74j$qHS!WS{E5>M}?EEAW(K z5%9rtEV@n?Wb~#lV38P<m?p&qge^h)}Fih*yGPwS8WrFfmM_cElE;S&2x z>RgV6^#3n$b(LOw%qdyGGaCWU*09M&KuCI2=?Ewv5c8v(OHYEwi;YF>a6EpP^2A0< zy1MMw2z~RR_lHLYa4bZ2v)wrI^$f7|Vz+s#tR3-^pU>*6DVRk!uE7{pWx#YGcgtPR zly&Vq0|e6wOTMsQj*OuWMJc_DW(_sggkeMTCjW4^{_i3;Sp{W)X+Sgn5<70dbqfYC z5TFI9_D#J;NzEg>C;sG(h_SV<`FzSermlEgfpd@X+xcJT zzEycI>?nC|-}9wdvp21Jm;Ts#zTqNmRUeePS><|F;@1_nFHZY$;rpwv^OD7`UK=1P z%pK90idLm<_o{hQ*+W|=_Uksk(6<-pY}JyTN{F0=cOvcDNdlQ$^|G5|b(^t}>o{!| zRX^kCp(Y#U5fs>XC7SL!i0}^FiFIEPB&zJ~7-A4vAC5dyxZ_qSZ+D=@8I@j~poCne zBCWEMMv8ar)SexK^;Ys>*mLe08n7MJPX^Xm-4xUl|~vadmI`PJM$BfAMZ ze$;{H-Q|IXX|cHPQ7Qg-hrd|n=+G@4y$lx-PM9OFH!gy_(t83uT2UjauwuH~daK@T z|AD+!mhQCL_F6+9F?^?@=Ge*C3IRX$E{OG$d@KrKIJ9oFs}$+h>{sLDVkW|8+F#vn zv{FkjC#U1vn>$hZD!O-EDgz$kxAr!x>Yn>V%b8gO96VPvH6ryze?6r@=Wfb5!IaSX z2FI1yO$R--apli{45=?9-@!O-L;6;5AE+ynJUtKBvehWI6*#bMeEqDNs5!r^e6D6v z0Xy50)6;S!fFE&-TgTQ`2A zUB7;>ZtOxPVq+O7=S7oj=uzyi$C}2}&jEKIz^d$nEptMv$@6T6M-gk!CiTCU0gwUGOsOY$FrT zLG^uQ-n-Tt_4-@5$&>9Cqt6Ij?w}Lhdr8g4@dvwrTJ#C*~U$IP6)L2g?RT)vC!*9NuM`v?SAt3!3Q#4C;Xf$(^wBO~)u6jagm` zfL>o!aOD^a@W7S&uf+yEo;D3HZ)kJLB?gzk84_B>gQgQ{i94@ntjMPKEyZJ)=9;5+ zf|$k_Z9I`cEPvIxpX_rD{a}gYK1#3sNLfP|nL%mO2>Ei20XwEo`V?0Fbxboj(R2)t zXLAVLJKh2cIW(IgHb?UtT|%hPxc<^f8K=59a zS08R7F)BC(w4J801^9p@=ZaqRCv0h+cFFvWG?CPfhAuHf?#;KYX5WuyioiJb&`P)x$0PE6UDvOHh_m76`m%RPy9P79?t@5viGvLhQV zqrb|Vha`AU6wiMsxP#lH##h~|`#h&a4?~stRfsj@HKW%?_3A|6j++9KiMX0_xzQM( zq?#n2sgLm)_mDD&o;dF(qh>T>FHbp%4W>4s4X{h>wo{HX5Rx&KtxtFB9aB?|cXa#_GcXqh`}v}}?=qtJ%Z_oT zC3NI=Km~Y&T)e~ZPrRNH>;#h>^+V^i^*O*3c$Mu50{n#Z175G1%0TV>gVT=G{ElF( z%BSaNJd!G#=7JA2tOAuR8q(SIkR8WF6x@m>`=@DnsIk}jishHHduSBhKC#{eRA4a= zb!CD1W>SthO#@Fm@VIODWU=Jz*7vw!XR$ft8lMqnRGmJ?9A;HM$XChw$8#$oFuXD& zS^fZ_Y2%Wv_?R4ATmPqCl=vVm)71?{b}9Yiuy}@sah#ED^6KakGHnoh&Z|w#D>$Y3 z&~N9-$eMyWDdNoLO4`AaWLokgy=LAi)9j9wHpqJ$4dp1*o(F|A{-QMf8a%JoCGFvo zQB!429gVYLg2{AAuF}&N&>8f(LK^8?@!TUn@E*a_oZl)!C95LX6Mxg)miK_ISn(+q{0$>I=QR3>5z z%GsE2YjVtcZE&!7(JHV=N68|uUJ*()YFDbeXYa6-$2d%8ct zFLzbs>p7{IiU>)g-b_aR`+M52`aH41zsufjEt30uP>9l~KFoUY@#C~rLSKe0G=DW- z%)Mi@V(<;w+4P|w~7^6p?#T-qlm<4M=_Pl$?Q*&8UOW6+Xcl=kq%kXT% zeDzlP?}_P+q87YbU%c0o+=+0`3P_Ue)onL3POW}gf~1tk0v90|Y&U|HG!#c2MAg{6 z>%tW2WPiBXmZGSjhTRXgTxghsy+HATQ4goQ$@CzZcVEe*Oy^H-#pHSV3EG!4braNy z{WIA2ei-^xgVT4n6hrO17|$j{zAZq<$RQcK6t_}QydRZhGO{U z8g|C{16OK#*PFi#1&xzC_LAV^&(+YIn7~`m9k`wC#HCy7eC0vjsZi=(jZPERkCRF( zaIIk9Yus^bo@0t-G9l~-x#S2^O>4zMX)HRRz7?VMinr}7x|#i+=Q2K}v&?<8HK51k z)2(LkExrr_R@c66a}Y&QlwH-EPPAzjEkyX#k*#Db^-5r^FRiZZsNpHAfuVCmvF6@p zM#S)l;9kWfDL#zmw={B3GoxcgO`Lv-+ff_^ z`G*jeP2fFSadxdy-Q@s&E94H%a0w1L`Q-kW=ZopW|8w=gXU-c9TeW=kk7utiwy9^^Yqof2hAt*t|TqNQaw>0{q- zw12D##i9B-&YU?&y|6QuN{aMs4}2OXkA2nuU}-I5iT~+Gy6Q6O{4%^XlRvwr&)vy% zqNe(wr}YF;mV@*RgS?BvBwRsZPE*wEn=JbVxvW+HKh9#xypc8#nbHC zYfCzVS~OUB&7RQsIUj2^Z_2_-vUfUg8QE1$6hR{&Pxs-R%}UxQA?1^*G=Gh>=%!sI zi;;>di{2amP~2qfci_!}*+cFJcYk^EP#ZMte!~e{vcJn)-FNOBcsXY;Zr+xcawHqB z!Eh&Z$P_UsVx_^6Fo-P|)$7}Q)ns;}S}?mTo6k&U>uKpuMt%fZw4NUqKdA)=_Y6#B z8gnd6L{U_5kl$s{Tmxz&~Lzvbc>< zOm)*{pOAu80)|t0yCQFFIY(zOb_G`+fbV zEx(dojtzX%LQRn_3>;iWVcSoO7uW}i3^i#AL zsZVtgRO?N<6u^4ov%fQ$&+g(*@8HeD(*RVn&*b!RDW!K9+8qEGk~FEy@%5me>M2Z>ta_>l4X z^F~$w#&79CmFDh!+9`InB1*pqtRI8+`8jg6r)FbqoT$*8fHG*VxKN)V@pkAE_6={> z<@2tYvT>w`*G~1uDDO(Dq4p~rdzWrD8TkbSk4y@Y3Dw3O1elrI=(4WQa%ti*Wr-`j z=q8J1hdLep7+%CVAB8gJU&~^MZLOt6trUB3x=29q3uj!;Yc1dcz-KC1C%uNN^u>$t z=I4!Z@qcH_r4lYSYRP4?=oJle9RK?>3hLK@bf_#}P>MTiH5G>YVl_lElSyiH8Sg`> zUpf~G_bb-cw#&@TZyx)iS7QE9jSrvhiq8@chwWy~=sw#B@fwzeHuhPRFGmGW!XHCD zr067NhwTxhec-wb;(>;`!oH#ZZDvj2qpL5EIZJ%V54jzs%W|UHbP1|Ayx@wC$JHp`eMr;ha+Dl$_Wc2QJix2H+Gg8UD4^lA zh^AJLUbFhLI9KV<&iQpGQ;B&m+pk|+^oheLIZ-7;{;13nXntV8{S!2Xk7G1phg>TC znAL7)g8g^U`}@)*8ANAq((t~!a<=em>rbvc{&XAv`9uM)4_jKa-EY*YoHNr|n|?(j z?q4_^)&@sHzdSfeo*o^Lplnsy?uF+BxNk1H4i!jM6yfS^m)#`7zGs-0Z`0sOkh-V)OQ{uZRT0OdVOwfVG{RE=s(3S;V9-I*vl0E}rW2i2YsF4$0ovq7-(u|u`U|Fi zkm(=OV{FKRSvSwcX)9sN@ecI9($7^)mc{oVS&cqHvVL!SxxiRTY zWqgCzxpTgMKd=E^H>Sg7w&BSr_ACCI;J<@x)aJZ9)ndb>PoH8~-RoP!h0?ele%T$w z+s_62h}3D4&%*i3jQaVey1A+%VF{Py;GL&77o9PvEaIqLIVUYu@Tey=N2w_fs&C0j z#~P#787>!~{+3T|ncHPb92MM-Lsc7TsrtrtP}4x%KeI-IM>0t+%qFWfQ5c&~r_!=qlPibs1{9z& z>7P)rv{E!T%x^3p@vj&$is;E@M)j4t7Z_-;cDxr>fkr17zB#L7RsY27bh-Fe+EDwL zVZbXhXpm9P1H0hYOF?FF1Pk=Z?R=jI>K)IW9s&N+)3SFHAZLWmx@HQ|!KRFPgQ=;O zK1cR6X(11D>op)d@95Gm)0IU_wF&n3?uo^mst@Cdk92q9VMrS@4)W&`T~@dAstoa? zmZi*X8&M9q4nZGE0IhFzeP`~o_%08$iwC_{u{+b)Db?=hI<^-QADmHz3eLhfRJ5=&II#b?^tRwIH<5~XrLz1Nh zbEb!3`!~3`ud!KIs0po0|HSG9;hnx`B-^9hzbnM{&}~Pa*7ysVRszE)=XvTStt>XU z4#boD)|=P%>#XeCDueq2G*^e7oF-38H?QmPjDyRS{CDMW%&fn zoK1bB_e`Db;)}KHX$C5DF1fi#-W6ibOpUcl{^q}w{jrCb={|bBI&wM-$?(mb+EBKu zx!b%O?C+~@2Gwgr4*d*w3aDFKPR>(xn=Hb1pvueld#?D37l@;ff0QaBtH~4uAN))+Bni z%Wp#5Pz6k~s-nMLYdtj|J>32@YW|ILI`_|f4TC>2Ee67Ng zpW7q;T2Zk_$T9DnC#R4Fw&P1}B=OihXB#^Rg;!cdhz=*lGOv36U}^_5b#XgY#=P31 zi&tmgo<15^IgY`dk1FlDZSG~ z455hZO>hpd_9^PE{@JRHHePAHA(|kKH&opD6=9kZwEpp0Vu5RqXB2zU$c)mk+FV7NF#EcWrn+bv&RyJ7Tt#+m}YLcnrpHA9<+0#A;x_@}hQ5!|R7 z9K8RWQrN5=Z`E7g!?_8Yq|8hKcedPw7|xi%l}?v|Jnl}y2ArAC?-c1y0M|GMbUOt* z{;ZGm*Nur%dRmPMEacwr*-NlYS^qScZjn6|hP=>Iz7f()SfL-6n??jx5g9^J^mou` z(xjGz9? z!OiCfdlRmUkB%Eheyttcj2#EBYox^bd#la3`v&vDiF_IPY#OycJq#o&7EZ>l#_3|EF&<+=k5o|4J-*_e zfo@&`iab>O0-6VhP2_-K7Po!sE{Qvr!z^MLe2EsCOZL_9Qn~7=Dklq&AUiPjy0TV) zaiv{wi2ylRs~BruO!VjPYV8&~kJUmR;0JPKQ&w-aCIucG%CD;Kwh%o2FB zk!(i}i#5Mq_q!@U7`K}q67RIk31hmy{f5j^S=5NOfN%6gj{uj<0~>hdEVbvbyhrrQ zA6UE-j*-3U7NM=<6TIt)fWD}J=L|`6G4Bu0wb7r3QaFE11yCno*sf!$!ezU&YLEz24BATdoXK)L zOgSg)T`4X?g!i1e9s*Qs>bfevhd(s@kjiot$e)8ki0A(Iwbu-d6}=i6jJb_^pJ!!2 zIqN1+oA(NR-g{?+Lg}|+ecZvs?{)Cd-4)rDMW&QafP}e$9BthJWEU*;VxmgCuZmXBT%e8vjTeMaG1$_;mzJ4q3V+HaK5K&q|a(~(8_f@ zuYo+7*TAS!vnqFQi&2nUOE9f35r4`V(*3)THOu@qdtts<)L#Qw5aV6CB`gJ@$WY5j z;$2DGT?Io9ic=$W(=Drq+n6Uo1{^>EuE#*Jcg9>sv!_fRQF&B?_55DJH6LvhyC0%*l~x0ein3%<#4IR-tH=WTa3f63Ie9%8f!xCxEuNUD(GK{ z(M4hDrFCsZ>aa`JTSaRwyz+7Tu)AVAV``w8KftafL>Mr23#Sd>ehmU3dnu;P;$TVnoXN>!JX1C2D65q@@IOss(xeKPe3Y~ zu_3wH6|mfP_G;0Rg1pHku}IHSAioO~k7Sv@dpTbI*{s)~M2^F$Fc!i(+wm2VT+)i< zhf5ydbMOwoODr}svn^kbYDdv;tDVT`;&E{#emVCo6buqP0odZ=?Wbf^LXj0!6Sn&Z z>fxYxu(H?TPe|?Xr-^%ey}ueWw=lFM+pyi8oE-)e9YU^5dwLvj3px+ymboq*`3&kl zm3u10BOYm!31edUAQk<^!;vkbcjnG3qFI&l-Fk<)%UwpSrqMt9I7o1$kK@E?Nv_-) zZp_dO#~llxDzEKdMeQNN3(*5tNd_a&e3kjbXrOR}DsVxstD_0Hp|pIbu8p`l4orKrx41(#So@kfN@6&oKv(VSgSc)rm2A zhBQU07G1Lr>Uap?G6 MwV#zedGqmq0N&Wp$N&HU literal 0 HcmV?d00001