-
Notifications
You must be signed in to change notification settings - Fork 6
plugin sdk database
Plugins can integrate with SQL Server for persistent storage. The database infrastructure is managed by Security Center through a state machine.
flowchart TB
A[Plugin Host Database Layer] --> B[Creates/Upgrades/Connects to SQL Server]
B --> C[Calls plugin's DatabaseManager methods]
C --> D[Plugin uses connection string for data access]
Database state machine:
stateDiagram-v2
[*] --> Startup
Startup --> Connected: Version valid
Startup --> WaitingForUpgrade: Version too old
Startup --> Disconnected: Connection failed or no DB
Disconnected --> Startup: Retry or recovered
Disconnected --> Creating: CreateDatabase or auto-create
Creating --> Startup: Create complete
Creating --> Error: Create failed
WaitingForUpgrade --> Upgrading: UpgradeDatabase
WaitingForUpgrade --> Disconnected: Connection lost
Upgrading --> Startup: Upgrade finished
Connected --> Disconnected: Connection lost
Connected --> BackingUp: BackupDatabase
Connected --> Restoring: RestoreDatabase
Connected --> Dropping: DropDatabase
Connected --> Cleanup: CleanupDatabase
Connected --> ResolvingConflicts: ResolveConflicts
BackingUp --> Connected: Backup complete (prev Connected)
BackingUp --> Startup: Backup complete (prev not Connected)
BackingUp --> Error: Backup failed
Restoring --> Startup: Restore complete
Restoring --> Error: Restore failed
Dropping --> Disconnected: Drop complete
Cleanup --> Startup: Cleanup complete
ResolvingConflicts --> Startup
Error --> Startup: Retry
Key concepts:
- Security Center manages database lifecycle
- Plugin provides schema and logic via DatabaseManager
- Each plugin role instance gets its own database
- Security Center provides database configuration for each role
- Supports SQL Server only (no other databases)
Implement IPluginDatabaseSupport interface:
public class MyPlugin : Plugin, IPluginDatabaseSupport
{
public DatabaseManager DatabaseManager => new MyDatabaseManager();
}Interface requirements:
- Must provide a
DatabaseManagerinstance - DatabaseManager defines schema, upgrades, and cleanup
- Security Center calls DatabaseManager methods at appropriate times
Create a class inheriting from DatabaseManager:
Every DatabaseManager subclass must override GetSpecificCreationScript and SetDatabaseInformation. Override OnDatabaseStateChanged, GetDatabaseCleanupThresholds, DatabaseCleanup, and GetDatabaseUpgradeItems only when your plugin needs state notifications, cleanup settings, cleanup work, or database upgrade scripts.
public class MyDatabaseManager : DatabaseManager
{
private DatabaseConfiguration m_config;
public override string GetSpecificCreationScript(string databaseName)
{
// Return the SQL that creates your plugin's tables.
return string.Empty;
}
public override void SetDatabaseInformation(DatabaseConfiguration config)
{
m_config = config;
}
}Controls whether the database is created in single-user mode:
public class MyDatabaseManager : DatabaseManager
{
// Override to disable single-user mode during database creation
public override bool UseSingleUserDatabaseCreation => false;
}Default value: true
When true (default):
- Database is created in single-user mode
- Prevents other connections during creation
- Provides exclusive access for schema setup
- Recommended for most scenarios
When false:
- Database is created in multi-user mode
- Other connections may access the database during creation
- Use when single-user mode causes issues with your database setup
The database layer starts after OnPluginLoaded() and before OnPluginStart():
flowchart TB
A[1. Plugin constructed] --> B["2. Plugin.Initialize(engine, roleGuid, culture)<br/>OnPluginLoaded() runs"]
B --> C{Implements IPluginDatabaseSupport?}
C -->|No| H1[3. OnPluginStart called]
C -->|Yes| D[3. DatabaseManager.SetDatabaseInformation]
D --> E["4. Database layer created<br/>reads GetDatabaseUpgradeItems()"]
E --> F[5. Database state machine starts]
F --> G["6. Creating state calls GetSpecificCreationScript()<br/>when database is missing"]
F --> I["7. Upgrading state runs upgrade items<br/>when version is behind"]
F --> J["8. OnDatabaseStateChanged notifications<br/>Startup, Disconnected, Creating, Upgrading, Connected, Error"]
F --> H2[9. OnPluginStart called]
Critical timing:
- In
OnPluginLoaded(), database may not be ready -
OnPluginStart()does not guaranteeDatabaseState.Connected - Use
OnDatabaseStateChanged()to wait forDatabaseState.Connected
See Plugin SDK Lifecycle for lifecycle details.
Called if database doesn't exist. Returns SQL to create tables and schema.
public override string GetSpecificCreationScript(string databaseName)
{
return $@"
CREATE TABLE [{databaseName}].[dbo].[EventLog] (
[Id] INT IDENTITY(1,1) PRIMARY KEY,
[Timestamp] DATETIME2 NOT NULL,
[EventType] NVARCHAR(50) NOT NULL,
[Description] NVARCHAR(MAX),
[EntityGuid] UNIQUEIDENTIFIER
);
CREATE INDEX IX_EventLog_Timestamp
ON [{databaseName}].[dbo].[EventLog]([Timestamp]);
CREATE TABLE [{databaseName}].[dbo].[Configuration] (
[Key] NVARCHAR(100) PRIMARY KEY,
[Value] NVARCHAR(MAX)
);
";
}Important
- Security Center supplies the database name
- Always use the
databaseNameparameter in your SQL - If the script fails, database creation fails
Called before the database layer starts and provides connection info.
public override void SetDatabaseInformation(DatabaseConfiguration config)
{
m_config = config;
// Use CreateSqlDatabaseConnection() for a ready-to-use connection
// or use ConnectionString + DatabaseCredential for manual connection creation
// Store the connection information for later database operations
}DatabaseConfiguration properties:
-
ConnectionString- SQL Server connection string (does not include credentials if using SqlCredential) -
DatabaseCredential- SqlCredential for authentication (null if using Windows integrated security) -
CreateSqlDatabaseConnection()- Creates a configured SqlConnection ready for use
CreateSqlDatabaseConnection() returns a Microsoft.Data.SqlClient.SqlConnection, and DatabaseCredential is a Microsoft.Data.SqlClient.SqlCredential - not the legacy System.Data.SqlClient types. Reference the Microsoft.Data.SqlClient package if you declare these types explicitly or build commands and parameters yourself.
When called:
- Before the database layer starts
- Before
OnPluginStart()
Called on database state transitions.
public override void OnDatabaseStateChanged(DatabaseNotification notification)
{
switch (notification.State)
{
case DatabaseState.Disconnected:
// Handle disconnected state
break;
case DatabaseState.Creating:
// Handle database creation state
break;
case DatabaseState.Upgrading:
// Handle database upgrade state
break;
case DatabaseState.Connected:
// Handle connected state
break;
case DatabaseState.Error:
// Handle database error state
break;
}
}DatabaseNotification properties:
-
State- Current database state
Config Tool allows administrators to configure data retention policies for plugins.
Define data retention thresholds that appear in Config Tool:
public override IEnumerable<DatabaseCleanupThreshold> GetDatabaseCleanupThresholds()
{
yield return new DatabaseCleanupThreshold(
name: "EventLog",
title: "Event Log",
defaultIsEnabled: true,
defaultRetentionPeriod: 90);
yield return new DatabaseCleanupThreshold(
name: "AuditTrail",
title: "Audit Trail",
defaultIsEnabled: true,
defaultRetentionPeriod: 365);
}DatabaseCleanupThreshold constructor:
DatabaseCleanupThreshold(string name, string title, bool defaultIsEnabled = true, int defaultRetentionPeriod = 90)-
name- Unique identifier for this threshold (used inDatabaseCleanupcallback) -
title- User-friendly title displayed in Config Tool -
defaultIsEnabled- Whether cleanup is enabled by default -
defaultRetentionPeriod- Default days to retain data (must be > 0 if enabled, must be 0 if disabled)
Config Tool displays these thresholds and allows administrators to:
- Adjust retention periods
- Schedule automatic cleanup
- Manually trigger cleanup
Called when cleanup is triggered (scheduled or manual):
public override void DatabaseCleanup(string name, int retentionPeriod)
{
switch (name)
{
case "EventLog":
CleanupEventLog(retentionPeriod);
break;
case "AuditTrail":
CleanupAuditTrail(retentionPeriod);
break;
}
}
private void CleanupEventLog(int retentionDays)
{
var cutoffDate = DateTime.UtcNow.AddDays(-retentionDays);
using (var connection = m_config.CreateSqlDatabaseConnection())
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = @"
DELETE FROM EventLog
WHERE Timestamp < @CutoffDate";
command.Parameters.AddWithValue("@CutoffDate", cutoffDate);
command.ExecuteNonQuery();
}
}
}Important
- Cleanup runs on a background thread
- Can be long-running without blocking plugin
- Should handle errors gracefully
- Log progress and results
Support database schema versioning with upgrade scripts.
Define incremental schema upgrades:
public override IEnumerable<DatabaseUpgradeItem> GetDatabaseUpgradeItems()
{
// Upgrade from version 1 to version 2
yield return new DatabaseUpgradeItem(
sourceVersion: 1,
targerVersion: 2,
upgradeScript: @"
ALTER TABLE EventLog
ADD [Severity] INT NOT NULL DEFAULT 0;
");
// Upgrade from version 2 to version 3
yield return new DatabaseUpgradeItem(
sourceVersion: 2,
targerVersion: 3,
upgradeScript: @"
CREATE TABLE AuditLog (
Id INT IDENTITY(1,1) PRIMARY KEY,
Timestamp DATETIME2 NOT NULL,
Action NVARCHAR(100),
UserGuid UNIQUEIDENTIFIER
);
");
// Upgrade from version 3 to version 4
yield return new DatabaseUpgradeItem(
sourceVersion: 3,
targerVersion: 4,
upgradeScript: @"
CREATE INDEX IX_AuditLog_Timestamp
ON AuditLog(Timestamp);
");
}DatabaseUpgradeItem constructor:
DatabaseUpgradeItem(int sourceVersion, int targerVersion, string upgradeScript)-
sourceVersion- The version to upgrade from -
targerVersion- The version to upgrade to (note: parameter name contains a typo in the API) -
upgradeScript- SQL script to run for this upgrade
Note
The parameter name targerVersion contains a typo in the SDK API. Use this exact spelling when using named parameters.
How upgrades work:
- Security Center tracks current database version
- Applies upgrades sequentially (1 -> 2 -> 3 -> 4)
- Each upgrade runs in a transaction
- If upgrade fails, database stays at previous version
- Upgrades run during database state transitions and can still be in progress when
OnPluginStart()runs
Version numbering rules:
- Each upgrade specifies source and target versions
- Upgrades must be sequential (no gaps)
- Never change existing upgrade scripts after deployment
Use the DatabaseConfiguration from SetDatabaseInformation(). Wait for
DatabaseState.Connected in OnDatabaseStateChanged() before using the database.
private DatabaseConfiguration m_dbConfig;
public override void SetDatabaseInformation(DatabaseConfiguration config)
{
m_dbConfig = config;
}
public override void OnDatabaseStateChanged(DatabaseNotification notification)
{
if (notification.State == DatabaseState.Connected)
{
InsertEvent("Plugin started", "PluginStarted");
}
}
private void InsertEvent(string description, string eventType)
{
using (var connection = m_dbConfig.CreateSqlDatabaseConnection())
{
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandText = @"
INSERT INTO EventLog (Timestamp, EventType, Description)
VALUES (@Timestamp, @EventType, @Description)";
command.Parameters.AddWithValue("@Timestamp", DateTime.UtcNow);
command.Parameters.AddWithValue("@EventType", eventType);
command.Parameters.AddWithValue("@Description", description);
command.ExecuteNonQuery();
}
}
}Use async methods for database access:
private async Task<List<EventRecord>> GetRecentEventsAsync(int count)
{
var events = new List<EventRecord>();
using (var connection = m_dbConfig.CreateSqlDatabaseConnection())
{
await connection.OpenAsync();
using (var command = connection.CreateCommand())
{
command.CommandText = @"
SELECT TOP (@Count) Timestamp, EventType, Description
FROM EventLog
ORDER BY Timestamp DESC";
command.Parameters.AddWithValue("@Count", count);
using (var reader = await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
events.Add(new EventRecord
{
Timestamp = reader.GetDateTime(0),
EventType = reader.GetString(1),
Description = reader.GetString(2)
});
}
}
}
}
return events;
}Administrators configure database settings in Config Tool:
Path: System > Roles > PLUGIN-ROLE > Resources > Database
Replace PLUGIN-ROLE with your plugin role name.
Configurable settings:
- SQL Server instance
- Database name (auto-generated, can be changed)
- Connection encryption
- Cleanup thresholds and schedules
Database capabilities:
- Database resources are added to the role when
IPluginDatabaseSupportis implemented - Database encryption support is available with
IPluginDatabaseSupport
- Plugin SDK overview: Plugin architecture, lifecycle, and components.
- Plugin SDK threading: Engine thread, QueueUpdate, and async patterns for plugins.
- Plugin SDK lifecycle: Plugin initialization, startup, and disposal phases.
- Plugin SDK state management: Reporting plugin and entity health to Config Tool.
- About logging: SDK logging configuration and severity levels.
- Overview
- Connecting to Security Center
- SDK certificates
- Referencing SDK assemblies
- SDK compatibility
- Entities
- Entity cache
- Entity loading, cache, and query options
- 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
- Engine extensions
- 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