-
Notifications
You must be signed in to change notification settings - Fork 6
platform sdk entity cache
The Engine's entity cache is a fundamental component of the Genetec SDK that provides efficient, client-side storage and management of entities retrieved from the Security Center system. Think of it as a local copy of the entities your application needs, which stays automatically synchronized with the server.
Before diving into how the cache works, it's important to understand what entities are in the Genetec SDK context.
An Entity is the base abstract class that represents any object in your Security Center system. All entities in Security Center inherit from this base Entity class, which provides common properties and functionality shared across all entity types.
Every entity has two types of identifiers:
GUID (Global Unique Identifier):
- Always present and immutable
- 36-character format:
5645151a-d758-429c-8b09-25c125b6a6fb - Unique across the entire system
- Automatically assigned when the entity is created
Logical ID (optional):
- Integer value from 0 to 9,999,999 (7 digits maximum)
- Must be manually assigned through Config Tool or the SDK
- Can be changed after creation
- Not unique across entity types (a Camera and a Door can both have logical ID 123)
- Useful for human-readable identification
-
GetEntity(EntityType, logicalId)throwsArgumentOutOfRangeExceptionif outside valid range
The SDK includes many specific entity types that inherit from the base Entity class:
// Base class with common properties
Entity entity = engine.GetEntity(someGuid);
string name = entity.Name; // Common property
string description = entity.Description; // Common property
// Specific entity types with additional properties
Camera camera = engine.GetEntity<Camera>(cameraGuid);
// Camera-specific properties would be available here
Cardholder cardholder = engine.GetEntity<Cardholder>(cardholderGuid);
string firstName = cardholder.FirstName; // Cardholder-specific property
string lastName = cardholder.LastName; // Cardholder-specific propertyCommon entity types include:
- Camera, Door, Area, AccessPoint
- Cardholder, CardholderGroup, Credential
- Alarm, Schedule, AccessRule
- Role, User, UserGroup
- And many others
Entities can be related to each other, forming a hierarchy or network of connections. For example:
- A Camera belongs to a VideoUnit (physical device)
- A Door can have multiple AccessPoints (card readers)
- A Cardholder can belong to multiple CardholderGroups
Understanding these relationships is important when working with DownloadAllRelatedData, which we'll cover later.
- Performance: Fast local access to entity data without repeated server queries
- Real-time Updates: Automatic synchronization with server-side changes
- Reduced Network Traffic: Minimizes communication with the server
- Consistency: Ensures your application has up-to-date entity information
These sections describe the cache behavior you can observe when reading entities, holding references, and reconnecting.
The entity cache is thread-safe for read operations. You can safely call GetEntity() and GetEntities() from any thread.
Note
While cache access is thread-safe, GetEntity(guid) with default behavior may trigger a server query if the entity is not cached. This I/O operation can block the calling thread. Use GetEntity(guid, query: false) when you need guaranteed non-blocking cache-only access.
The cache maintains exactly one object instance per entity using a dictionary structure for fast lookups. This means:
// These both return the SAME object instance
Entity entity1 = engine.GetEntity(someGuid);
Entity entity2 = engine.GetEntity(someGuid);
// entity1 and entity2 point to the exact same object in memory
Console.WriteLine(ReferenceEquals(entity1, entity2)); // True, same reference!Each Engine instance maintains its own separate cache:
using var engine1 = new Engine();
using var engine2 = new Engine();
// Both connect to the same Directory Server
await engine1.LogOnAsync(server, username, password);
await engine2.LogOnAsync(server, username, password);
// Both load the same camera
await LoadCameras(engine1);
await LoadCameras(engine2);
Entity camera1 = engine1.GetEntity(someCameraGuid);
Entity camera2 = engine2.GetEntity(someCameraGuid);
// These are DIFFERENT object instances
Console.WriteLine(ReferenceEquals(camera1, camera2)); // False, different references!
// But they represent the same logical entity
Console.WriteLine(camera1.Guid == camera2.Guid); // True, same entity GUIDWhen the Engine loses connection to the server:
- The cache is cleared - all entity objects are disposed and removed
-
GetEntity()will returnnullfor previously cached entities - Existing entity references continue to exist but point to disposed objects
- These disposed entity objects have
IsDisposed = true - Accessing properties on disposed entities returns default values (empty string,
Guid.Empty, 0, etc.) - Upon reconnection, you must reload entities to repopulate the cache
- Existing references still point to the old disposed objects, not new fresh ones
Important
You cannot dispose entity objects yourself. Only the Engine manages entity lifecycle.
Entity camera = engine.GetEntity(someCameraGuid);
Console.WriteLine(camera.IsDisposed); // False - entity is active
Console.WriteLine(camera.Name); // "Camera 01" - actual name
// Connection is lost...
Console.WriteLine(camera.IsDisposed); // True - entity is disposed
Console.WriteLine(camera.Name); // "" - empty string (default value)
Console.WriteLine(camera.Guid); // Guid.Empty (default value)
// Always check IsDisposed before using entity data
if (!camera.IsDisposed)
{
Console.WriteLine(camera.Name);
}
// After reconnection and reloading...
await LoadCameras(); // Must reload entities
Entity newCamera = engine.GetEntity(someCameraGuid);
Console.WriteLine(ReferenceEquals(camera, newCamera)); // False - different instances!
Console.WriteLine(camera.IsDisposed); // True - old reference still disposed
Console.WriteLine(newCamera.IsDisposed); // False - new instance is active
Console.WriteLine(newCamera.Name); // "Camera 01" - actual name restoredWhen an entity changes (locally or remotely), the cache updates the single object instance. Any references you hold to that entity will automatically reflect the changes because they all point to the same object.
This sequence shows what enters the cache automatically and what you must load explicitly.
When you create an Engine instance, the cache is empty:
using var engine = new Engine();
// Cache is empty at this pointAfter successful authentication, some entities are automatically loaded into the cache:
ConnectionStateCode state = await engine.LogOnAsync(server, username, password);
// Cache now contains system entities like User, Role, Server, etc.Entity types typically loaded automatically:
- Application (the SDK client application)
- Network (associated with connected servers)
- Partition (root partition)
- Role (directory failover role)
- Server (connected directory server and failover servers)
- SystemConfiguration
- User (logged-in user)
- UserGroup (parent groups of logged-in user)
Note: The exact entities loaded depend on your system configuration and user privileges.
These system entities are loaded automatically because they're essential for the SDK to function properly. This also means you'll receive notifications for changes to these entities without needing to explicitly query for them.
Entities are loaded into the cache through queries. This step is crucial because it also registers your interest in those entity types with the server:
async Task LoadCameras()
{
var query = (EntityConfigurationQuery)engine.ReportManager.CreateReportQuery(ReportType.EntityConfiguration);
query.EntityTypeFilter.Add(EntityType.Camera);
await Task.Factory.FromAsync(query.BeginQuery, query.EndQuery, null);
}Important
By querying for a specific entity type, you're telling the server "I want to know about all cameras, including new ones created later."
The Engine provides three batched events to notify you about cache changes. These events tell you about cache operations, not necessarily system operations.
Note
The single-entity events (EntityAdded, EntityInvalidated, EntityRemoved) are deprecated. Use the batched versions (EntitiesAdded, EntitiesInvalidated, EntitiesRemoved) for better scalability.
Fired when entities are added to the cache:
engine.EntitiesAdded += (sender, e) =>
{
foreach (EntityUpdateInfo info in e.Entities)
{
Entity entity = engine.GetEntity(info.EntityGuid);
Console.WriteLine($"Added to cache: {entity.Name} (Local: {e.IsLocalUpdate})");
}
};When this fires:
- During initial query execution (IsLocalUpdate = false)
- When you create a new entity locally (IsLocalUpdate = true)
- When another client creates a new entity (IsLocalUpdate = false)
Fired after entities have been updated in the cache:
engine.EntitiesInvalidated += (sender, e) =>
{
foreach (EntityUpdateInfo info in e.Entities)
{
Entity entity = engine.GetEntity(info.EntityGuid);
// The entity object already has the updated properties
Console.WriteLine($"Updated in cache: {entity.Name} (Local: {e.IsLocalUpdate})");
}
};When this fires:
- Immediately when you modify an entity locally (IsLocalUpdate = true)
- When another client modifies an entity and the change is committed to the server (IsLocalUpdate = false)
Fired when entities are removed from the cache:
engine.EntitiesRemoved += (sender, e) =>
{
foreach (EntityUpdateInfo info in e.Entities)
{
// The entity is already gone from the cache at this point
// GetEntity() will always return null here
Entity entity = engine.GetEntity(info.EntityGuid); // Always null!
Console.WriteLine($"Removed from cache: {info.EntityType} {info.EntityGuid}");
}
};When this fires: When an entity is deleted from the system and removed from the cache.
Important
Since the entity has already been removed from the cache, calling GetEntity() in this event handler will always return null.
Connection loss also removes all entities from the cache, but does not fire EntitiesRemoved. The cache is silently cleared and existing entity references become disposed.
The IsLocalUpdate property is available on the event args (not on EntityUpdateInfo). It tells you the source of the change:
- True: Change was made through this same Engine instance
- False: Change came from somewhere else (Config Tool, Security Desk, another SDK application, system roles, etc.)
engine.EntitiesInvalidated += (sender, e) =>
{
if (e.IsLocalUpdate)
{
// Change originated from this Engine instance
}
else
{
// Change came from another client or system role
}
};Important
From the Directory Server's perspective, everything is a "client": your SDK app, Config Tool, Security Desk, and all system roles.
Understanding when events fire is crucial:
- Local changes: Events fire immediately when you make the change (before server commitment)
- Remote changes: Events fire after the change has been committed to the Directory Server
No Rollback Notification: If a local change fails to commit to the server, there's no automatic notification about the failure.
Use these query options to control how much data enters the cache and how the results are returned.
async Task LoadCameras()
{
var query = (EntityConfigurationQuery)engine.ReportManager.CreateReportQuery(ReportType.EntityConfiguration);
query.EntityTypeFilter.Add(EntityType.Camera);
await Task.Factory.FromAsync(query.BeginQuery, query.EndQuery, null);
}async Task LoadCamerasWithRelatedData()
{
var query = (EntityConfigurationQuery)engine.ReportManager.CreateReportQuery(ReportType.EntityConfiguration);
query.EntityTypeFilter.Add(EntityType.Camera);
query.DownloadAllRelatedData = true; // This loads related entities too
await Task.Factory.FromAsync(query.BeginQuery, query.EndQuery, null);
}When set to true, the query downloads comprehensive entity data:
- All entity properties - Both common and type-specific properties are loaded immediately
- Membership information - Which groups the entity belongs to and which entities it contains
- Related entities - Dependent entities are loaded into the cache (see table below)
- Event-to-action associations - Configured actions for entity events
-
Custom fields - All custom field values (equivalent to setting
DownloadCustomFields = true)
When set to false (default):
- Common properties only - Name, Description, GUID, running status, partition membership
- Lazy loading - Type-specific properties are fetched on first access, triggering additional server requests
- No related entities - You must query related entities separately
- No memberships - Group membership data is not included
For certain entity types, DownloadAllRelatedData = true also loads related entities into the cache. These related entities do not appear in the query result DataTable but are available via GetEntity().
| When You Query | Related Entities Loaded |
|---|---|
| Cardholder | Credentials assigned to the cardholder |
| Visitor | Credentials assigned to the visitor |
| Camera | Video streams, archived streams, replacement-camera relationships, video encoder device |
| Door | Child access points (readers, REX devices), schedules |
| Device | Access points using the device, streams |
| Schedule | Access rules using the schedule |
| Role | Agents assigned to the role |
| User | Applications logged in with this user |
| Network | Routes associated with the network |
| InterfaceModule | Child interface modules |
| SystemConfiguration | Custom events |
In addition to the type-specific relationships above, every queried entity also loads the following relationships under DownloadAllRelatedData = true:
- Hierarchical parents and children (the entity hierarchy)
- Parent partition
- Owning role (relevant for federated and role-owned entities)
- Parent map and map-layer (when the entity is placed on a map)
- Event-filter-configuration entity (when one is configured)
Other entity types not listed in the table above (such as Unit, Alarm, Area, Zone, Elevator, CardholderGroup) load these universal relationships and may also load type-specific relationships such as user/user-group recipients (Alarm), child access points (Area, Zone, Elevator), or member cardholders (CardholderGroup).
- When you need type-specific properties for multiple entities
- When you need membership information (group members or parent groups)
- When you need related entities (credentials, devices, streams, etc.)
- When building a comprehensive view of entity relationships
- When you only need basic entity information (Name, GUID, Description)
- When querying large numbers of entities and only need common properties
- When optimizing for initial load time and network bandwidth
The StrictResults property controls whether the query result DataTable includes only entities that match your filter criteria.
var query = (EntityConfigurationQuery)engine.ReportManager.CreateReportQuery(ReportType.EntityConfiguration);
query.EntityTypeFilter.Add(EntityType.Camera);
query.DownloadAllRelatedData = true;
query.StrictResults = true; // Only cameras appear in results- The query result DataTable contains only GUIDs that match your filter criteria
- Related entities downloaded via
DownloadAllRelatedDataare still added to the cache but do not appear in the result DataTable
- The query result DataTable may include GUIDs for related entities in addition to the entities matching your filter
- This can be useful when you want to know which related entities were downloaded
- When iterating over query results and you only want entities matching your filter
- When using the result DataTable to build a list of specific entity types
In most cases, the default value is fine since you typically access entities through GetEntity() or GetEntities() rather than iterating the result DataTable directly.
You can also load specific entities if you already know their GUIDs:
async Task LoadSpecificEntities(Engine engine, IEnumerable<Guid> entityGuids)
{
var query = (EntityConfigurationQuery)engine.ReportManager.CreateReportQuery(ReportType.EntityConfiguration);
// Add specific GUIDs to load
foreach (var guid in entityGuids)
{
query.EntityGuids.Add(guid);
}
await Task.Factory.FromAsync(query.BeginQuery, query.EndQuery, null);
}Important behaviors when using EntityGuids:
- Filters should not be used - When specifying EntityGuids, don't use EntityTypeFilter, Name, Description, or other filters since you're already targeting specific entities by their GUIDs
- Invalid GUIDs are skipped - the query loads only entities that actually exist
- No notification registration - this does not register interest for future notifications about those entity types
- Pagination doesn't work with EntityGuids - all specified GUIDs are processed in a single query (unlike EntityTypeFilter queries, which support pagination)
Performance consideration: Don't specify too many GUIDs in a single query. If you have a large number of entities to load, split them into multiple smaller queries to avoid performance issues.
For advanced scenarios, you can use a two-step approach to first get entity GUIDs, then selectively load them:
Step 1: Get only the GUIDs (nothing loaded into cache):
var guidQuery = (EntityConfigurationQuery)engine.ReportManager.CreateReportQuery(ReportType.EntityConfiguration);
guidQuery.EntityTypeFilter.Add(EntityType.Camera);
guidQuery.Name = "Lobby"; // Only cameras whose names contain "Lobby"
guidQuery.GuidsOnly = true; // Only return GUIDs, don't load into cache
var guidResults = await Task.Factory.FromAsync(guidQuery.BeginQuery, guidQuery.EndQuery, null);Note
GuidsOnly = true prevents entities from being loaded into the cache, but using EntityTypeFilter still registers for change notifications with the server. This means GetEntities(EntityType.Camera) returns an empty list after this query since no entities were cached. The notification registration only affects newly created entities of those types. When a new entity of a registered type is created (by your application or any other client), your application receives an EntitiesAdded event. Existing entities that were never loaded into the cache do not trigger EntitiesInvalidated or EntitiesRemoved events when modified or deleted. Only entities that are actually in the cache (loaded through GetEntity(), step 2, or a previous query) trigger invalidation and removal events.
Step 2: Use those GUIDs to load actual entities:
var loadQuery = (EntityConfigurationQuery)engine.ReportManager.CreateReportQuery(ReportType.EntityConfiguration);
// Add the GUIDs from step 1
foreach (DataRow row in guidResults.Data.Rows)
{
loadQuery.EntityGuids.Add(row.Field<Guid>("Guid"));
}
loadQuery.DownloadAllRelatedData = true;
await Task.Factory.FromAsync(loadQuery.BeginQuery, loadQuery.EndQuery, null);This pattern is useful when you want to apply complex filters first, then decide which entities to actually load into the cache.
When using pagination, entities are loaded per page:
async Task LoadEntitiesWithPagination(Engine engine, params EntityType[] types)
{
var query = (EntityConfigurationQuery)engine.ReportManager.CreateReportQuery(ReportType.EntityConfiguration);
query.EntityTypeFilter.AddRange(types);
query.DownloadAllRelatedData = true;
query.Page = 1;
query.PageSize = 1000;
QueryCompletedEventArgs args;
do
{
// Entities from this page are immediately added to the cache
// and trigger EntitiesAdded events
args = await Task.Factory.FromAsync(query.BeginQuery, query.EndQuery, null);
query.Page++;
} while (args.Data.Rows.Count >= query.PageSize);
}After entities are in the cache, choose the retrieval method based on whether you want cache-only access or server fallback.
// Default behavior, will query server if not in cache
Entity entity = engine.GetEntity(entityGuid);
// Using logical ID and entity type
Entity entity = engine.GetEntity(EntityType.Camera, logicalId);
// Explicit control over querying behavior
Entity entity = engine.GetEntity(entityGuid, query: true); // Will query server if not cached
Entity entity = engine.GetEntity(entityGuid, query: false); // Only returns if already cached
// Generic versions for type safety (avoid casting)
Camera camera = engine.GetEntity<Camera>(cameraGuid);
Camera camera = engine.GetEntity<Camera>(cameraGuid, query: true);
Camera camera = engine.GetEntity<Camera>(cameraGuid, query: false);Important distinction:
-
GetEntity(guid)orGetEntity(guid, true): If the entity is not in the cache, automatically queries the server to fetch it and adds it to the cache -
GetEntity(guid, false): Returns entity only if already cached, null otherwise -
GetEntity(EntityType, logicalId): Behaves like the defaultGetEntity(guid), will query server if not cached
Note
- When
GetEntity()fetches an entity from the server, it will trigger an EntitiesAdded event. - However,
GetEntity()does not register interest in that entity type for future notifications. - Only EntityConfigurationQuery and specialized queries register for notifications about new entities of those types created by other clients.
// Option 1: Non-generic with cast
var cameras = engine.GetEntities(EntityType.Camera).OfType<Camera>().ToList();
// Option 2: Generic overload (preferred)
var cameras = engine.GetEntities<Camera>(EntityType.Camera);Important
GetEntities() always returns entities that are already in the cache. It will not query the server to fetch entities that aren't cached.
Remember: Multiple calls return references to the same object instances.
The next sections explain which properties are available immediately and when additional data is loaded.
Entity properties are divided into two categories:
Common Properties (always loaded):
- Name, Description, Logical ID, GUID, CreatedOn
- These are shared by all entity types and are always loaded when calling
GetEntity()or executing any query
Specific Properties (lazy loaded):
- FirstName, LastName (for Cardholders)
- EmailAddress, MobilePhoneNumber (for Cardholders)
- Recipients (for Alarms)
- Camera-specific capabilities and settings
- These are specific to each entity type and require special handling
When you use GetEntity() or run a query with DownloadAllRelatedData = false:
// This loads only common properties
Cardholder cardholder = engine.GetEntity(cardholderGuid) as Cardholder;
// This sends a Directory query to load the cardholder-specific properties
string firstName = cardholder.FirstName; // Directory query happens here
string lastName = cardholder.LastName; // No additional query, already loaded aboveImportant
GetEntity() does not have a DownloadAllRelatedData option. It always behaves like DownloadAllRelatedData = false.
To prevent repeated Directory queries, use EntityConfigurationQuery with DownloadAllRelatedData = true:
// This loads both common AND specific properties upfront
var query = (EntityConfigurationQuery)engine.ReportManager.CreateReportQuery(ReportType.EntityConfiguration);
query.EntityTypeFilter.Add(EntityType.Cardholder);
query.DownloadAllRelatedData = true; // Critical for performance!
await Task.Factory.FromAsync(query.BeginQuery, query.EndQuery, null);
// Now accessing specific properties won't trigger additional queries
var cardholders = engine.GetEntities(EntityType.Cardholder).OfType<Cardholder>();
foreach (var cardholder in cardholders)
{
// These accesses are fast, no Directory queries
string fullName = $"{cardholder.FirstName} {cardholder.LastName}";
}Bad Pattern (sends one Directory query per cardholder):
// Don't do this, each FirstName access sends a Directory query
for (int i = 0; i < 100; i++)
{
Cardholder cardholder = engine.GetEntity(cardholderGuids[i]) as Cardholder;
string name = cardholder.FirstName; // Directory query for each iteration
}Good Pattern (one query loads everything):
// Do this instead, load all cardholders with specific properties upfront
var query = (EntityConfigurationQuery)engine.ReportManager.CreateReportQuery(ReportType.EntityConfiguration);
query.EntityTypeFilter.Add(EntityType.Cardholder);
query.DownloadAllRelatedData = true;
// Add specific GUIDs if needed
foreach (var guid in cardholderGuids)
{
query.EntityGuids.Add(guid);
}
await Task.Factory.FromAsync(query.BeginQuery, query.EndQuery, null);
// Now all accesses are fast
foreach (var guid in cardholderGuids)
{
Cardholder cardholder = engine.GetEntity(guid) as Cardholder;
string name = cardholder.FirstName; // Fast, no additional query
}While EntityConfigurationQuery is the most common way to load entities, the SDK also provides specialized query types that offer entity-specific filters and functionality:
CardholderConfigurationQuery: Provides cardholder-specific filters like:
- FirstName, LastName, FullName filtering with search modes and sorting
- Email, MobilePhoneNumber filtering with search modes
- AccessStatus filtering (Active, Expired, Inactive)
- CardholderGroupIds filtering
- ActivationTimeRange and ExpirationTimeRange
- VisitorsOnly filtering (true/false/null for both)
CredentialConfigurationQuery: Offers credential-specific options:
- UniqueIds collection for searching specific credentials
- FormatType filtering for credential formats
- UnassignedOnly filtering
- MobileCredentialOnly filtering
UserConfigurationQuery: Provides user-specific filters:
- FirstName, LastName filtering with search modes
- Email filtering
- Status filtering (Activated, Deactivated)
- UserGroupIds filtering
- SecurityLevel range filtering
- GroupRecursive search option
AccessRuleConfigurationQuery: Offers access rule filtering:
- AccessRuleType filtering
Consider using specialized queries when:
- You need entity-specific filtering that EntityConfigurationQuery doesn't provide
- You're working extensively with one entity type and need its specialized features
- You want more intuitive, type-specific property names and filters
Example:
// Using specialized query for advanced cardholder filtering
var cardholderQuery = (CardholderConfigurationQuery)engine.ReportManager.CreateReportQuery(ReportType.CardholderConfiguration);
// Set cardholder-specific filters
cardholderQuery.FirstName = "John";
cardholderQuery.FirstNameSearchMode = StringSearchMode.StartsWith;
cardholderQuery.AccessStatus.Add(CardholderState.Active);
cardholderQuery.VisitorsOnly = false; // Only regular cardholders, not visitors
cardholderQuery.DownloadAllRelatedData = true;
await Task.Factory.FromAsync(cardholderQuery.BeginQuery, cardholderQuery.EndQuery, null);Tip
Check the SDK documentation for specialized query types for the entities you work with most frequently.
These behaviors affect notification timing, memory usage, and when the SDK reaches back to the server.
To receive notifications about new entities created by other clients, you must have queried for that entity type at least once. The query filters don't matter; just querying for the type registers your interest with the server.
// This enables notifications for ALL new cameras, regardless of the filter
var query = (EntityConfigurationQuery)engine.ReportManager.CreateReportQuery(ReportType.EntityConfiguration);
query.EntityTypeFilter.Add(EntityType.Camera);
query.Name = "Some Specific Camera"; // Restrictive filter
await Task.Factory.FromAsync(query.BeginQuery, query.EndQuery, null);
// Now you'll get EntitiesAdded events for any new cameras created by other clientsModifying one entity can trigger EntitiesInvalidated events for multiple related entities due to relationships. All entities in a cascade will have the same IsLocalUpdate value.
- Memory usage is the primary constraint for the cache
- There's no way to selectively remove entities from the cache
- The only way to clear the cache is to disconnect the Engine (but this is overkill unless you're truly done)
- Be thoughtful about which entity types you load
Even if entities are already cached, running another query will:
- Always execute the SQL query on the server - this is important to understand
- Not fire duplicate EntitiesAdded events for already-cached entities
- Update the cache if any entities have changed since they were last loaded
This means EntityConfigurationQuery always hits the server, regardless of what's in your cache.
When the Engine loses connection:
- The cache is immediately cleared - GetEntity() and GetEntities() calls will no longer work
-
Existing entity references continue to point to disposed objects with
IsDisposed = true - Upon reconnection, you must manually re-execute queries to repopulate the cache
These patterns show common ways to subscribe to updates and work with cached references safely.
// Register interest in cameras
await LoadCameras();
// Now you'll get notifications for all camera changes
engine.EntitiesAdded += (sender, e) =>
{
foreach (var info in e.Entities.Where(i => i.EntityType == EntityType.Camera))
{
Console.WriteLine($"New camera: {engine.GetEntity(info.EntityGuid).Name}");
}
};// Get a reference to an entity
Camera camera = engine.GetEntities(EntityType.Camera).OfType<Camera>().First();
// This reference will automatically reflect updates
// No need to call GetEntity() again after EntitiesInvalidated eventsvoid PrintEntityCache(Engine engine)
{
Console.WriteLine("===== Entity Cache Summary =====");
int totalEntities = 0;
foreach (var entityType in Enum.GetValues(typeof(EntityType))
.OfType<EntityType>()
.Where(t => t != EntityType.None && t != EntityType.ReportTemplate)
.OrderBy(t => t.ToString()))
{
int count = engine.GetEntities(entityType).Count;
totalEntities += count;
if (count > 0)
{
Console.WriteLine($"{entityType}: {count:N0}");
}
}
Console.WriteLine($"Total entities: {totalEntities:N0}");
}-
About entities: Entity model and
IPartitionSupport. - About the Report Manager: Running queries against the Directory and processing results.
- Understanding DownloadAllRelatedData and StrictResults: How configuration queries control which entities load and which appear in the result.
- Working with events: Subscribing to system events from the Platform SDK.
- Overview
- Connecting to Security Center
- SDK certificates
- Referencing SDK assemblies
- SDK compatibility
- Entities
- Entity cache
- Transactions
- Events
- Actions
- Security Desk
- Custom events
- ReportManager
- ReportManager query reference
- Access control raw events
- DownloadAllRelatedData and StrictResults
- Privileges
- Partitions
- About custom fields
- About video
- About cameras
- Enrolling a video unit
- Archiver and auxiliary archiver roles
- Archive transfer
- About access control
- About cardholders and credentials
- About doors, areas, elevators, and access points
- About access rules and schedules
- About access control units and interface modules
- Enrolling an access control unit
- Door templates
- Visitors
- Mobile credentials
- About threat levels
- About alarms
- Maps
- Logging
- Overview
- Certificates
- Lifecycle
- Threading
- State management
- Configuration
- Restricted configuration
- Events
- Queries
- Request manager
- Database
- Entity ownership
- Entity mappings
- Server management
- Custom privileges
- Custom entity types
- Resolving non-SDK assemblies
- Deploying plugins
- .NET 8 support
- Overview
- Certificates
- Creating modules
- Tasks
- Pages
- Components
- Tile extensions
- Services
- Contextual actions
- Options extensions
- Configuration pages
- Monitors
- Shared components
- Commands
- Extending events
- Map extensions
- Timeline providers
- Image extractors
- Credential encoders
- Credential readers
- Cardholder fields extractors
- Badge printers
- Content builders
- Dashboard widgets
- Incidents
- Logon providers
- Pinnable content builders
- Custom report pages
- Overview
- Getting started
- MediaPlayer
- VideoSourceFilter
- MediaExporter
- MediaFile
- G64 converters
- FileCryptingManager
- PlaybackSequenceQuerier
- PlaybackStreamReader
- OverlayFactory
- PtzCoordinatesManager
- AudioTransmitter
- AudioRecorder
- AnalogMonitorController
- Camera blocking
- Overview
- Getting started
- Referencing entities
- Entity operations
- About access control in the Web SDK
- About video in the Web SDK
- Users and user groups
- Partitions
- Custom fields
- Custom card formats
- Actions
- Events and alarms
- Incidents
- Reports
- Tasks
- Macros
- Custom entity types
- System endpoints
- Performance guide
- Reference
- Under the hood
- Troubleshooting