Skip to main content

Azure Table Storage

Azure Table Storage is used for storing image and video metadata, durable entity state data, and acquisition instance tracking. The Infrastructure package implements a dual-table index pattern for image metadata types — each image metadata type is backed by a primary table and an index table for efficient querying by alternate keys.

Registration

Table storage repositories are registered as part of the AddAlertCAStorageServices extension method:

services.AddAlertCAStorageServices(configuration);

This registers TableServiceClient via Azure.Extensions and all table repositories as scoped services. Index table repositories are injected into their corresponding primary repositories to enable automatic index maintenance on writes.

Configuration

{
"TableStorageOptions": {
"AccountName": "",
"SingleImageMetadataTableName": "",
"PanoImageMetadataTableName": "",
"PreStitchImageMetadataTableName": "",
"VideoMetadataTableName": "",
"ImageAcquisitionInstanceTableName": "",
"DurableEntityTableName": ""
}
}

Index table names are not configurable — each index repository uses a static TableName property (e.g., SingleImageMetadataIndexRepository.TableName = "SingleImageMetadataIndex").

Architecture

Note that video metadata does not have an index table — only single image, pano image, and pre-stitch image metadata use the dual-table pattern.

Dual-Table Index Pattern

Each image metadata entity has two tables. The primary repository's overridden CreateAsync automatically writes to both the primary and index tables:

  • Primary table: PartitionKey = CameraId, queries optimized for "get images for a camera in a time range"
  • Index table: PartitionKey = yyyyMMddHH format, RowKey = deviceId_reverseTicks — optimized for time-based batch lookups

Base Class: TableStorageRepository<T>

All table repositories inherit from TableStorageRepository<T> where T : BaseTableEntity:

MethodDescription
CreateAsync(T entity)Insert entity
UpdateAsync(T entity)Upsert entity
DeleteAsync(string rowkey, string partitionKey)Delete entity by keys
DeleteBatchAsync(IEnumerable<T> entities)Batch delete — groups by partition key, chunks of 100, parallel execution
DeleteByQueryAsync(Expression<Func<T, bool>> predicate)Query then batch delete matching entities
GetByQueryAsync(Expression<Func<T, bool>> predicate)Query with LINQ expressions
GetLatestRow(Expression<Func<T, bool>> predicate)Get single most recent entity matching predicate
GetLatestRowsBySpecifiedCount(Expression<Func<T, bool>> predicate, int rowCount)Get N most recent entities
GetAllAsync()Retrieve all entities

BaseTableEntity implements ITableEntity and provides default PartitionKey, RowKey (GUID), Timestamp (UTC now), and ETag (All).

Repositories

SingleImageMetadataRepository

Stores metadata for individual camera capture images. Overrides CreateAsync to automatically add an index entry.

Interface: ISingleImageMetadataRepository extends ITableStorageRepository<SingleImageMetadataEntity>

Table name: "SingleImageMetadata" (hardcoded in constructor)

MethodDescription
GetLatestImageMetadataByCameras(List<string> cameraIds)Get latest image per camera (iterates each camera)
GetSelectedImageMetadataByCamerasAndTimestamp(List<string> cameraIds, DateTimeOffset start, DateTimeOffset end)Get first image per camera in time range
GetLatestImageMetadataByCameraId(string cameraId)Get single latest image
GetImageMetadataByCameraIdAndTimeRange(string cameraId, DateTimeOffset start, DateTimeOffset end)Get all images in time range
GetRecentImagesByCameraId(string cameraId, int imageCount)Get N most recent images
AddIndexEntryAsync(SingleImageMetadataEntity entity)Write index entry (called automatically by CreateAsync)

PanoImageMetadataRepository

Stores metadata for stitched panoramic images. Overrides CreateAsync to auto-index.

Interface: IPanoImageMetadataRepository extends ITableStorageRepository<PanoImageMetadataEntity>

Table name: "PanoImageMetadata"

MethodDescription
GetStitchedImagesByCameraInTimeRange(string cameraId, DateTimeOffset start, DateTimeOffset end)Get pano images in time range
GetLatestStitchedImageByCameraId(string cameraId)Get latest pano image
GetLatestClearDayImageByCamera(string cameraId)Get latest image with IsClearDay == true
AddIndexEntryAsync(PanoImageMetadataEntity entity)Write index entry

PreStitchImageMetadataRepository

Stores metadata for raw pre-stitch images (before panoramic stitching). Overrides CreateAsync to auto-index.

Interface: IPreStitchImageMetadataRepository extends ITableStorageRepository<PreStitchImageMetadataEntity>

Table name: "PreStitchImageMetadata"

MethodDescription
GetImagesByCorrelationId(string cameraId, string correlationId)Get images by PanoImageCorrelationId
AddIndexEntryAsync(PreStitchImageMetadataEntity entity)Write index entry

Index Repositories

Each image metadata type has a corresponding index repository. All three share the same pattern — a static TableName and a single AddIndexEntryAsync method that creates a MetadataIndexEntity:

RepositoryStatic Table Name
SingleImageMetadataIndexRepository"SingleImageMetadataIndex"
PanoImageMetadataIndexRepository"PanoImageMetadataIndex"
PreStitchImageMetadataIndexRepository"PreStitchImageMetadataIndex"
public async Task AddIndexEntryAsync(DateTimeOffset timestamp, string rowkey, string deviceId)
{
var indexEntity = MetadataIndexEntity.Create(timestamp, rowkey, deviceId);
await base.CreateAsync(indexEntity);
}

VideoMetadataRepository

Stores metadata for video recordings. Does not inherit ITableStorageRepository<T> — uses a standalone interface. No index table.

Interface: IVideoMetadataRepository (standalone, not extending ITableStorageRepository)

Table name: configured via TableStorageOptions.VideoMetadataTableName

MethodDescription
GetLatestVideoMetadataByCameraId(string cameraId)Get latest video metadata
GetVideoMetadataByCameraIdAndTimeRange(string cameraId, DateTimeOffset start, DateTimeOffset end)Get videos in time range
InsertVideoMetadata(VideoMetadataEntity metadata)Insert video metadata (delegates to base CreateAsync)

DurableEntityDataRepository

Stores state data for Azure Durable Entity functions — orchestration mode status, rates, and timing per device. Does not inherit ITableStorageRepository<T>.

Interface: IDurableEntityDataRepository (standalone)

Table name: configured via TableStorageOptions.DurableEntityTableName

The DurableEntityData entity has properties: DeviceId, CurrentConfig, SingleModeStatus/Rate, PanoModeStatus/Rate, LeasedModeStatus/Rate/EndDt, TurboModeStatus/Rate/EndDt, LastModifiedDt.

MethodDescription
GetAllDurableEntities()Get all durable entities
GetLatestDurableEntityByDeviceId(string deviceId)Get entity by device (partition key lookup)
InsertOrMergeDurableEntity(DurableEntityData entity)Upsert with TableUpdateMode.Merge
DeleteDurableEntity(string deviceId)Delete entity by device ID

ImageAcquisitionInstanceRepository

Tracks image acquisition orchestration instances (recent run history).

Interface: IImageAcquisitionInstanceRepository (standalone)

Table name: configured via TableStorageOptions.ImageAcquisitionInstanceTableName

MethodDescription
GetCurrentOrchestrationStatus(int minutes)Get running instances from last N minutes (filters out @camerastate@ entries)

Usage Example

using Alert.CA.Infrastructure.Repositories.TableStorageRepositories.Interfaces;

public class MetadataService(
ISingleImageMetadataRepository metadataRepo,
IPanoImageMetadataRepository panoRepo)
{
public async Task<IEnumerable<SingleImageMetadataEntity>> GetImagesForCamera(
string cameraId, DateTimeOffset start, DateTimeOffset end)
{
return await metadataRepo.GetImageMetadataByCameraIdAndTimeRange(
cameraId, start, end);
}

public async Task<PanoImageMetadataEntity?> GetLatestClearDay(string cameraId)
{
return await panoRepo.GetLatestClearDayImageByCamera(cameraId);
}
}