Isolation Transaction در ASP.NET Core
⚡ خلاصه سریع — اصل کاریها
اینها مهمترین چیزهایی هستن که اگه هیچی ندونی، فقط اینا رو بدونی کافیه:
- نکته ۱:
Isolation Levelیعنی سطح ایزولیشن مشخص میکنه تراکنشهای همزمان چقدر دادههای یکدیگر رو ببینن. - نکته ۲: در EF Core با
context.Database.BeginTransactionAsync(IsolationLevel.ReadCommitted)یک تراکنش با سطح ایزولیشن مشخص شروع میشه. - نکته ۳: سطح پیشفرض SQL Server یعنی
ReadCommittedفقط جلویDirty Readرو میگیره. - نکته ۴: سطح
Serializableقویترین ایزولیشن است، ولی Locking و Deadlock بیشتری ایجاد میکنه. - نکته ۵: همیشه از
await usingبرای تراکنش استفاده کن وCommitAsyncیاRollbackAsyncرو فراخوانی کن. - نکته ۶:
Savepointروی یک تراکنش امکان Rollback جزئی بدون از بین بردن کل تراکنش رو میده. - نکته ۷: تراکنش رو تا جای ممکن کوتاه نگه دار؛ هیچ I/O خارجی یا عملیات طولانی داخل تراکنش انجام نده.
- نکته ۸: برای چند
DbContextیا چند دیتابیس، معمولاً باید ازTransactionScopeاستفاده کنی.
۳ اشتباه رایج که باید ازشون پرهیز کنی:
- فراموش کردن
CommitAsyncیاRollbackAsyncو رها کردن تراکنش باز. - استفاده از
Serializableبرای همه تراکنشها بهطور پیشفرض. - باز نگه داشتن تراکنش هنگام انتظار برای پاسخ یک API یا یک عملیات سنگین.
اگر فقط ۵ دقیقه وقت داری، اینها رو یاد بگیر:
BeginTransactionAsyncبرای شروع تراکنش با سطح ایزولیشن مشخص.CommitAsyncوRollbackAsyncبرای پایان تراکنش.- انتخاب درست
IsolationLevelبر اساس نوع Anomaly که باید جلوش رو بگیری.
۱. مقدمه
این موضوع چیه؟
وقتی چند کاربر بهطور همزمان روی یک دیتابیس کار میکنن، ممکنه یکی از اونها در حال تغییر دادهها باشه و دیگری همون لحظه بخواد همون دادهها رو بخونه یا تغییر بده.
Isolation Transaction یعنی تعیین اینکه هر تراکنش چقدر از تغییرات تراکنشهای دیگر رو ببینه و چه قفلهایی روی دادهها اعمال بشه.
در ASP.NET Core و وقتی با Entity Framework Core کار میکنی، میتونی برای هر تراکنش یک سطح ایزولیشن مشخص کنی. این سطح مستقیماً تعیین میکنه:
- چه خطاهای همزمانی مثل
Dirty Readرخ بده یا نده. - چه قفلهایی روی ردیفها یا محدودهها اعمال بشه.
- برنامه چقدر بتونه همزمانی و کارایی بالایی داشته باشه.
چرا باید یادش بگیری؟
اگر سطح ایزولیشن رو درست انتخاب نکنی، یکی از این اتفاقها میافته:
- کاربر A موجودی حساب رو میخونه، کاربر B هم همون لحظه برداشت میکنه و A هنوز با مقدار قدیمی کار میکنه.
- یک گزارشگیری دو بار پشتسرهم تعداد ردیفهای مختلفی نشون میده.
- یک محصول با موجودی صفر به فروش میرسه چون دو خرید همزمان هر دو فکر کردن موجودی کافیه.
- دیتابیس بهخاطر قفلهای زیاد قفل میشه و Deadlock رخ میده.
یادگیری این موضوع باعث میشه همزمانی امن داشته باشی، یعنی نه داده غلط بخونی و نه دیتابیس از کار بیفته.
کجا استفاده میشه؟
- سیستمهای بانکی و انتقال وجه
- سیستمهای خرید و فروش و موجودی انبار
- رزرو بلیط، هتل، صندلی سینما
- گزارشگیریهای مالی حساس
- هر جایی که چند کاربر بهطور همزمان روی یک مجموعه داده مشترک کار میکنن
۲. پیشنیازها
قبل از شروع، بهتره اینها رو بدونی:
- زبان C#: آشنایی با کلاسها، async/await
- ASP.NET Core: ایجاد Web API یا MVC
- Entity Framework Core: مفاهیم
DbContext، مدلها،SaveChanges - SQL و تراکنشها: آشنایی اولیه با
BEGIN TRANSACTION،COMMIT،ROLLBACK - مفاهیم ACID: بدون اینکه عمیق باشی، بدونی تراکنش باید Atomic باشه
ابزارهای لازم
- .NET 8 یا .NET 9 SDK
- Visual Studio یا VS Code
- SQL Server یا PostgreSQL (مثالها با SQL Server است)
- پکیجهای زیر:
Microsoft.EntityFrameworkCoreMicrosoft.EntityFrameworkCore.SqlServerیا متناسب با دیتابیسMicrosoft.EntityFrameworkCore.Toolsبرای Migration
۳. نصب و راهاندازی
گام ۱: ایجاد پروژه
dotnet new webapi -n TransactionDemo
cd TransactionDemo
گام ۲: نصب پکیجها
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Tools
گام ۳: ساختن مدل و DbContext
فایل Entities/Account.cs:
namespace TransactionDemo.Entities;
public class Account
{
public int Id { get; set; }
public string Owner { get; set; } = string.Empty;
public decimal Balance { get; set; }
}
فایل Data/AppDbContext.cs:
using Microsoft.EntityFrameworkCore;
using TransactionDemo.Entities;
namespace TransactionDemo.Data;
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options)
{
}
public DbSet<Account> Accounts => Set<Account>();
}
گام ۴: ثبت DbContext در Program.cs
using Microsoft.EntityFrameworkCore;
using TransactionDemo.Data;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();
app.MapControllers();
app.Run();
و در appsettings.json:
{
"ConnectionStrings": {
"DefaultConnection": "Server=.;Database=TransactionDemo;Trusted_Connection=True;TrustServerCertificate=True"
}
}
گام ۵: اجرای Migration
dotnet ef migrations add Init
dotnet ef database update
حالا پروژه آماده استفاده از تراکنشهاست.
۴. مفاهیم پایه
تراکنش چیست؟
تراکنش یک واحد логиکی از کار هست که یا باید کاملاً اجرا بشه یا کاملاً برگرده.
به این خاصیت Atomicity میگن.
ACID چیست؟
- Atomicity: همه عملیات یا موفق میشن یا هیچکدوم.
- Consistency: تراکنش دادهها رو از یک حالت معتبر به حالت معتبر دیگه میبره.
- Isolation: تراکنشهای همزمان نباید همدیگه رو خراب کنن.
- Durability: بعد از Commit، داده حتی با قطع برق باقی میمونه.
Isolation Level یعنی چه؟
هر تراکنش میتونه دادههایی رو که تراکنشهای دیگر در حال تغییرش هستن ببینه یا نبینه.
به این «میزان دیدن» یا «درجه ایزولیشن» میگن.
سه مشکل معروف در همزمانی
۱. Dirty Read (خواندن کثیف)
تراکنش B قبل از Commit شدن تراکنش A، دادههای A رو میخونه.
اگر A برگشت بخوره، B یک مقدار ناموجود رو خونده.
۲. Non-Repeatable Read (خواندن غیرقابلتکرار)
در یک تراکنش، یک رکورد دو بار خونده میشه.
بار اول مقدار X بود، بار دوم مقدار Y شده چون یک تراکنش دیگه همون ردیف رو تغییر داده و Commit کرده.
۳. Phantom Read (خواندن فانتوم)
یک Query در یک تراکنش دوبار اجرا میشه.
بار اول ۵ ردیف برمیگردونه، بار دوم ۶ ردیف میشه چون یک تراکنش دیگه یک ردیف جدید اضافه یا کم کرده.
سطحهای Isolation در SQL Server
| سطح Isolation | Dirty Read | Non-Repeatable Read | Phantom Read | قفلگذاری |
|---|---|---|---|---|
| ReadUncommitted | بله | بله | بله | خیلی کم |
| ReadCommitted | خیر | بله | بله | متوسط |
| RepeatableRead | خیر | خیر | بله | زیاد |
| Serializable | خیر | خیر | خیر | خیلی زیاد |
| Snapshot | خیر | خیر | خیر | Optimistic |
شروع تراکنش با EF Core
using System.Data;
using Microsoft.EntityFrameworkCore;
await using var transaction = await context.Database.BeginTransactionAsync(
IsolationLevel.ReadCommitted);
// ... عملیات روی context ...
await context.SaveChangesAsync();
await transaction.CommitAsync();
اگر خطایی رخ بده و CommitAsync صدا زده نشه، await using بهصورت خودکار تراکنش را Rollback میکنه.
مقدار IsolationLevel از کجا میاد؟
از enum زیر:
using System.Data;
IsolationLevel.ReadUncommitted
IsolationLevel.ReadCommitted
IsolationLevel.RepeatableRead
IsolationLevel.Serializable
IsolationLevel.Snapshot
۵. مثالهای کد ساده
مثال ۱: تراکنش ساده با ReadCommitted
سناریو: کم کردن موجودی یک محصول.
using System.Data;
using Microsoft.EntityFrameworkCore;
public async Task DecreaseStockAsync(AppDbContext context, int productId, int quantity)
{
await using var transaction = await context.Database
.BeginTransactionAsync(IsolationLevel.ReadCommitted);
try
{
var product = await context.Products.FirstAsync(p => p.Id == productId);
if (product.Stock < quantity)
throw new InvalidOperationException("Stock is not enough");
product.Stock -= quantity;
await context.SaveChangesAsync();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
توضیح:
- تراکنش با
ReadCommittedشروع میشه. - محصول خونده میشه و موجودی بهروزرسانی میشه.
- اگر خطایی رخ بده، Rollback میشه.
- این کد جلوی Dirty Read رو میگیره ولی ممکنه Lost Update یا Non-Repeatable Read پیش بیاد.
مثال ۲: تراکنش Serializable برای جلوگیری از Phantom
public async Task<List<Order>> GetPendingOrdersWithLockAsync(AppDbContext context)
{
await using var transaction = await context.Database
.BeginTransactionAsync(IsolationLevel.Serializable);
var orders = await context.Orders
.Where(o => o.Status == "Pending")
.ToListAsync();
// تا زمانی که تراکنش باز است، تراکنشهای دیگر نمیتوانند
// ردیف جدیدی در محدوده این شرط اضافه کنند.
await transaction.CommitAsync();
return orders;
}
توضیح:
- با
Serializable، قفلهای Range اعمال میشن. - تراکنشهای دیگر نمیتونن یک ردیف جدید با همان وضعیت اضافه کنن.
- این سطح برای گزارشگیریهای حساس مناسبه، ولی قفل زیاد میزنه.
مثال ۳: استفاده از Snapshot
public async Task<Product> GetProductSnapshotAsync(AppDbContext context, int productId)
{
await using var transaction = await context.Database
.BeginTransactionAsync(IsolationLevel.Snapshot);
var product = await context.Products.FirstAsync(p => p.Id == productId);
await transaction.CommitAsync();
return product;
}
توضیح:
Snapshotقفل نمیزنه و نسخهسازی ردیف انجام میده.- این سطح Optimistic Concurrency هست؛ ممکنه در Update با خطای conflict مواجه بشی.
- برای استفاده از
Snapshotروی SQL Server باید تنظیم دیتابیس فعال باشه:ALTER DATABASE YourDb SET ALLOW_SNAPSHOT_ISOLATION ON;
مثال ۴: Savepoint برای Rollback جزئی
public async Task ProcessWithSavepointAsync(AppDbContext context)
{
await using var transaction = await context.Database
.BeginTransactionAsync(IsolationLevel.ReadCommitted);
try
{
context.Logs.Add(new Log { Step = "Step1" });
await context.SaveChangesAsync();
await transaction.CreateSavepointAsync("AfterStep1");
context.Logs.Add(new Log { Step = "Step2" });
await context.SaveChangesAsync();
// فرض کن Step2 نباید انجام میشد
await transaction.RollbackSavepointAsync("AfterStep1");
context.Logs.Add(new Log { Step = "Step3" });
await context.SaveChangesAsync();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
نتیجه:
Step1وStep3ثبت میشن.Step2حذف میشه بدون اینکه کل تراکنش Rollback بشه.
۶. مثال واقعی و کاربردی
پروژه کوچک: انتقال وجه بین دو حساب
این سناریو واقعیترین مثال برای تراکنش و ایزولیشنه.
مدل
public class Account
{
public int Id { get; set; }
public string Owner { get; set; } = string.Empty;
public decimal Balance { get; set; }
}
سرویس انتقال وجه
using System.Data;
using Microsoft.EntityFrameworkCore;
public class TransferService
{
private readonly AppDbContext _context;
public TransferService(AppDbContext context)
{
_context = context;
}
public async Task TransferAsync(int sourceId, int destinationId, decimal amount)
{
if (amount <= 0)
throw new ArgumentException("Amount must be positive");
// در SQL Server، UPDLOCK از Lost Update جلوگیری میکنه.
await using var transaction = await _context.Database
.BeginTransactionAsync(IsolationLevel.Serializable);
try
{
var source = await _context.Accounts
.FromSqlInterpolated($"SELECT * FROM Accounts WITH (UPDLOCK) WHERE Id = {sourceId}")
.SingleAsync();
var destination = await _context.Accounts
.FromSqlInterpolated($"SELECT * FROM Accounts WITH (UPDLOCK) WHERE Id = {destinationId}")
.SingleAsync();
if (source.Balance < amount)
throw new InvalidOperationException("Insufficient balance");
source.Balance -= amount;
destination.Balance += amount;
await _context.SaveChangesAsync();
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
}
}
توضیح:
IsolationLevel.Serializableقفلهای Range اعمال میکنه.WITH (UPDLOCK)مطمئن میشه که ردیفها برای Update قفل شدن و تراکنش دیگر نمیتونه همزمان تغییرش بده.- اگر موجودی کافی نباشه، Rollback انجام میشه.
- اگر خطای دیتابیس یا Cancellation رخ بده، کل عملیات برمیگرده.
ثبت سرویس
builder.Services.AddScoped<TransferService>();
استفاده در Controller
[ApiController]
[Route("api/[controller]")]
public class TransfersController : ControllerBase
{
private readonly TransferService _transferService;
public TransfersController(TransferService transferService)
{
_transferService = transferService;
}
[HttpPost]
public async Task<IActionResult> Transfer(
[FromQuery] int sourceId,
[FromQuery] int destinationId,
[FromQuery] decimal amount,
CancellationToken cancellationToken)
{
try
{
await _transferService.TransferAsync(sourceId, destinationId, amount);
return Ok("Transfer completed");
}
catch (InvalidOperationException ex)
{
return BadRequest(ex.Message);
}
}
}
۷. مباحث پیشرفته
۱. انتخاب سطح ایزولیشن بر اساس مشکل
| مشکل | سطح مناسب |
|---|---|
| فقط خواندن گزارش بدون نگرانی از داده کثیف | ReadUncommitted |
| عملیات عادی با جلوگیری از Dirty Read | ReadCommitted |
| نیاز به قفل روی ردیفها در طول تراکنش | RepeatableRead |
| جلوگیری از Phantom و تضمین کامل | Serializable |
| خواندن زیاد و نوشتن کم با همزمانی بالا | Snapshot |
۲. Snapshot Isolation روی SQL Server
برای فعالسازی:
ALTER DATABASE YourDb SET ALLOW_SNAPSHOT_ISOLATION ON;
برای فعالسازی ReadCommitted Snapshot (برای همه کوئریها):
ALTER DATABASE YourDb SET READ_COMMITTED_SNAPSHOT ON;
تفاوت مهم:
ALLOW_SNAPSHOT_ISOLATIONفقط برای تراکنشهایی هست که صریحاًIsolationLevel.Snapshotمیگیرند.READ_COMMITTED_SNAPSHOTرفتار سطحReadCommittedرا بهصورت Snapshot تغییر میده.
۳. TransactionScope برای Multi-Database
اگر چند DbContext یا حتی چند دیتابیس مختلف داری، EF Core به تنهایی تراکنش توزیعشده نمیسازه.
باید از TransactionScope استفاده کنی:
using System.Transactions;
using var scope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions
{
IsolationLevel = IsolationLevel.Serializable,
Timeout = TimeSpan.FromSeconds(30)
},
TransactionScopeAsyncFlowOption.Enabled);
await using var context1 = new AppDbContext(options1);
await using var context2 = new AppDbContext(options2);
// عملیات روی context1
// عملیات روی context2
scope.Complete();
نکتهها:
- حتماً
TransactionScopeAsyncFlowOption.Enabledبذار، مخصوصاً وقتی async/await داری. TransactionScopeممکنه تراکنش توزیعشده رو فعال کنه که نیازمند تنظیمات خاص روی دیتابیس و DTC هست.
۴. Savepoint در تراکنش
Savepoint برای Rollback جزئی عالیه:
await transaction.CreateSavepointAsync("Savepoint1");
// ...
await transaction.RollbackSavepointAsync("Savepoint1");
await transaction.ReleaseSavepointAsync("Savepoint1");
۵. استفاده از DbTransaction خارجی
اگر قبلاً یک DbTransaction از SqlConnection ساختهای، میتونی EF Core رو بهش متصل کنی:
await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
await using var dbTransaction = await connection.BeginTransactionAsync(
IsolationLevel.ReadCommitted);
await using var context = new AppDbContext(options);
await context.Database.UseTransactionAsync(dbTransaction);
// ... عملیات ...
await dbTransaction.CommitAsync();
۸. اشتباهات رایج
۱. فراموش کردن Commit یا Rollback
// ❌ اشتباه
var transaction = await context.Database.BeginTransactionAsync();
context.Products.Add(new Product());
await context.SaveChangesAsync();
// فراموش شده: transaction.CommitAsync()
✅ درست:
await using var transaction = await context.Database.BeginTransactionAsync();
context.Products.Add(new Product());
await context.SaveChangesAsync();
await transaction.CommitAsync();
۲. استفاده از TransactionScope بدون Async Flow
// ❌ اشتباه در ASP.NET Core با async/await
using var scope = new TransactionScope();
await SomeAsyncMethod();
✅ درست:
using var scope = new TransactionScope(
TransactionScopeAsyncFlowOption.Enabled);
await SomeAsyncMethod();
۳. تراکنش بسیار طولانی
این یک اشتباه جدی است:
- قفلها روی دیتابیس نگه داشته میشوند.
- کاربران دیگر معطل میشوند.
- احتمال Deadlock بالا میرود.
✅ همیشه تراکنش را کوتاه کن؛ عملیات I/O خارجی، ارسال ایمیل، صدا زدن API را بیرون از تراکنش انجام بده.
۴. استفاده از Serializable برای همه چیز
قویترین سطح همیشه بهترین نیست.
اگر فقط یک Update ساده داری، ReadCommitted معمولاً کافیه.
۵. استفاده از ReadUncommitted برای داده مالی
این سطح اجازه خواندن داده کثیف را میده.
برای موجودی حساب، گزارش مالی، رزرو، هرگز ReadUncommitted نده.
۶. تصور Nested Transaction در EF Core
EF Core از Nested Transaction پشتیبانی نمیکنه.
اگر بخوای یک تراکنش داخل تراکنش دیگر باز کنی، معمولاً یا خطا میگیری یا رفتار نامشخصی رخ میده.
بهجاش از Savepoint استفاده کن.
۷. فراموش کردن Rollback در Catch
// ❌ اشتباه
try
{
// ...
await transaction.CommitAsync();
}
catch
{
// Rollback فراموش شده
}
✅ درست:
try
{
// ...
await transaction.CommitAsync();
}
catch
{
await transaction.RollbackAsync();
throw;
}
۹. بهترین روشها (Best Practices)
۱. همیشه از await using استفاده کن
await using var transaction = await context.Database.BeginTransactionAsync();
این کار باعث میشه اگر CommitAsync فراموش بشه، Dispose تراکنش را Rollback کنه.
۲. سطح ایزولیشن را آگاهانه انتخاب کن
| سناریو | سطح پیشنهادی |
|---|---|
| CRUD ساده | ReadCommitted |
| گزارشگیری سریع با احتمال خطای کم | ReadUncommitted |
| خواندن چندباره همان ردیف | RepeatableRead |
| رزرو، انتقال مالی | Serializable |
| خواندن زیاد، نوشتن کم | Snapshot |
۳. تراکنش را تا جای ممکن کوتاه کن
- قبل از تراکنش، Validation انجام بده.
- داخل تراکنش فقط عملیات دیتابیس انجام بده.
- عملیات Logging غیرضروری داخل تراکنش نگذار.
۴. از Async API استفاده کن
در ASP.NET Core همیشه از BeginTransactionAsync، CommitAsync، RollbackAsync استفاده کن تا Thread Pool مسدود نشه.
۵. برای Lost Update از Lock Hint یا Concurrency Token استفاده کن
در SQL Server:
var account = await context.Accounts
.FromSqlInterpolated($"SELECT * FROM Accounts WITH (UPDLOCK) WHERE Id = {id}")
.SingleAsync();
یا از ConcurrencyToken و RowVersion استفاده کن.
۶. Deadlock را مدیریت کن
- همیشه ترتیب یکسان برای قفلکردن منابع داشته باش.
- زمان تراکنش را کوتاه کن.
- از Retry Policy استفاده کن.
۷. برای هر Case تست همزمانی بنویس
با Task.WhenAll یا مشابه، همزمانی را تست کن:
var tasks = Enumerable.Range(0, 10)
.Select(i => transferService.TransferAsync(1, 2, 100));
await Task.WhenAll(tasks);
۱۰. منابع و ادامه مسیر
- Microsoft Learn: EF Core Transactions
- System.Data.IsolationLevel
- SQL Server Transaction Isolation Levels
- TransactionScope Class
- SQL Server Locking
اگه میخوای عمیقتر بشی:
- کتاب
SQL Server Internals: Transaction Logging and Recovery - دورههای Performance Tuning در SQL Server
- مقالات درباره Concurrency در EF Core با Pessimistic و Optimistic Concurrency
۱۱. مرجع کامل توابع و متدها (API Deep Dive)
در این بخش، متدهای اصلی مرتبط با Isolation Transaction در EF Core را با جزئیات کامل بررسی میکنیم.
📌 متد: BeginTransaction(IsolationLevel)
امضای متد (Signature):
public static IDbContextTransaction BeginTransaction(
this DatabaseFacade databaseFacade,
IsolationLevel isolationLevel
);
ورودیها (Parameters):
| نام پارامتر | نوع (Type) | اجباری؟ | توضیح دقیق | مثال مقدار |
|---|---|---|---|---|
databaseFacade |
DatabaseFacade |
بله (گیرنده Extension) | دسترسی به دیتابیس از طریق context.Database |
context.Database |
isolationLevel |
System.Data.IsolationLevel |
بله | سطح ایزولیشن تراکنش | IsolationLevel.ReadCommitted |
مقدار برگشتی (Return Value):
- نوع:
IDbContextTransaction - شیء تراکنش که میتونی
CommitیاRollbackرو روی آن صدا بزنی. - اگر BeginTransaction ناموفق باشد، Exception پرتاب میشود نه null.
کاری که انجام میده (گام به گام):
۱. بررسی میکند که این DbContext به یک Provider متصل است.
۲. بررسی میکند که قبلاً تراکنش باز وجود ندارد یا اینکه Provider از تراکنش پشتیبانی میکند.
۳. از DbConnection مربوط به Provider، تراکنش با سطح ایزولیشن مشخص را آغاز میکند.
۴. تراکنش را در یک شیء IDbContextTransaction میپیچد و برمیگرداند.
مثال ساده و قابل اجرا:
using System.Data;
using Microsoft.EntityFrameworkCore;
using var context = new AppDbContext(options);
using var transaction = context.Database
.BeginTransaction(IsolationLevel.ReadCommitted);
var product = context.Products.First(p => p.Id == 1);
product.Stock -= 5;
context.SaveChanges();
transaction.Commit();
مثال واقعی و کاربردی:
public void TransferMoney(int sourceId, int destinationId, decimal amount)
{
using var transaction = _context.Database
.BeginTransaction(IsolationLevel.Serializable);
try
{
var source = _context.Accounts
.FromSqlRaw("SELECT * FROM Accounts WITH (UPDLOCK) WHERE Id = {0}", sourceId)
.Single();
var destination = _context.Accounts
.FromSqlRaw("SELECT * FROM Accounts WITH (UPDLOCK) WHERE Id = {0}", destinationId)
.Single();
if (source.Balance < amount)
throw new InvalidOperationException("Insufficient balance");
source.Balance -= amount;
destination.Balance += amount;
_context.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
خطاها و Exceptions احتمالی:
| Exception | زمان رخ دادن | راهحل |
|---|---|---|
InvalidOperationException |
تراکنش قبلی باز است، یا Provider نصب نشده است | اطمینان از بستهبودن تراکنش قبلی یا ثبت Provider |
ArgumentException |
مقدار IsolationLevel نامعتبر است | استفاده از enum معتبر |
NotSupportedException |
Provider از سطح ایزولیشن پشتیبانی نمیکند | تغییر سطح به پشتیبانیشده |
DbException |
خطا در اتصال به دیتابیس | بررسی Connection String و در دسترس بودن دیتابیس |
Overloadها:
// تراکنش با سطح ایزولیشن پیشفرض
IDbContextTransaction BeginTransaction();
// با سطح ایزولیشن مشخص
IDbContextTransaction BeginTransaction(IsolationLevel isolationLevel);
اشتباهات رایج:
- ❌ باز کردن تراکنش بعد از بستن Connection → ✅ همیشه Connection باید باز باشد.
- ❌ استفاده از
BeginTransactionدر متد async → ✅ ازBeginTransactionAsyncاستفاده کن. - ❌ فراموش کردن Commit → ✅ از
usingیاtry/finallyاستفاده کن.
متدهای مرتبط:
BeginTransactionAsyncCommitRollbackUseTransaction
📌 متد: BeginTransactionAsync(IsolationLevel, CancellationToken)
امضای متد (Signature):
public static Task<IDbContextTransaction> BeginTransactionAsync(
this DatabaseFacade databaseFacade,
IsolationLevel isolationLevel,
CancellationToken cancellationToken = default
);
ورودیها (Parameters):
| نام پارامتر | نوع (Type) | اجباری؟ | توضیح دقیق | مثال مقدار |
|---|---|---|---|---|
databaseFacade |
DatabaseFacade |
بله (گیرنده Extension) | دسترسی به دیتابیس | context.Database |
isolationLevel |
IsolationLevel |
بله | سطح ایزولیشن تراکنش | IsolationLevel.Snapshot |
cancellationToken |
CancellationToken |
خیر | سیگنال لغو عملیات | HttpContext.RequestAborted |
مقدار برگشتی (Return Value):
- نوع:
Task<IDbContextTransaction> - پس از await کردن،
IDbContextTransactionبازگشت داده میشود. - اگر Operation لغو شود،
OperationCanceledExceptionپرتاب میشود.
کاری که انجام میده (گام به گام):
۱. بررسی CancellationToken برای لغو.
۲. بررسی Provider و اتصال دیتابیس.
۳. شروع غیرهمزمان تراکنش روی DbConnection با سطح ایزولیشن مشخص.
۴. بازگرداندن Task<IDbContextTransaction>.
مثال ساده و قابل اجرا:
await using var context = new AppDbContext(options);
await using var transaction = await context.Database
.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken);
var order = new Order { TotalPrice = 250 };
context.Orders.Add(order);
await context.SaveChangesAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
مثال واقعی و کاربردی:
public async Task ReserveSeatAsync(int seatId, CancellationToken cancellationToken)
{
await using var transaction = await _context.Database
.BeginTransactionAsync(IsolationLevel.Serializable, cancellationToken);
try
{
var seat = await _context.Seats
.FirstAsync(s => s.Id == seatId, cancellationToken);
if (seat.IsReserved)
throw new InvalidOperationException("Seat already reserved");
seat.IsReserved = true;
await _context.SaveChangesAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
}
catch
{
await transaction.RollbackAsync(cancellationToken);
throw;
}
}
خطاها و Exceptions احتمالی:
| Exception | زمان رخ دادن | راهحل |
|---|---|---|
OperationCanceledException |
لغو از طریق CancellationToken | مدیریت لغو در Controller |
InvalidOperationException |
تراکنش باز موجود یا Provider نامعتبر | عدم باز کردن تراکنش دوم |
DbException |
خطای دیتابیس | بررسی اتصال |
NotSupportedException |
سطح ایزولیشن پشتیبانی نمیشود | تغییر سطح |
Overloadها:
Task<IDbContextTransaction> BeginTransactionAsync(CancellationToken cancellationToken = default);
Task<IDbContextTransaction> BeginTransactionAsync(IsolationLevel isolationLevel, CancellationToken cancellationToken = default);
اشتباهات رایج:
- ❌ استفاده از
usingبه جایawait using→ ✅ برای Async Dispose بایدawait usingبذاری. - ❌ عدم ارسال CancellationToken از Controller → ✅ همیشه
cancellationTokenرا پاس بده. - ❌ فراخوانی
BeginTransactionAsyncبا Connection بسته → ✅ ابتدا Connection را باز کن.
متدهای مرتبط:
BeginTransactionCommitAsyncRollbackAsyncCreateSavepointAsync
📌 متد: Commit()
امضای متد (Signature):
public void Commit();
ورودیها (Parameters):
| نام پارامتر | نوع | اجباری؟ | توضیح دقیق | مثال مقدار |
|---|---|---|---|---|
| — | — | — | هیچ پارامتری ندارد | — |
مقدار برگشتی (Return Value):
- نوع:
void - چیزی برنمیگرداند. در صورت موفقیت، تغییرات روی دیتابیس دائمی میشوند.
کاری که انجام میده (گام به گام):
۱. بررسی میکند که تراکنش هنوز فعال است و Commit/Rollback نشده است.
۲. گرفتن DbTransaction زیرین.
۳. فراخوانی Commit بر روی DbTransaction ADO.NET.
۴. انتشار تغییرات و پایان تراکنش.
مثال ساده و قابل اجرا:
using var transaction = context.Database.BeginTransaction();
var user = new User { Name = "Ali" };
context.Users.Add(user);
context.SaveChanges();
transaction.Commit();
مثال واقعی و کاربردی:
public void AddOrder(Order order)
{
using var transaction = _context.Database.BeginTransaction();
try
{
_context.Orders.Add(order);
_context.SaveChanges();
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
خطاها و Exceptions احتمالی:
| Exception | زمان رخ دادن | راهحل |
|---|---|---|
InvalidOperationException |
تراکنش قبلاً Commit یا Rollback شده باشد | فقط یکبار Commit/Rollback کن |
ObjectDisposedException |
تراکنش Dispose شده باشد | از Commit قبل از Dispose استفاده کن |
DbException |
خطای دیتابیس هنگام Commit | مدیریت خطا و Rollback |
Overloadها:
- دارد: فقط نسخه Sync.
اشتباهات رایج:
- ❌ صدا زدن
Commitبعد ازDispose→ ✅ Commit قبل از Dispose. - ❌ فرض اینکه بعد از Commit باز هم میشه Rollback کرد → ✅ پس از Commit تراکنش تمام شده است.
متدهای مرتبط:
CommitAsyncRollbackBeginTransaction
📌 متد: CommitAsync(CancellationToken)
امضای متد (Signature):
public Task CommitAsync(CancellationToken cancellationToken = default);
ورودیها (Parameters):
| نام پارامتر | نوع (Type) | اجباری؟ | توضیح دقیق | مثال مقدار |
|---|---|---|---|---|
cancellationToken |
CancellationToken |
خیر | سیگنال لغو عملیات Commit | cancellationToken |
مقدار برگشتی (Return Value):
- نوع:
Task - عملیات غیرهمزمان Commit را اجرا میکند. در صورت لغو،
OperationCanceledExceptionپرتاب میشود.
کاری که انجام میده (گام به گام):
۱. بررسی میکند که تراکنش فعال است.
۲. گرفتن DbTransaction زیرین.
۳. فراخوانی CommitAsync بر روی DbTransaction با همان CancellationToken.
۴. برگرداندن Task برای await.
مثال ساده و قابل اجرا:
await using var transaction = await context.Database
.BeginTransactionAsync();
context.Products.Add(new Product { Name = "Book" });
await context.SaveChangesAsync();
await transaction.CommitAsync();
مثال واقعی و کاربردی:
public async Task CompleteOrderAsync(int orderId, CancellationToken cancellationToken)
{
await using var transaction = await _context.Database
.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken);
var order = await _context.Orders.FirstAsync(o => o.Id == orderId, cancellationToken);
order.Status = "Completed";
await _context.SaveChangesAsync(cancellationToken);
await transaction.CommitAsync(cancellationToken);
}
خطاها و Exceptions احتمالی:
| Exception | زمان رخ دادن | راهحل |
|---|---|---|
OperationCanceledException |
لغو از طریق CancellationToken | مدیریت لغو |
InvalidOperationException |
تراکنش قبلاً پایان یافته | فقط یکبار Commit |
DbException |
خطای Commit در دیتابیس | Rollback در Catch |
Overloadها:
Task CommitAsync();
Task CommitAsync(CancellationToken cancellationToken);
اشتباهات رایج:
- ❌ استفاده از
Commit()در متد async → ✅ ازCommitAsyncاستفاده کن. - ❌ نگه نداشتن
awaitدر مقابلCommitAsync→ ✅ همیشهawaitکن.
متدهای مرتبط:
CommitRollbackAsyncBeginTransactionAsync
📌 متد: Rollback()
امضای متد (Signature):
public void Rollback();
ورودیها (Parameters):
| نام پارامتر | نوع | اجباری؟ | توضیح دقیق | مثال مقدار |
|---|---|---|---|---|
| — | — | — | هیچ پارامتری ندارد | — |
مقدار برگشتی (Return Value):
- نوع:
void - تمام تغییرات انجامشده در تراکنش جاری را بازمیگرداند.
کاری که انجام میده (گام به گام):
۱. بررسی میکند که تراکنش هنوز فعال است.
۲. گرفتن DbTransaction زیرین.
۳. فراخوانی Rollback بر روی DbTransaction.
۴. لغو تمام عملیات و همه Savepointها.
مثال ساده و قابل اجرا:
using var transaction = context.Database.BeginTransaction();
try
{
context.Users.Add(new User { Name = "Ali" });
context.SaveChanges();
throw new InvalidOperationException("Simulated error");
}
catch
{
transaction.Rollback();
throw;
}
مثال واقعی و کاربردی:
public void UpdateInventory(int productId, int quantity)
{
using var transaction = _context.Database.BeginTransaction();
try
{
var product = _context.Products.First(p => p.Id == productId);
product.Stock -= quantity;
_context.SaveChanges();
if (product.Stock < 0)
throw new InvalidOperationException("Negative stock");
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
خطاها و Exceptions احتمالی:
| Exception | زمان رخ دادن | راهحل |
|---|---|---|
InvalidOperationException |
تراکنش قبلاً Commit/Rollback شده | فقط یکبار Rollback کن |
ObjectDisposedException |
تراکنش Dispose شده | Rollback قبل از Dispose |
DbException |
خطا در دیتابیس | مدیریت خطا |
Overloadها:
- فقط نسخه Sync.
اشتباهات رایج:
- ❌ استفاده از
Rollbackبعد ازCommit→ ✅ پس از Commit نمیشه Rollback کرد. - ❌ ننوشتن
throwبعد از Rollback در Catch → ✅ همیشهthrowکن تا خطا بالا برود.
متدهای مرتبط:
RollbackAsyncCommitRollbackSavepoint
📌 متد: RollbackAsync(CancellationToken)
امضای متد (Signature):
public Task RollbackAsync(CancellationToken cancellationToken = default);
ورودیها (Parameters):
| نام پارامتر | نوع (Type) | اجباری؟ | توضیح دقیق | مثال مقدار |
|---|---|---|---|---|
cancellationToken |
CancellationToken |
خیر | سیگنال لغو عملیات Rollback | cancellationToken |
مقدار برگشتی (Return Value):
- نوع:
Task - عملیات غیرهمزمان Rollback را اجرا میکند.
کاری که انجام میده (گام به گام):
۱. بررسی میکند که تراکنش فعال است.
۲. گرفتن DbTransaction زیرین.
۳. فراخوانی RollbackAsync بر روی DbTransaction.
۴. بازگرداندن Task برای await.
مثال ساده و قابل اجرا:
await using var transaction = await context.Database
.BeginTransactionAsync();
try
{
context.Users.Add(new User { Name = "Ali" });
await context.SaveChangesAsync();
throw new InvalidOperationException("Simulated error");
}
catch
{
await transaction.RollbackAsync();
throw;
}
مثال واقعی و کاربردی:
public async Task ProcessPaymentAsync(Payment payment, CancellationToken cancellationToken)
{
await using var transaction = await _context.Database
.BeginTransactionAsync(IsolationLevel.ReadCommitted, cancellationToken);
try
{
_context.Payments.Add(payment);
await _context.SaveChangesAsync(cancellationToken);
// فرض کن یک سرویس خارجی خطا میدهد
if (payment.Amount <= 0)
throw new InvalidOperationException("Invalid amount");
await transaction.CommitAsync(cancellationToken);
}
catch
{
await transaction.RollbackAsync(cancellationToken);
throw;
}
}
خطاها و Exceptions احتمالی:
| Exception | زمان رخ دادن | راهحل |
|---|---|---|
OperationCanceledException |
لغو از طریق CancellationToken | مدیریت لغو |
InvalidOperationException |
تراکنش قبلاً پایان یافته | فقط یکبار Rollback |
DbException |
خطای دیتابیس | مدیریت خطا |
Overloadها:
Task RollbackAsync();
Task RollbackAsync(CancellationToken cancellationToken);
اشتباهات رایج:
- ❌ استفاده از
Rollback()در متد async → ✅ ازRollbackAsyncاستفاده کن. - ❌ حذف
throwپس از Rollback → ✅ حتماًthrowکن.
متدهای مرتبط:
RollbackCommitAsyncRollbackSavepointAsync
📌 متد: CreateSavepoint(string)
امضای متد (Signature):
public void CreateSavepoint(string name);
ورودیها (Parameters):
| نام پارامتر | نوع (Type) | اجباری؟ | توضیح دقیق | مثال مقدار |
|---|---|---|---|---|
name |
string |
بله | نام Savepoint برای Rollback جزئی | "AfterInsert" |
مقدار برگشتی (Return Value):
- نوع:
void - چیزی برنمیگرداند. یک نقطه ذخیره داخل تراکنش ایجاد میکند.
کاری که انجام میده (گام به گام):
۱. بررسی میکند که تراکنش جاری فعال است.
۲. بررسی میکند که Provider از Savepoint پشتیبانی میکند (SupportsSavepoints).
۳. صدور دستور SAVE TRANSACTION name (در SQL Server) روی اتصال جاری.
۴. ثبت Savepoint برای استفاده بعدی.
مثال ساده و قابل اجرا:
using var transaction = context.Database.BeginTransaction();
context.Users.Add(new User { Name = "A" });
context.SaveChanges();
transaction.CreateSavepoint("AfterA");
context.Users.Add(new User { Name = "B" });
context.SaveChanges();
transaction.RollbackSavepoint("AfterA"); // افزودن B لغو میشود
transaction.Commit(); // فقط A ثبت میشود
مثال واقعی و کاربردی:
public void CreateOrderWithOptionalLogging(Order order, bool addLog)
{
using var transaction = _context.Database.BeginTransaction();
_context.Orders.Add(order);
_context.SaveChanges();
transaction.CreateSavepoint("OrderSaved");
if (addLog)
{
_context.Logs.Add(new Log { Message = "Order created" });
_context.SaveChanges();
}
// اگر تصمیم به حذف Log بگیریم:
if (!addLog)
{
transaction.RollbackSavepoint("OrderSaved");
}
transaction.Commit();
}
خطاها و Exceptions احتمالی:
| Exception | زمان رخ دادن | راهحل |
|---|---|---|
InvalidOperationException |
تراکنش فعال نیست | ابتدا تراکنش را Begin کن |
ArgumentException |
name خالی یا null است |
نام معتبر بده |
NotSupportedException |
Provider از Savepoint پشتیبانی نمیکند | بررسی SupportsSavepoints |
Overloadها:
void CreateSavepoint(string name);
Task CreateSavepointAsync(string name, CancellationToken cancellationToken = default);
اشتباهات رایج:
- ❌ استفاده از Savepoint بدون بررسی
SupportsSavepoints→ ✅ ابتدا بررسی کن. - ❌ نام تکراری مبهم → ✅ نام یکتا و معنادار بده.
متدهای مرتبط:
RollbackSavepointReleaseSavepointBeginTransaction
📌 متد: RollbackSavepoint(string)
امضای متد (Signature):
public void RollbackSavepoint(string name);
ورودیها (Parameters):
| نام پارامتر | نوع (Type) | اجباری؟ | توضیح دقیق | مثال مقدار |
|---|---|---|---|---|
name |
string |
بله | نام Savepoint برای Rollback به آن نقطه | "OrderSaved" |
مقدار برگشتی (Return Value):
- نوع:
void - تغییرات بعد از Savepoint را بازمیگرداند، اما کل تراکنش را Rollback نمیکند.
کاری که انجام میده (گام به گام):
۱. بررسی میکند که تراکنش جاری فعال است.
۲. بررسی میکند که Savepoint با نام دادهشده وجود دارد.
۳. اجرای دستور ROLLBACK TRANSACTION name (در SQL Server).
۴. حفظ تغییرات قبل از Savepoint و لغو تغییرات پس از آن.
مثال ساده و قابل اجرا:
using var transaction = context.Database.BeginTransaction();
context.Users.Add(new User { Name = "A" });
context.SaveChanges();
transaction.CreateSavepoint("AfterA");
context.Users.Add(new User { Name = "B" });
context.SaveChanges();
transaction.RollbackSavepoint("AfterA"); // B حذف میشود
transaction.Commit();
مثال واقعی و کاربردی:
public void SetupUserAccount(User user)
{
using var transaction = _context.Database.BeginTransaction();
_context.Users.Add(user);
_context.SaveChanges();
transaction.CreateSavepoint("UserSaved");
try
{
_context.Accounts.Add(new Account { UserId = user.Id, Balance = 0 });
_context.SaveChanges();
}
catch
{
// اگر ساخت Account خطا خورد، فقط Account برگردد، نه User
transaction.RollbackSavepoint("UserSaved");
}
transaction.Commit();
}
خطاها و Exceptions احتمالی:
| Exception | زمان رخ دادن | راهحل |
|---|---|---|
InvalidOperationException |
تراکنش فعال نیست یا Savepoint وجود ندارد | بررسی نام و تراکنش |
ArgumentException |
name خالی یا null است |
نام معتبر بده |
NotSupportedException |
Provider از Savepoint پشتیبانی نمیکند | بررسی SupportsSavepoints |
Overloadها:
void RollbackSavepoint(string name);
Task RollbackSavepointAsync(string name, CancellationToken cancellationToken = default);
اشتباهات رایج:
- ❌ Rollback به Savepointای که هرگز ساخته نشده → ✅ ابتدا
CreateSavepointکن. - ❌ فراموش کردن
ReleaseSavepointپس از اتمام کار → ✅ در صورت نیاز Release کن.
متدهای مرتبط:
CreateSavepointReleaseSavepointRollback
۱۲. مقایسه کلاسها/توابع مشابه
مقایسه Database.BeginTransaction با SqlConnection.BeginTransaction و TransactionScope
| ویژگی | Database.BeginTransaction (EF Core) |
SqlConnection.BeginTransaction (ADO.NET) |
TransactionScope |
|---|---|---|---|
| سطح انتزاع | بالا، روی DbContext | پایین، روی DbConnection | بالاتر، روی TransactionManager |
| استفاده آسان | بله | متوسط | بله برای Multi-Database |
| پشتیبانی Async | بله | بله | بله (با Enabled) |
| تراکنش توزیعشده | خیر | خیر | بله |
| Commit/Rollback دستی | بله | بله | خیر (خودکار با Complete) |
| Nested Transaction | پشتیبانی نمیشود؛ Savepoint | پشتیبانی نمیشود؛ Savepoint | گاهی پشتیبانی نسبی |
| مناسب برای | عملیات روی یک DbContext | کنترل دقیق Connection | چند DbContext یا چند دیتابیس |
| نیاز به پکیج | فقط EF Core Provider | System.Data.SqlClient یا Microsoft.Data.SqlClient |
System.Transactions |
مقایسه سطحهای ایزولیشن
| سطح | جلوگیری از Dirty Read | جلوگیری از Non-Repeatable | جلوگیری از Phantom | قفلگذاری | کارایی |
|---|---|---|---|---|---|
| ReadUncommitted | ❌ | ❌ | ❌ | خیلی کم | خیلی بالا |
| ReadCommitted | ✅ | ❌ | ❌ | متوسط | بالا |
| RepeatableRead | ✅ | ✅ | ❌ | زیاد | متوسط |
| Serializable | ✅ | ✅ | ✅ | خیلی زیاد | پایین |
| Snapshot | ✅ | ✅ | ✅ | Optimistic | بالا |
۱۳. سناریوهای واقعی (Real-World Scenarios)
سناریو ۱: انتقال وجه بین دو حساب
نیاز:
اگر پول از یک حساب کم شد و به حساب دیگر اضافه نشد، نباید هیچ تغییری ثبت شود.
سطح ایزولیشن: Serializable یا RepeatableRead همراه با UPDLOCK.
راهحل:
await using var transaction = await _context.Database
.BeginTransactionAsync(IsolationLevel.Serializable);
var source = await _context.Accounts
.FromSqlRaw("SELECT * FROM Accounts WITH (UPDLOCK) WHERE Id = {0}", sourceId)
.SingleAsync();
var destination = await _context.Accounts
.FromSqlRaw("SELECT * FROM Accounts WITH (UPDLOCK) WHERE Id = {0}", destinationId)
.SingleAsync();
source.Balance -= amount;
destination.Balance += amount;
await _context.SaveChangesAsync();
await transaction.CommitAsync();
سناریو ۲: مدیریت موجودی انبار
نیاز:
اگر ۱۰ کاربر همزمان ۱۰ محصول خریدن و موجودی واقعی ۵ است، فقط ۵ خرید باید موفق باشد.
سطح ایزولیشن: RepeatableRead یا Serializable.
راهحل:
await using var transaction = await _context.Database
.BeginTransactionAsync(IsolationLevel.RepeatableRead);
var product = await _context.Products
.FromSqlRaw("SELECT * FROM Products WITH (UPDLOCK) WHERE Id = {0}", productId)
.SingleAsync();
if (product.Stock < quantity)
throw new InvalidOperationException("Not enough stock");
product.Stock -= quantity;
await _context.SaveChangesAsync();
await transaction.CommitAsync();
سناریو ۳: رزرو صندلی
نیاز:
یک صندلی فقط یکبار رزرو شود.
سطح ایزولیشن: Serializable یا RepeatableRead با بررسی وضعیت.
راهحل:
await using var transaction = await _context.Database
.BeginTransactionAsync(IsolationLevel.Serializable);
var seat = await _context.Seats.SingleAsync(s => s.Id == seatId);
if (seat.IsReserved)
throw new InvalidOperationException("Seat already reserved");
seat.IsReserved = true;
await _context.SaveChangesAsync();
await transaction.CommitAsync();
سناریو ۴: چند دیتابیس یا چند Context
نیاز:
عملیات روی دو DbContext یا دو دیتابیس باید اتمیک باشد.
راهحل: TransactionScope
using var scope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions
{
IsolationLevel = IsolationLevel.ReadCommitted,
Timeout = TimeSpan.FromMinutes(1)
},
TransactionScopeAsyncFlowOption.Enabled);
await using var firstContext = new AppDbContext(firstOptions);
await using var secondContext = new AppDbContext(secondOptions);
// عملیات روی firstContext
// عملیات روی secondContext
scope.Complete();
۱۴. کارایی و Performance
چرا انتخاب Isolation Level روی کارایی اثر میگذارد؟
- سطوح بالاتر مثل
Serializableقفلهای بیشتر و طولانیتری اعمال میکنند. - قفلهای بیشتر یعنی Blocking بیشتر و در نتیجه انتظار بیشتر برای سایر تراکنشها.
- هرچه تراکنش طولانیتر باشد، احتمال Deadlock بالا میرود.
جدول تقریبی هزینه
| سطح Isolation | Locking | Blocking | Deadlock | Speed |
|---|---|---|---|---|
| ReadUncommitted | خیلی کم | خیلی کم | خیلی کم | خیلی بالا |
| ReadCommitted | کم | کم | کم | بالا |
| RepeatableRead | متوسط | متوسط | متوسط | متوسط |
| Serializable | زیاد | زیاد | زیاد | پایین |
| Snapshot | Optimistic | کم | کم | بالا (با هزینه TempDB) |
نکات بهینهسازی
۱. کمترین سطح ممکن را انتخاب کن
برای عملیات ساده CRUD، ReadCommitted معمولاً کافی است.
فقط وقتی واقعاً به قویترین سطح نیاز داری از Serializable استفاده کن.
۲. تراکنش را کوتاه نگه دار
// ❌ بد
await using var transaction = await context.Database.BeginTransactionAsync();
await SendEmailAsync(); // طولانی و غیرضروری داخل تراکنش
var product = await context.Products.FirstAsync();
await transaction.CommitAsync();
// ✅ خوب
await SendEmailAsync(); // قبل از تراکنش
await using var transaction = await context.Database.BeginTransactionAsync();
var product = await context.Products.FirstAsync();
await transaction.CommitAsync();
۳. از Index استفاده کن
اگر WHERE یا JOIN روی ستونهای بدون Index اجرا شود، دیتابیس Table Scan میکند و قفلهای اضافی میزند.
برای ستونهای مورد استفاده در تراکنشهای حساس، Index بساز.
۴. Snapshot برای کاهش Blocking
در سیستمهای خواندنزیاد/نوشتنکم، Snapshot میتواند همزمانی را بالا ببرد، اما:
- از فضای
tempdbاستفاده میکند. - باید
ALLOW_SNAPSHOT_ISOLATIONرا فعال کنی. - ممکن است
Update conflictرخ بدهد.
۵. مدیریت Deadlock با Retry
همیشه Deadlock امکان دارد. یک Retry Policy ساده:
public async Task ExecuteWithRetryAsync(Func<Task> operation, int maxRetries = 3)
{
for (int attempt = 1; attempt <= maxRetries; attempt++)
{
try
{
await operation();
return;
}
catch (SqlException ex) when (ex.Number == 1205 && attempt < maxRetries)
{
await Task.Delay(TimeSpan.FromMilliseconds(attempt * 200));
}
}
}
۱۵. چکلیست یادگیری
مفاهیم:
- تفاوت ACID با Isolation Level را میدانم.
- سه Anomaly یعنی Dirty Read، Non-Repeatable Read و Phantom Read را میشناسم.
- همه مقادیر
IsolationLevelرا میدانم. - میدانم هر سطح کدام Anomaly را کنترل میکند.
مهارتهای کدنویسی:
- میتوانم با
BeginTransactionAsyncتراکنش شروع کنم. - میتوانم
CommitAsyncوRollbackAsyncرا درست به کار ببرم. - میتوانم Savepoint بسازم و Rollback جزئی کنم.
- میتوانم در Catch تراکنش را Rollback کنم.
- میتوانم با
TransactionScopeچند Context را تراکنش بدهم.
انتخاب درست:
- برای عملیات مالی، سطح ایزولیشن مناسب را انتخاب میکنم.
- برای گزارشگیری سبک،
ReadUncommittedرا فقط وقتی قابل قبول است استفاده میکنم. - در سیستمهای خواندنزیاد،
Snapshotرا بررسی میکنم. - میدانم
Serializableچه زمانی ضروری است و چه زمانی فقط سربار اضافه ایجاد میکند.
تست:
- یک تست همزمانی با
Task.WhenAllنوشتهام. - رفتار Lost Update را با UPDLOCK یا Concurrency Token تست کردهام.
- Deadlock و Retry Policy را امتحان کردهام.