LiteOrm supports two transaction management approaches: declarative transactions and manual transactions.
| Scenario | Recommended Approach | Reason |
|---|---|---|
| Standard business service methods | Declarative | Clean code, clear boundaries |
| Need explicit control over commit/rollback timing | Manual | Finer granularity |
| Combining multiple DAO/Service writes | Declarative preferred | Closer to business encapsulation |
| Infrastructure layer, batch processing, special transaction boundaries | Manual | Better for fine-grained control |
Use the [Transaction] attribute to mark methods. The framework automatically manages transaction begin, commit, and rollback.
public class UserService : EntityService<User>
{
private readonly IOrderService _orderService;
public UserService(IOrderService orderService)
{
_orderService = orderService;
}
[Transaction]
public async Task CreateUserWithOrder(User user, Order order)
{
await InsertAsync(user);
order.UserId = user.Id;
await _orderService.InsertAsync(order);
}
}
Declarative transactions support nesting. Nested methods use the same transaction:
[Transaction]
public async Task TransferMoney(long fromId, long toId, decimal amount)
{
var fromAccount = await _accountService.GetObjectAsync(fromId);
var toAccount = await _accountService.GetObjectAsync(toId);
fromAccount.Balance -= amount;
toAccount.Balance += amount;
await _accountService.Update(fromAccount);
await Update(fromAccount); // Same transaction
await _accountService.Update(toAccount);
await Update(toAccount); // Same transaction
}
[Transaction] attribute requires Castle.Core dynamic proxy supportpublic and called through interface to take effect[Transaction]
public async Task SubmitOrderAsync(CreateOrderInput input)
{
var order = new Order
{
UserId = input.UserId,
Amount = input.Amount
};
await _orderService.InsertAsync(order);
foreach (var item in input.Items)
{
await _orderItemService.InsertAsync(new OrderItem
{
OrderId = order.Id,
ProductId = item.ProductId,
Quantity = item.Quantity
});
}
await _auditLogService.InsertAsync(new AuditLog
{
Action = "SubmitOrder",
RefId = order.Id.ToString()
});
}
This pattern is suitable for typical business transactions like “main table + details + audit log.”
LiteOrm.Demo\Demos\TransactionDemo.cs demonstrates a useful failure rollback scenario: create a user first, then insert an intentionally invalid sales record to trigger automatic transaction rollback.
var newUser = new User { UserName = "ThreeTierUser", Age = 25 };
var initialSale = new SalesRecord
{
ProductName = new string('A', 300), // Intentionally exceeds field length, triggers exception
Amount = 1
};
bool success = await factory.BusinessService
.RegisterUserWithInitialSaleAsync(newUser, initialSale);
This example is ideal for verifying “whether data already inserted in the main flow is also rolled back after an exception occurs.”
Control transactions manually through SessionManager.
var sessionManager = SessionManager.Current;
sessionManager.BeginTransaction();
try
{
// Execute multiple operations
await userService.InsertAsync(user);
await orderService.InsertAsync(order);
sessionManager.Commit();
}
catch
{
sessionManager.Rollback();
throw;
}
var sessionManager = SessionManager.Current;
sessionManager.BeginTransaction(IsolationLevel.ReadCommitted);
try
{
// Operations
sessionManager.Commit();
}
catch
{
sessionManager.Rollback();
throw;
}
var sessionManager = SessionManager.Current;
sessionManager.BeginTransaction(IsolationLevel.ReadCommitted);
try
{
var users = await userService.SearchAsync(u => u.Age >= 18);
sessionManager.Commit();
}
catch
{
sessionManager.Rollback();
throw;
}
LiteOrm’s transaction propagation behavior:
| Scenario | Behavior |
|---|---|
| No existing transaction | Creates new transaction |
| Has existing transaction | Joins existing transaction (nested) |
| Transaction failure | Full rollback |
LiteOrm uses SessionManager to manage database connections and transactions:
timestamp Relates to Transactionstimestamp-based optimistic concurrency and transactions are complementary, not competing, mechanisms:
timestamp checks prevent lost updates, where a later write silently overwrites an earlier one.A common combination is:
ObjectDAO<T>.Update(entity, timestamp) or UpdateAsync(entity, timestamp).false return value as a concurrency conflict and stop the workflow.[Transaction]
public async Task<bool> RenameUserAsync(int id, string newName)
{
var user = await _userViewDao.GetObject(id).FirstOrDefaultAsync();
if (user == null)
return false;
int originalVersion = user.Version;
user.UserName = newName;
user.Version = originalVersion + 1;
return await _userDao.UpdateAsync(user, originalVersion);
}
Recommendation:
timestamp checks when you need protection against concurrent overwrites.