-
Notifications
You must be signed in to change notification settings - Fork 4
platform sdk transactions
Transactions in the Genetec SDK are a local batching mechanism that groups multiple entity operations together and sends them to Security Center as a single request. This provides significant performance benefits and ensures that related operations either all succeed together or all fail together.
Think of transactions like filling an online shopping cart before checking out, rather than buying items one at a time. Each item you add to the cart is stored locally, and only when you "checkout" (commit) does the actual purchase (server communication) happen.
SDK transactions work differently from traditional database transactions. They are primarily a performance optimization tool that batches operations:
Without transactions:
// Each operation results in a separate server call
var cardholder = engine.CreateEntity("John Doe", EntityType.Cardholder); // Server call #1
cardholder.FirstName = "John"; // Server call #2
cardholder.LastName = "Doe"; // Server call #3
cardholder.EmailAddress = "john@example.com"; // Server call #4
// Total: 4 round-trips to serverWith transactions:
engine.TransactionManager.ExecuteTransaction(() =>
{
var cardholder = engine.CreateEntity("John Doe", EntityType.Cardholder);
cardholder.FirstName = "John";
cardholder.LastName = "Doe";
cardholder.EmailAddress = "john@example.com";
// Total: 1 round-trip to server
});- Start Transaction: SDK prepares to collect entity operations locally
- Accumulate Operations: Entity changes are stored in memory, visible immediately in your application
- Commit Transaction: All accumulated operations are sent to the server in one batch
- Server Processing: Security Center applies all changes atomically - either all succeed or all fail
Manual transactions (using CreateTransaction() and CommitTransaction()) are globally serialized across your entire application through a synchronization lock. This means only one manual transaction can be active at any time, regardless of how many threads your application has. When Thread A starts a manual transaction, Thread B attempting to start a manual transaction will block and wait until Thread A completes.
ExecuteTransaction() also uses this same lock internally, but releases it upon completion. Multiple ExecuteTransaction() calls don't necessarily block each other unless they overlap in time.
This serialization prevents data corruption by ensuring that multiple threads cannot create conflicting changes to related entities simultaneously. While this might seem like a performance limitation, it's actually a critical safety feature.
The performance impact of transactions is based on the number of requests to the directory server. Each property change that results in a database update equals one request.
Understanding Requests to Directory Server:
Without transactions, every update to an entity is sent immediately to the Directory Server as a separate request:
var cardholder = engine.CreateEntity("John", EntityType.Cardholder);
// → Sent to Directory Server immediately (Request #1)
cardholder.FirstName = "John";
// → Sent to Directory Server immediately (Request #2)
cardholder.LastName = "Doe";
// → Sent to Directory Server immediately (Request #3)
cardholder.EmailAddress = "john@example.com";
// → Sent to Directory Server immediately (Request #4)
partition.AddMember(cardholder);
// → Sent to Directory Server immediately (Request #5)
// Total: 5 requests to Directory ServerWith transactions, all updates are collected locally and sent as one batch when you commit:
engine.TransactionManager.ExecuteTransaction(() =>
{
var cardholder = engine.CreateEntity("John", EntityType.Cardholder); // Stored locally
cardholder.FirstName = "John"; // Stored locally
cardholder.LastName = "Doe"; // Stored locally
cardholder.EmailAddress = "john@example.com"; // Stored locally
partition.AddMember(cardholder); // Stored locally
// All operations sent together as 1 request when transaction commits
});
// Total: 1 request to Directory ServerPerformance Impact:
- Few requests (1-5): Minor performance improvement
- Medium requests (10-50): Significant performance improvement
- Many requests (100+): Major performance improvement
No Transaction Overhead: Creating a transaction is essentially free, it just tells the SDK to start collecting operations in memory instead of sending them immediately. The real work happens when you commit, which sends all the collected operations to the Directory Server in one efficient batch.
Transactions ensure that related operations either all succeed together or all fail together. This prevents scenarios where partial updates leave your data in an inconsistent state.
Example scenario: Creating a cardholder and their access permissions
- Without transactions: If creating the access rule fails, you're left with a cardholder who has no access
- With transactions: If any part fails, no cardholder is created at all
Use transactions when:
- Creating multiple related entities that should exist together
- Performing batch operations (creating many entities of the same type)
- Any scenario where partial success would be problematic
- You want to optimize performance for multiple entity operations
Transactions are optional: You can create, update, and delete entities without using transactions. The choice depends on your performance and consistency requirements.
The ExecuteTransaction and ExecuteTransactionAsync methods handle the transaction lifecycle automatically and are recommended for most scenarios.
Basic Synchronous Usage:
engine.TransactionManager.ExecuteTransaction(() =>
{
var cardholder = (Cardholder)engine.CreateEntity("John Doe", EntityType.Cardholder);
cardholder.FirstName = "John";
cardholder.LastName = "Doe";
cardholder.EmailAddress = "john@example.com";
});Asynchronous Usage:
await engine.TransactionManager.ExecuteTransactionAsync(() =>
{
var cardholder = (Cardholder)engine.CreateEntity("Jane Doe", EntityType.Cardholder);
cardholder.FirstName = "Jane";
cardholder.LastName = "Doe";
cardholder.EmailAddress = "jane@example.com";
});Returning Values:
var cardholderGuid = engine.TransactionManager.ExecuteTransaction(() =>
{
var cardholder = (Cardholder)engine.CreateEntity("Bob Smith", EntityType.Cardholder);
cardholder.FirstName = "Bob";
cardholder.LastName = "Smith";
return cardholder.Guid;
});Benefits of ExecuteTransaction:
- Automatic transaction lifecycle management
- Built-in exception handling with automatic rollback
- Thread-safe operation
- Works correctly in both plugin and external application contexts
- Prevents common mistakes like forgetting to commit or rollback
Manual transactions give you explicit control over the transaction lifecycle but require careful error handling and threading considerations.
Basic Manual Transaction:
engine.TransactionManager.CreateTransaction();
try
{
var cardholder = (Cardholder)engine.CreateEntity("Alice Johnson", EntityType.Cardholder);
cardholder.FirstName = "Alice";
cardholder.LastName = "Johnson";
cardholder.EmailAddress = "alice@example.com";
// Default behavior: rollBackOnFailure is true
engine.TransactionManager.CommitTransaction();
}
catch (Exception ex)
{
// With default rollBackOnFailure: true, rollback already happened automatically
logger.LogError(ex, "Transaction failed: {Message}", ex.Message);
throw;
}Understanding rollBackOnFailure Parameter:
// These are equivalent (true is the default)
engine.TransactionManager.CommitTransaction();
engine.TransactionManager.CommitTransaction(rollBackOnFailure: true);
// With rollBackOnFailure: true (default):
// If CommitTransaction throws an exception, RollbackTransaction is called automatically
// With rollBackOnFailure: false:
// You must manually handle rollback in your catch block
engine.TransactionManager.CommitTransaction(rollBackOnFailure: false);When You Need Manual Rollback: Manual rollback is only needed in two scenarios:
- When using rollBackOnFailure: false (rarely needed)
- When you want to abort before committing (business logic decision)
engine.TransactionManager.CreateTransaction();
try
{
var cardholder = (Cardholder)engine.CreateEntity("John Doe", EntityType.Cardholder);
// Check some business condition
if (SomeBusinessConditionFails())
{
// Decide to abort before even attempting commit
engine.TransactionManager.RollbackTransaction();
return;
}
engine.TransactionManager.CommitTransaction(); // Automatic rollback on failure
}
catch (Exception ex)
{
// Rollback already handled automatically
throw;
}When you call ExecuteTransaction from within another ExecuteTransaction, the inner call participates in the existing transaction rather than creating a new nested transaction. This is not true nesting like SQL Server supports - it's all one single transaction.
engine.TransactionManager.ExecuteTransaction(() =>
{
// Outer transaction
var partition = (Partition)engine.CreateEntity("Engineering", EntityType.Partition);
// This call participates in the same transaction
CreateUsersInPartition(partition);
// Everything commits together as one atomic operation
});
void CreateUsersInPartition(Partition partition)
{
// This automatically participates in the outer transaction if one exists
engine.TransactionManager.ExecuteTransaction(() =>
{
var user1 = (Cardholder)engine.CreateEntity("User 1", EntityType.Cardholder);
var user2 = (Cardholder)engine.CreateEntity("User 2", EntityType.Cardholder);
partition.AddMember(user1);
partition.AddMember(user2);
});
}Understanding the threading model is crucial for using transactions correctly, as the behavior differs significantly between plugins and external applications.
Per-Thread Transaction State:
The IsTransactionActive property returns different values for different threads. Each thread tracks its own transaction state independently.
// Thread A
engine.TransactionManager.CreateTransaction();
Console.WriteLine(engine.TransactionManager.IsTransactionActive); // True on Thread A
// Thread B (simultaneously)
Console.WriteLine(engine.TransactionManager.IsTransactionActive); // False on Thread BGlobal Serialization: Despite per-thread state tracking, manual transactions are globally serialized. Only one manual transaction can be active across the entire application at any time. When Thread A starts a manual transaction, Thread B attempting to start a manual transaction will block until Thread A completes.
Thread Affinity for Manual Transactions: Manual transactions must start and complete on the same thread. Attempting to commit or rollback a transaction from a different thread than the one that started it will result in synchronization exceptions.
Plugin Applications (Custom Roles): Plugins run inside the Security Center process and have specific threading requirements:
- EngineThread Requirement: All entity operations must execute on the EngineThread
-
Thread Detection: Use
engine.IsEngineThreadto check if you're on the correct thread (returns true only when actually on EngineThread) -
Automatic Threading:
ExecuteTransactionAsyncautomatically ensures operations run on the EngineThread -
Alternative Methods:
QueueUpdateandQueueUpdateAndWaitcan execute operations on EngineThread without automatic transaction creation
External Applications (Console, WPF, WinForms, Windows Service, ASP.NET): External applications have more threading flexibility:
-
Any Thread: Can use both
ExecuteTransactionandExecuteTransactionAsyncon any thread - Manual Transactions: Work on any thread, but must stay on the same thread throughout the transaction lifecycle
-
Thread Detection:
engine.IsEngineThreadchecks if on the proxy thread; in external apps, this depends on the proxy thread registration, but is generally not a concern for threading decisions - Full Control: Complete control over threading model
Understanding when to use these different methods is important for plugin development:
ExecuteTransactionAsync:
- EngineThread execution + automatic transaction management
- Use when you want both proper threading and transaction benefits
QueueUpdate:
- EngineThread execution + no automatic transaction
- Use when you want EngineThread execution but want to manage transactions manually or don't need transactions
QueueUpdateAndWait:
- Same as QueueUpdate but blocks until completion
- Use when you need synchronous execution on EngineThread
// In a plugin - ExecuteTransactionAsync (most common)
await engine.TransactionManager.ExecuteTransactionAsync(() =>
{
var cardholder = (Cardholder)engine.CreateEntity("Name", EntityType.Cardholder);
});
// In a plugin - QueueUpdate without transaction (for single operations)
engine.TransactionManager.QueueUpdate(() =>
{
var cardholder = (Cardholder)engine.CreateEntity("Name", EntityType.Cardholder);
// Single operation sent to server immediately
});
// In a plugin - QueueUpdate with manual transaction (advanced scenarios)
engine.TransactionManager.QueueUpdate(() =>
{
engine.TransactionManager.CreateTransaction();
try
{
var cardholder1 = (Cardholder)engine.CreateEntity("Name1", EntityType.Cardholder);
var cardholder2 = (Cardholder)engine.CreateEntity("Name2", EntityType.Cardholder);
engine.TransactionManager.CommitTransaction();
}
catch
{
// Rollback handled automatically with default rollBackOnFailure: true
throw;
}
});Entity Operations (Primary Purpose):
- Create entities:
engine.CreateEntity("Name", EntityType.Cardholder) - Update entity properties:
cardholder.FirstName = "John" - Delete entities
Acceptable Supporting Operations:
- Simple logging for debugging purposes
- Fast calculations and data transformations
- Conditional logic and loops for entity processing
- Memory operations and variable assignments
I/O Operations:
- File system access (reading or writing files)
- Network requests or HTTP calls
- Database calls to external databases
- Any operation that accesses external resources
Async Operations:
- Never use
awaitkeywords inside transactions (breaks thread affinity) - No
Task.Delay()or any delay operations
Blocking Operations:
-
Thread.Sleep()calls -
Waiting on external events or signals
-
Any operation that could block indefinitely
Query Operations:
- Never execute queries inside transactions, there is no point in doing so
- Queries should be executed before starting transactions or after committing them
These restrictions exist because:
-
Global Serialization: Transactions hold locks that prevent other transactions from running. Any slow operation affects your entire application's transaction throughput.
-
Thread Affinity: Manual transactions must complete on the same thread they started on. Async operations can cause thread switching.
-
Performance Impact: Long-running operations inside transactions create bottlenecks for all other transaction operations.
-
Design Purpose: The transaction system is specifically optimized for fast, local entity operations, not general-purpose operations.
engine.TransactionManager.ExecuteTransaction(() =>
{
// Create a cardholder
var cardholder = (Cardholder)engine.CreateEntity("John Doe", EntityType.Cardholder);
cardholder.FirstName = "John";
cardholder.LastName = "Doe";
cardholder.EmailAddress = "john@example.com";
// Create a partition for organization
var partition = (Partition)engine.CreateEntity("Engineering Team", EntityType.Partition);
// Add cardholder to partition
partition.AddMember(cardholder);
// All operations succeed together or fail together
});await engine.TransactionManager.ExecuteTransactionAsync(() =>
{
// Create multiple cardholders in one transaction
for (int i = 1; i <= 10; i++)
{
var cardholder = (Cardholder)engine.CreateEntity($"User {i}", EntityType.Cardholder);
cardholder.FirstName = "User";
cardholder.LastName = i.ToString();
cardholder.EmailAddress = $"user{i}@example.com";
}
// Much faster than 10 individual operations
});engine.TransactionManager.ExecuteTransaction(() =>
{
var cardholder = (Cardholder)engine.CreateEntity("Jane Smith", EntityType.Cardholder);
cardholder.FirstName = "Jane";
cardholder.LastName = "Smith";
cardholder.EmailAddress = "jane@example.com";
// Conditional logic based on data is fine
if (cardholder.EmailAddress.EndsWith("@company.com"))
{
var employeePartition = (Partition)engine.CreateEntity("Employees", EntityType.Partition);
employeePartition.AddMember(cardholder);
logger.LogDebug("Created employee cardholder {Name}", cardholder.Name);
}
});Understanding the specific exceptions that can occur during transaction operations helps you build robust applications.
Based on the SDK implementation, CommitTransaction() can throw these specific exceptions:
Before attempting commit:
-
SdkException(SdkError.NoActiveTransaction)- No transaction is currently active
During commit (server communication errors):
-
SecurityException- Insufficient privileges for the operations -
SdkException(SdkError.LicenseCountExceeded)- Operations would exceed license limits -
SdkException(SdkError.RecursionViolation)- Operations would cause a recursion violation -
SdkException(SdkError.UnsufficientPrivilege)- User lacks required privileges for entity or field update -
SdkException(SdkError.TransactionFailed)- General server error (default case)
try
{
engine.TransactionManager.ExecuteTransaction(() =>
{
var cardholder = (Cardholder)engine.CreateEntity("Test User", EntityType.Cardholder);
cardholder.FirstName = "Test";
cardholder.LastName = "User";
cardholder.EmailAddress = "test@example.com";
});
logger.LogInformation("Transaction completed successfully");
}
catch (SecurityException ex)
{
logger.LogError(ex, "Insufficient privileges for transaction");
}
catch (SdkException ex)
{
logger.LogError(ex, "SDK transaction failed: {ErrorMessage}", ex.Message);
switch (ex.ErrorCode)
{
case SdkError.LicenseCountExceeded:
logger.LogError("License limit exceeded");
break;
case SdkError.RecursionViolation:
logger.LogError("Transaction caused a recursion violation");
break;
case SdkError.UnsufficientPrivilege:
logger.LogError("User lacks required privileges");
break;
case SdkError.TransactionFailed:
logger.LogError("General transaction failure");
break;
default:
logger.LogError("Unknown SDK error: {ErrorCode}", ex.ErrorCode);
break;
}
}
catch (Exception ex)
{
logger.LogError(ex, "Unexpected error during transaction");
throw;
}engine.TransactionManager.CreateTransaction();
try
{
var cardholder = (Cardholder)engine.CreateEntity("Manual Transaction User", EntityType.Cardholder);
cardholder.FirstName = "Manual";
cardholder.LastName = "User";
// Default rollBackOnFailure: true means automatic rollback on commit failure
engine.TransactionManager.CommitTransaction();
}
catch (SdkException ex)
{
// Rollback already happened automatically due to default rollBackOnFailure: true
logger.LogError(ex, "Transaction failed and was automatically rolled back");
throw;
}
catch (Exception ex)
{
// For non-SDK exceptions, check if rollback is needed
if (engine.TransactionManager.IsTransactionActive)
{
logger.LogWarning("Manual rollback needed for non-SDK exception");
engine.TransactionManager.RollbackTransaction();
}
logger.LogError(ex, "Unexpected error in manual transaction");
throw;
}-
Security Center SDK Developer Guide Overview of the SDK framework and how to build integrations with Security Center.
-
Platform SDK
- Platform SDK Overview Introduction to the Platform SDK and core concepts.
- SDK Certificates Details certificates, licensing, and connection validation.
- Entity Guide Explains the core entity model, inheritance, and how to work with entities.
- Entity Cache Guide Describes the engine's local entity cache and synchronization.
- SDK Transactions Covers batching operations for performance and consistency.
- ReportManager Querying entities and activity data from Security Center.
- Events and Actions Subscribing to events and handling actions.
- Logging with the Genetec SDK How to configure logging, diagnostics, and debug methods.
- Referencing SDK Assemblies Best practices for referencing assemblies and resolving them at runtime.
- SDK Compatibility Guide Understanding backward compatibility and versioning in the SDK.
-
Plugin SDK
- Plugin SDK Overview Introduction to plugin architecture and capabilities.
- Plugin SDK Certificates SDK certificate requirements for plugin roles.
- Plugin SDK Lifecycle Initialization and disposal patterns.
- Plugin SDK Threading Threading model, QueueUpdate, and async patterns.
- Plugin SDK Configuration Configuration storage and monitoring.
- Plugin SDK Restricted Configuration Secure credential storage and admin-only configuration.
- Plugin SDK Database Database integration and schema management.
- Plugin SDK Events Event subscription and handling.
- Plugin SDK Queries Query processing and response handling.
- Plugin SDK Request Manager Request/response communication with clients.
- Plugin SDK Entity Ownership Understanding plugin-owned entities, running state management, and ownership release.
- Plugin SDK Entity Mappings Using EntityMappings for plugin-specific configuration and external system integration.
- Plugin SDK State Management Reporting plugin health and diagnostics.
- Plugin SDK Server Management High availability and server failover.
- Custom Privileges Defining and enforcing custom privileges.
- Resolving Non-SDK Assemblies Handling third-party dependencies in plugins and workspace modules.
- Deploying Plugins Registering and deploying plugins and workspace modules.
-
- Macro SDK Developer Guide Complete guide to creating server-side automation scripts in Security Center using C#.
- Getting Started Setup, authentication, and basic configuration for the Web SDK.
- Referencing Entities Entity discovery, search capabilities, and parameter formats.
- Entity Operations CRUD operations, multi-value fields, and method execution.
- Partitions Managing partitions, entity membership, and user access control.
- Custom Fields Creating, reading, writing, and filtering custom entity fields.
- Custom Card Formats Managing custom credential card format definitions.
- Actions Control operations for doors, cameras, macros, and notifications.
- Events and Alarms Real-time event monitoring, alarm monitoring, and custom events.
- Incidents Incident management, creation, and attachment handling.
- Reports Activity reports, entity queries, and historical data retrieval.
- Performance Guide Optimization tips and best practices for efficient API usage.
- Reference Entity GUIDs, EntityType enumeration, and EventType enumeration.
- Under the Hood Technical architecture, query reflection, and SDK internals.
- Troubleshooting Common error resolution and debugging techniques.
- Media Gateway Guide Setup and configuration of the Media Gateway role for video streaming.
- Web Player Guide Complete guide to integrating GWP for live and playback video streaming.
- Web Player API Reference Full API documentation with interfaces, methods, properties, and events.
- Web Player Sample Application Comprehensive demo showcasing all GWP features with timeline and PTZ controls.
- Genetec Web Player Multiplexing Sample Multi-camera grid demo using a shared WebSocket connection.