Skip to content

Commit bbec1b7

Browse files
committed
Merge branch 'main' of https://github.com/cajuncoding/SqlAppLockHelper into main
2 parents 1d4af33 + 3cd6ffc commit bbec1b7

1 file changed

Lines changed: 46 additions & 22 deletions

File tree

README.md

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,15 @@ The usage for both is identical, with only the import being different based on t
1515

1616
#### Locking Scopes (maps to the `@LockOwner` parameter of `sp_getapplock`):
1717
There are two scopes for Locks that are supported:
18-
- Session Scope (requires expclit release; implemented as IDisposable/IAsyncDisposable to provide reliable release via C# `using` pattern)
19-
- Transaction Scope (can be optionally released, but will automatically be released by SqlServer when Transaction is Commited/Rolled-back/Closed).
18+
- Session Scope (will automatically be released by Sql Server when the Sql Connection is disposed/closed; or may be optionally explicitly released).
19+
- Transaction Scope (Will automatically be released by Sql Server when Sql Transaction is Commited/Rolled-back/Closed; or can be optionally explicitly released).
20+
21+
Note: Explicit release can be done anytime from the `SqlServerAppLock` class returned from an acquired lock, and is also intrinsically done via IDisposable/IAsyncDisposable on the `SqlServerAppLock` class to provide reliable release when scope closes via C# `using` pattern.
2022

2123
#### Usage Notes:
22-
- The generally recommended approach is to use the *Transaction* scope because it is slightly safer (e.g. more resilient agains
24+
- The generally recommended approach is to use the *Transaction* scope because it is slightly safer (e.g. more resilient against
2325
abandoned locks) by allowing the Locks to automatically expire with the Transaction; and is the default behavior of Sql Server.
24-
- However the *Session* scope is reliably implemented via IDisposable/IAsyncDisposable C# interfaces leaving
25-
the main residual risk of Database communication or Network issues, whereby if we can't communicate with the DB then we can't explicity release the lock.
26+
- However the *Session* scope is reliably implemented as long as you always close/dispose of the connection and/or via the `SqlServerAppLock` class; which also implements IDisposable/IAsyncDisposable C# interfaces.
2627
- The lock _acquisition timeout_ value is the value (in seconds) for which Sql Server will try and wait for Lock Acquisition. By specifying Zero
2728
(0 seconds) then Sql Server will attempt to get the lock but immediately fail lock acquisition and return if it cannot
2829
acquire the lock.
@@ -33,10 +34,10 @@ acquire the lock.
3334

3435
#### Use Cases:
3536
- Provide a lock implementation similar to C# `lock (...) {}` but on a distributed scale across many instances of an
36-
application (e.g. Azure Functions, Load Balanced Servers, etc.) .
37-
- Provide a lock to ensure code is only ever run by one instance at a time (e.g. Bulk Loading or Bulk Synchronization processing,
37+
application (e.g. Azure Functions, Load Balanced Servers, etc.).
38+
- Provide a mutex lock to ensure code is only ever run by one instance at a time (e.g. Bulk Loading or Bulk Synchronization processing,
3839
Queue Processing logic, Transactional Outbox Pattern, etc.).
39-
- Many more I'm sure... but these are the ones that I've implemented in enterprises.
40+
- I'm sure there are many more... but these are the best examples that I've needed to implement in enterprises.
4041

4142

4243
## Nuget Package
@@ -62,9 +63,34 @@ using SqlAppLockHelper.SystemDataNS;
6263
Usage is very simple by using custom extensions of the SqlConnection or SqlTransaction. The following example shows
6364
the recommended usage of Transaction Scope by calling `.AcquireAppLockAsync(...)` on the SqlTransaction instance:
6465

65-
*NOTE:* Async is recommended, but the sync implementation works exactly the same -- sans async/await.
66+
*NOTES:*
67+
- Async is recommended, but the sync implementation works exactly the same -- sans async/await.
68+
- Default behavior is to throw a `SqlServerAppLockAcquisitionException` when lock acquisition fails but this can be controlled via `throwsException` parameter.
6669

67-
#### Using Sql Transaction (Transaction Scope will be used):
70+
#### Using Sql Transaction (Transaction Scope will be used) - Default behavior will throw an Exception:
71+
```csharp
72+
//Attempt Acquisition of Lock and Handle Exception if Lock cannot be acquired...
73+
try
74+
{
75+
await using var sqlConn = new SqlConnection(sqlConnectionString);
76+
await sqlConn.OpenAsync();
77+
78+
await using var sqlTrans = (SqlTransaction)await sqlConn.BeginTransactionAsync();
79+
80+
//Using any SqlTransaction (cast DbTransaction to SqlTransaction if needed), this will
81+
// attempt to acquire a distributed mutex/lock, and will wait up to 5 seconds before timing out.
82+
await using var appLock = await sqlTrans.AcquireAppLockAsync("MyAppBulkLoadingDistributedLock", 5);
83+
84+
//.... Custom logic that should only occur when a lock is held....
85+
86+
}
87+
catch (SqlServerAppLockAcquisitionException appLockException)
88+
{
89+
//.... A lock could not be acquired so handle as needed....
90+
}
91+
```
92+
93+
#### Using Sql Transaction (Transaction Scope will be used) - Without Exception Handling:
6894
```csharp
6995
await using var sqlConn = new SqlConnection(sqlConnectionString);
7096
await sqlConn.OpenAsync();
@@ -73,39 +99,37 @@ the recommended usage of Transaction Scope by calling `.AcquireAppLockAsync(...)
7399

74100
//Using any SqlTransaction (cast DbTransaction to SqlTransaction if needed), this will
75101
// attempt to acquire a distributed mutex/lock, and will wait up to 5 seconds before timing out.
76-
//Note: Default behavior is to throw and exception if the Lock cannot be acquired
77-
// (e.g. is already held by another process) but this can be overridden by parameter
78-
// to return the state in the appLock result.
79-
await using var appLock = await sqlTrans.AcquireAppLockAsync("MyAppBulkLoadingDistributedLock", 5);
102+
//Note: Default behavior is to throw and exception but this is controlled via throwsException param
103+
// and can then be managed via the returned the SqlServerAppLock result.
104+
await using var appLock = await sqlTrans.AcquireAppLockAsync("MyAppBulkLoadingDistributedLock", 5, false);
80105

81106
if(appLock.IsAcquired)
82107
{
83-
//.... Custom Lock that should only occur when a lock is held....
108+
//.... Custom logic that should only occur when a lock is held....
84109
}
85110

86111
```
87-
#### Using Sql Connection (Session Scope will be used):
112+
#### Using Sql Connection (Session Scope will be used) - Without Exception Handling:
88113
_*NOTE: *Application Lock should ALWAYS be explicity Disposed of to ensure Lock is released**_
89114
```csharp
90115
await using var sqlConn = new SqlConnection(sqlConnectionString);
91116
await sqlConn.OpenAsync();
92117

93118
//Using any SqlTransaction (cast DbTransaction to SqlTransaction if needed), this will
94119
// attempt to acquire a distributed mutex/lock, and will wait up to 5 seconds before timing out.
95-
//Note: Default behavior is to throw and exception if the Lock cannot be acquired
96-
// (e.g. is already held by another process) but this can be overridden by parameter
97-
// to return the state in the appLock result.
120+
//Note: Default behavior is to throw and exception but this is controlled via throwsException param
121+
// and can then be managed via the returned the SqlServerAppLock result.
98122
//Note: The IDisposable/IAsyncDisposable implementation ensures that the Lock is released!
99-
await using var appLock = await sqlConn.AcquireAppLockAsync("MyAppBulkLoadingDistributedLock", 5);
123+
await using var appLock = await sqlConn.AcquireAppLockAsync("MyAppBulkLoadingDistributedLock", 5, false);
100124

101125
if(appLock.IsAcquired)
102126
{
103-
//.... Custom Lock that should only occur when a lock is held....
127+
//.... Custom logic that should only occur when a lock is held....
104128
}
105129

106130
```
107131

108-
_**NOTE: More Sample code is provided in the Tests Project (as Integration Tests)...**_
132+
_**NOTE: More Sample code is provided in the Tests Project...**_
109133

110134

111135
```

0 commit comments

Comments
 (0)