This example project demonstrates a generic IoT use case for ScyllaDB in C#.
The documentation for this application and guided exercise is here.
The application allows tracking of pets health indicators and consist of three parts:
- migrate (
dotnet run --project CarePet.Migrate.csproj) - creates thecarepetkeyspace and tables - collar (
dotnet run --project CarePet.Sensor.csproj) - generates a pet health data and pushes it into the storage - web app (
dotnet run --project CarePet.csproj) - REST API service for tracking pets health state
Prerequisites:
To run a local ScyllaDB cluster consisting of three nodes with
the help of docker and docker-compose execute:
$ docker-compose up -d
Docker-compose will spin up three nodes: csharp-carepet-scylla1-1, csharp-carepet-scylla2-1
and csharp-carepet-scylla3-1. You can access them with the docker command.
To execute CQLSH:
$ docker exec -it csharp-csharp-carepet-scylla1-1 cqlsh
To execute nodetool:
$ docker exec -it csharp-carepet-scylla1-1 nodetool status
Shell:
$ docker exec -it csharp-carepet-scylla1-1 shell
You can inspect any node by means of the docker inspect command
as follows. for example:
$ docker inspect csharp-carepet-scylla1-1
To get node IP address run:
$ docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' csharp-carepet-scylla1-1
You will need to reference this value multiple times later so if it's easier
for you can save it as a variable NODE1:
$ NODE1=$(docker inspect -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' csharp-carepet-scylla1-1)
To initialize database execute:
$ dotnet build CarePet.Migrate.csproj
$ dotnet run --project CarePet.Migrate.csproj --hosts $NODE1
Expected output:
info: CarePet.Commands.MigrateCommand[0]
Creating keyspace carepet...
info: CarePet.Commands.MigrateCommand[0]
Keyspace carepet created successfully
info: CarePet.Commands.MigrateCommand[0]
Creating tables...
Keyspace: carepet; Table: measurement
Keyspace: carepet; Table: owner
Keyspace: carepet; Table: pet
Keyspace: carepet; Table: sensor
Keyspace: carepet; Table: sensor_avg
You can check the database structure with:
$ docker exec -it csharp-carepet-scylla1-1 cqlsh
cqlsh> DESCRIBE KEYSPACES
carepet system_schema system_auth system system_distributed system_traces
cqlsh> USE carepet;
cqlsh:carepet> DESCRIBE TABLES
pet sensor_avg measurement owner sensor
cqlsh:carepet> DESCRIBE TABLE pet
CREATE TABLE carepet.pet (
owner_id uuid,
pet_id uuid,
chip_id text,
species text,
breed text,
color text,
gender text,
address text,
age int,
name text,
weight float,
PRIMARY KEY (owner_id, pet_id)
) WITH CLUSTERING ORDER BY (pet_id ASC)
AND bloom_filter_fp_chance = 0.01
AND caching = {'keys': 'ALL', 'rows_per_partition': 'ALL'}
AND comment = ''
AND compaction = {'class': 'SizeTieredCompactionStrategy'}
AND compression = {'sstable_compression': 'org.apache.cassandra.io.compress.LZ4Compressor'}
AND crc_check_chance = 1.0
AND dclocal_read_repair_chance = 0.1
AND default_time_to_live = 0
AND gc_grace_seconds = 864000
AND max_index_interval = 2048
AND min_index_interval = 128
AND read_repair_chance = 0.0
AND speculative_retry = '99.0PERCENTILE';
To generate pet health data:
$ dotnet build CarePet.Sensor.csproj
$ dotnet run --project CarePet.Sensor.csproj --hosts $NODE1 --datacenter datacenter1 --measure 00:01:00 --buffer-interval 00:01:00
Expected output:
info: CarePet.Sensor[0]
owner = Owner{ownerId=c94a6726-85d4-4c05-b5a8-351837c26c42, name='N9fhYPFp', address='Hu6YX0yTyk'}
info: CarePet.Sensor[0]
pet = Pet{ownerId=c94a6726-85d4-4c05-b5a8-351837c26c42, petId=6db45498-244c-460c-a567-2098ab7fb848, chipId='', species='', breed='', color='', gender='', age=63, weight=13.053496, address='home', name='gj5vYSKI'}
info: CarePet.Sensor[0]
sensor = Sensor{petId=6db45498-244c-460c-a567-2098ab7fb848, sensorId=3c6ba0fc-9516-4dc4-8ab5-1ebe50f37f03, type='T'}
info: CarePet.Sensor[0]
sensor = Sensor{petId=6db45498-244c-460c-a567-2098ab7fb848, sensorId=33d51816-5df6-4e62-ad0b-4e039b198dab, type='P'}
info: CarePet.Sensor[0]
sensor = Sensor{petId=6db45498-244c-460c-a567-2098ab7fb848, sensorId=d03a3de6-5f19-4cdc-854c-392f3212b70c, type='L'}
info: CarePet.Sensor[0]
sensor = Sensor{petId=6db45498-244c-460c-a567-2098ab7fb848, sensorId=35cbd6db-a580-44bb-ac14-e82f8c1fb727, type='R'}
info: CarePet.Sensor[0]
Measure{sensorId=3c6ba0fc-9516-4dc4-8ab5-1ebe50f37f03, ts=8/25/2025 9:16:02 AM +00:00, value=102}
info: CarePet.Sensor[0]
Measure{sensorId=33d51816-5df6-4e62-ad0b-4e039b198dab, ts=8/25/2025 9:16:02 AM +00:00, value=89}
info: CarePet.Sensor[0]
Measure{sensorId=d03a3de6-5f19-4cdc-854c-392f3212b70c, ts=8/25/2025 9:16:02 AM +00:00, value=0.30064157}
info: CarePet.Sensor[0]
Measure{sensorId=35cbd6db-a580-44bb-ac14-e82f8c1fb727, ts=8/25/2025 9:16:02 AM +00:00, value=36}
info: CarePet.Sensor[0]
pushing data
info: CarePet.Sensor[0]
Measure{sensorId=3c6ba0fc-9516-4dc4-8ab5-1ebe50f37f03, ts=8/25/2025 9:17:02 AM +00:00, value=105}
info: CarePet.Sensor[0]
Measure{sensorId=33d51816-5df6-4e62-ad0b-4e039b198dab, ts=8/25/2025 9:17:02 AM +00:00, value=93}
info: CarePet.Sensor[0]
Measure{sensorId=d03a3de6-5f19-4cdc-854c-392f3212b70c, ts=8/25/2025 9:17:02 AM +00:00, value=2.0097651}
info: CarePet.Sensor[0]
Measure{sensorId=35cbd6db-a580-44bb-ac14-e82f8c1fb727, ts=8/25/2025 9:17:02 AM +00:00, value=37}
info: CarePet.Sensor[0]
pushing data
...
You can stop the data generation with Ctrl+C.
To start the web server:
$ dotnet run --project CarePet.csproj --hosts $NODE1 --datacenter datacenter1
Expected output:
info: CarePet.Program[0]
Starting CarePet server on port 8000...
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://0.0.0.0:8000
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
Now you can open the Swagger UI at http://localhost:8000/swagger or test the endpoints directly.
Use available pets/owners/sensors. To populate them use sensor command.
Get owner information:
$ curl http://127.0.0.1:8000/api/owner/{owner-id}
Expected output:
{"ownerId":"f1c8ca4c-06c5-4b1a-9e39-8e3d4d5f6789","address":"123 Main St, New York, NY","name":"John Doe"}
Get pets by owner:
$ curl http://127.0.0.1:8000/api/pet/owner/{owner-id}
Expected output:
[{"ownerId":"f1c8ca4c-06c5-4b1a-9e39-8e3d4d5f6789","petId":"a1b2c3d4-e5f6-7890-1234-567890abcdef","chipId":"CHIP123456","species":"Dog","breed":"Labrador","color":"Golden","gender":"Male","age":3,"weight":25.5,"address":"","name":"Buddy"}]
Get sensors for a pet:
$ curl http://127.0.0.1:8000/api/sensor/pet/{pet-id}
Expected output:
[{"petId":"a1b2c3d4-e5f6-7890-1234-567890abcdef","sensorId":"5a9da084-ea49-4ab1-b2f8-d3e3d9715e7d","type":"T"},{"petId":"a1b2c3d4-e5f6-7890-1234-567890abcdef","sensorId":"e81915d6-1155-45e4-9174-c58e4cb8cecf","type":"P"}]
Get measurements for a sensor with time range:
$ curl "http://127.0.0.1:8000/api/sensor/{sensor-id}/measurements?from=2025-08-06T00:00:00Z&to=2025-08-07T00:00:00Z"
Expected output:
[{"sensorId":"5a9da084-ea49-4ab1-b2f8-d3e3d9715e7d","timestamp":"2025-08-06T12:30:00Z","value":38.5}]
Get daily averaged values:
$ curl http://127.0.0.1:8000/api/sensor/{sensor-id}/values/day/2025-08-06
Expected output:
[0,0,0,0,0,0,0,0,0,0,0,0,38.5,39.1,0,0,0,0,0,0,0,0,0,0]
Package structure is as follows:
| Name | Purpose |
|---|---|
| Commands/ | CLI command implementations |
| Commands/MigrateCommand | Database schema installation |
| Commands/SensorCommand | Pet collar simulation |
| Controllers/ | REST API controllers |
| Database/ | ScyllaDB connection and repositories |
| Models/ | Data models (Owner, Pet, Sensor, etc.) |
| Config/ | Configuration classes |
| cql/ | Database schema files |
The application uses ASP.NET Core to serve REST endpoints with Swagger/OpenAPI documentation available at /swagger.
The API provides the following endpoints:
GET /api/owner/{id}- Get owner informationGET /api/pet/owner/{ownerId}- Get all pets for an ownerGET /api/pet/{ownerId}/{petId}- Get specific pet informationGET /api/sensor/pet/{petId}- Get all sensors for a petGET /api/sensor/{sensorId}/measurements- Get measurements with optional time rangeGET /api/sensor/{sensorId}/values/day/{date}- Get hourly averaged values for a day
Collars are small devices that attach to pets and collect data with the help of different sensors. After the data is collected it may be delivered to the central database for the analysis and health status checking.
Collar code sits in the Commands/SensorCommand and uses the ScyllaDB C# driver
to connect to the database directly and publish its data.
Collar gathers sensors measurements, aggregates data in a buffer and sends it every minute or when the buffer is full.
Overall all applications in this repository use the ScyllaDB C# driver for:
- Relational Object Mapping (ORM)
- Migration support (DDL)
- Connection session management (Session, Cluster)
- Query builder
The application leverages .NET's built-in dependency injection, configuration management, and logging frameworks for a robust and maintainable codebase.
The application uses the following sensor types:
- Temperature (T): Pet body temperature (36-46°C)
- Pulse (P): Heart rate (60-110 bpm)
- Location (L): GPS coordinates
- Respiration (R): Breathing rate (10-30 breaths/min)
- ScyllaDB C# Driver: Optimized for ScyllaDB with connection pooling and prepared statements
- Modern C# patterns: Async/await, nullable reference types, dependency injection
- Command-line interface: Built with CommandLineParser for easy CLI usage
- Configuration: Supports appsettings.json, environment variables, and command-line options
- API documentation: Automatic Swagger/OpenAPI documentation generation
- Docker support: Complete containerization with Docker and Docker Compose
The application can be configured through:
appsettings.jsonfile- Environment variables (with
Database__prefix) - Command-line arguments
Example configuration:
{
"Database": {
"Keyspace": "carepet",
"Hosts": ["localhost"],
"Port": 9042,
"Datacenter": "datacenter1"
}
}All commands support the following ScyllaDB connection options:
--hosts: ScyllaDB host addresses (default: localhost)--port: ScyllaDB port (default: 9042)--datacenter: ScyllaDB datacenter name (default: datacenter1)--keyspace: Keyspace name (default: carepet)
Additional command-specific options:
Migrate command:
--replication-factor: Keyspace replication factor (default: 3)
Sensor command:
--interval: Measurement interval in milliseconds (default: 1000)--buffer-size: Buffer size for batching measurements (default: 100)
Server command:
--port: HTTP server port (default: 8000)
Use --help with any command to see all available options:
$ dotnet run --project CarePet.Migrate.csproj --help
$ dotnet run --project CarePet.Sensor.csproj --help
$ dotnet run --project CarePet.csproj --help