{"id":30830566,"url":"https://github.com/thenativeweb/eventsourcingdb-client-dotnet","last_synced_at":"2025-09-06T15:55:29.340Z","repository":{"id":299204550,"uuid":"980258831","full_name":"thenativeweb/eventsourcingdb-client-dotnet","owner":"thenativeweb","description":"The official .NET client SDK for EventSourcingDB.","archived":false,"fork":false,"pushed_at":"2025-08-18T18:51:35.000Z","size":73,"stargazers_count":6,"open_issues_count":5,"forks_count":3,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-08-18T19:02:41.506Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://www.eventsourcingdb.io","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/thenativeweb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-08T20:45:09.000Z","updated_at":"2025-08-18T18:50:34.000Z","dependencies_parsed_at":"2025-06-15T10:28:03.150Z","dependency_job_id":"377b8ac0-1c2d-47ef-8869-3450099804f4","html_url":"https://github.com/thenativeweb/eventsourcingdb-client-dotnet","commit_stats":null,"previous_names":["thenativeweb/eventsourcingdb-client-dotnet"],"tags_count":25,"template":false,"template_full_name":null,"purl":"pkg:github/thenativeweb/eventsourcingdb-client-dotnet","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thenativeweb%2Feventsourcingdb-client-dotnet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thenativeweb%2Feventsourcingdb-client-dotnet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thenativeweb%2Feventsourcingdb-client-dotnet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thenativeweb%2Feventsourcingdb-client-dotnet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/thenativeweb","download_url":"https://codeload.github.com/thenativeweb/eventsourcingdb-client-dotnet/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/thenativeweb%2Feventsourcingdb-client-dotnet/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":273926542,"owners_count":25192318,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-06T02:00:13.247Z","response_time":2576,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-09-06T15:55:18.836Z","updated_at":"2025-09-06T15:55:29.324Z","avatar_url":"https://github.com/thenativeweb.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# eventsourcingdb\n\nThe official .NET client SDK for [EventSourcingDB](https://www.eventsourcingdb.io) – a purpose-built database for event sourcing.\n\nEventSourcingDB enables you to build and operate event-driven applications with native support for writing, reading, and observing events. This client SDK provides convenient access to its capabilities in .NET.\n\nFor more information on EventSourcingDB, see its [official documentation](https://docs.eventsourcingdb.io/).\n\nThis client SDK includes support for [Testcontainers](https://testcontainers.com/) to spin up EventSourcingDB instances in integration tests. For details, see [Using Testcontainers](#using-testcontainers).\n\n## Getting Started\n\nInstall the client SDK:\n\n```shell\ndotnet add package EventSourcingDb\n```\n\nImport the `Client` class and create an instance by providing the URL of your EventSourcingDB instance and the API token to use:\n\n```csharp\nusing EventSourcingDb;\n\nvar url = new Uri(\"http://localhost:3000\");\nvar apiToken = \"secret\";\n\nvar client = new Client(url, apiToken);\n```\n\nThen call the `PingAsync` method to check whether the instance is reachable. If it is not, the method will throw an exception:\n\n```csharp\nawait client.PingAsync();\n```\n\nOptionally, you might provide a `CancellationToken`.\n\n*Note that `PingAsync` does not require authentication, so the call may succeed even if the API token is invalid.*\n\nIf you want to verify the API token, call `VerifyApiTokenAsync`. If the token is invalid, the function will throw an exception:\n\n```csharp\nawait client.VerifyApiTokenAsync();\n```\n\nOptionally, you might provide a `CancellationToken`.\n\n## Writing Events\n\nCall the `WriteEventsAsync` method and provide a collection of events. You do not have to set all event fields – some are automatically added by the server.\n\nSpecify `Source`, `Subject`, `Type`, and `Data` according to the [CloudEvents](https://docs.eventsourcingdb.io/fundamentals/cloud-events/) format.\n\nFor `Data`, you may provide any object that is serializable to JSON. It is recommended to use properties with JSON attributes to control the serialization.\n\nThe method returns a list of written events, including the fields added by the server:\n\n```csharp\nvar @event = new EventCandidate(\n    Source: \"https://library.eventsourcingdb.io\",\n    Subject: \"/books/42\",\n    Type: \"io.eventsourcingdb.library.book-acquired\",\n    Data: new {\n        title = \"2001 – A Space Odyssey\",\n        author = \"Arthur C. Clarke\",\n        isbn = \"978-0756906788\"\n    }\n);\n\nvar writtenEvents = await client.WriteEventsAsync(new[] { @event });\n```\n\n*Optionally, you might provide a `CancellationToken`.*\n\n### Using the `IsSubjectPristine` precondition\n\nIf you only want to write events in case a subject (such as `/books/42`) does not yet have any events, use the `IsSubjectPristinePrecondition`:\n\n```csharp\nvar writtenEvents = await client.WriteEventsAsync(\n    new[] { @event },\n    new[] { Precondition.IsSubjectPristinePrecondition(\"/books/42\") }\n);\n```\n\n### Using the `IsSubjectOnEventId` precondition\n\nIf you only want to write events in case the last event of a subject (such as `/books/42`) has a specific ID (e.g., `\"0\"`), use the `IsSubjectOnEventIdPrecondition`:\n\n```csharp\nvar writtenEvents = await client.WriteEventsAsync(\n    new[] { @event },\n    new[] { Precondition.IsSubjectOnEventIdPrecondition(\"/books/42\", \"0\") }\n);\n```\n\n*Note that according to the CloudEvents standard, event IDs must be of type string.*\n\n### Using the `IsEventQlQueryTrue` precondition\n\nIf you want to write events depending on an EventQL query, use the `IsEventQlQueryTruePrecondition`:\n\n```csharp\nvar writtenEvents = await client.WriteEventsAsync(\n    new[] { @event },\n    new[] { Precondition.IsEventQlQueryTruePrecondition(\"FROM e IN events WHERE e.type == 'io.eventsourcingdb.library.book-borrowed' PROJECT INTO COUNT() \u003c 10\") }\n);\n```\n\n*Note that the query must return a single row with a single value, which is interpreted as a boolean.*\n\n## Reading Events\n\nTo read all events of a subject, call the `ReadEventsAsync` method and pass the subject and an options object. Set `Recursive` to `false` to ensure that only events of the given subject are returned, not events of nested subjects.\n\nThe method returns an async stream, which you can iterate over using `await foreach`:\n\n```csharp\nawait foreach (var @event in client.ReadEventsAsync(\n    \"/books/42\",\n    new ReadEventsOptions(Recursive: false)))\n{\n    // Handle event\n}\n```\n\nIf an error occurs, the stream will terminate with an exception.\n\n*Optionally, you might provide a `CancellationToken`.*\n\n#### Deserializing Event Data\n\nEach event contains a `Data` property, which holds the event payload as JSON. To deserialize this payload into a strongly typed object, call `GetData\u003cT\u003e()`:\n\n```csharp\nvar book = @event.GetData\u003cBookAcquired\u003e();\n```\n\nAlternatively, you can use the non-generic overload `GetData(Type)` to resolve the type at runtime:\n\n```csharp\nvar type = typeof(BookAcquired);\nvar book = (BookAcquired)@event.GetData(type)!;\n```\n\nIf you prefer to work directly with the JSON structure, access the `Data` property as a `JsonElement`:\n\n```csharp\nvar title = @event.Data.GetProperty(\"title\").GetString();\n```\n\n### Reading from subjects recursively\n\nIf you want to read not only all events of a subject, but also the events of all nested subjects, set `Recursive` to `true`:\n\n```csharp\nawait foreach (var @event in client.ReadEventsAsync(\n    \"/books/42\",\n    new ReadEventsOptions(Recursive: true)))\n{\n    // ...\n}\n```\n\nThis also allows you to read *all* events ever written by using `/` as the subject.\n\n### Reading in anti-chronological order\n\nBy default, events are read in chronological order. To read in anti-chronological order, use the `Order` option:\n\n```csharp\nawait foreach (var @event in client.ReadEventsAsync(\n    \"/books/42\",\n    new ReadEventsOptions(\n        Recursive: false,\n        Order: Order.Antichronological)))\n{\n    // ...\n}\n```\n\n*Note that you can also use `Order.Chronological` to explicitly enforce the default order.*\n\n### Specifying bounds\n\nIf you only want to read a range of events, set the `LowerBound` and `UpperBound` options — either one of them or both:\n\n```csharp\nawait foreach (var @event in client.ReadEventsAsync(\n    \"/books/42\",\n    new ReadEventsOptions(\n        Recursive: false,\n        LowerBound: new Bound(\"100\", BoundType.Inclusive),\n        UpperBound: new Bound(\"200\", BoundType.Exclusive))))\n{\n    // ...\n}\n```\n\n### Starting from the latest event of a given type\n\nTo start reading from the latest event of a specific type, set the `FromLatestEvent` option:\n\n```csharp\nawait foreach (var @event in client.ReadEventsAsync(\n    \"/books/42\",\n    new ReadEventsOptions(\n        Recursive: false,\n        FromLatestEvent: new ReadFromLatestEvent(\n            Subject: \"/books/42\",\n            Type: \"io.eventsourcingdb.library.book-borrowed\",\n            IfEventIsMissing: ReadIfEventIsMissing.ReadEverything))))\n{\n    // ...\n}\n```\n\n*Note that `FromLatestEvent` and `LowerBound` cannot be used at the same time.*\n\n## Running EventQL Queries\n\nTo run an EventQL query, call the `RunEventQlQueryAsync` method and provide the query as an argument. The method returns an async stream, which you can iterate over using `await foreach`:\n\n```csharp\nawait foreach (var row in client.RunEventQlQueryAsync(\n    \"FROM e IN events PROJECT INTO e\"))\n{\n    // ...\n}\n```\n\nEach row is returned as a `JsonElement`.\n\n*Optionally, you might provide a `CancellationToken`.*\n\n### Typed Results\n\nIf you want results to be deserialized automatically, use the generic overload `RunEventQlQueryAsync\u003cTRow\u003e`. Each row is deserialized into `TRow` according to your projection.\n\n*When using the non-generic overload, each row is a `JsonElement`. With the generic overload, each row is a `TRow` instance. Ensure your projection matches the shape of `TRow`.*\n\n## Observing Events\n\nTo observe all future events of a subject, call the `ObserveEventsAsync` method and pass the subject and an options object. Set `Recursive` to `false` to observe only the events of the given subject.\n\nThe method returns an async stream:\n\n```csharp\nawait foreach (var @event in client.ObserveEventsAsync(\n    \"/books/42\",\n    new ObserveEventsOptions(Recursive: false)))\n{\n    // Handle event\n}\n```\n\nIf an error occurs, the stream will terminate with an exception.\n\n*Optionally, you might provide a `CancellationToken`.*\n\n#### Deserializing Event Data\n\nEach event contains a `Data` property, which holds the event payload as JSON. To deserialize this payload into a strongly typed object, call `GetData\u003cT\u003e()`:\n\n```csharp\nvar book = @event.GetData\u003cBookAcquired\u003e();\n```\n\nAlternatively, you can use the non-generic overload `GetData(Type)` to resolve the type at runtime:\n\n```csharp\nvar type = typeof(BookAcquired);\nvar book = (BookAcquired)@event.GetData(type)!;\n```\n\nIf you prefer to work directly with the JSON structure, access the `Data` property as a `JsonElement`:\n\n```csharp\nvar title = @event.Data.GetProperty(\"title\").GetString();\n```\n\n### Observing from subjects recursively\n\nIf you want to observe not only the events of a subject, but also events of all nested subjects, set `Recursive` to `true`:\n\n```csharp\nawait foreach (var @event in client.ObserveEventsAsync(\n    \"/books/42\",\n    new ObserveEventsOptions(Recursive: true)))\n{\n    // ...\n}\n```\n\nThis also allows you to observe *all* events ever written by using `/` as the subject.\n\n### Specifying bounds\n\nIf you want to start observing from a certain point, set the `LowerBound` option:\n\n```csharp\nawait foreach (var @event in client.ObserveEventsAsync(\n    \"/books/42\",\n    new ObserveEventsOptions(\n        Recursive: false,\n        LowerBound: new Bound(\"100\", BoundType.Inclusive))))\n{\n    // ...\n}\n```\n\n### Starting from the latest event of a given type\n\nTo observe starting from the latest event of a specific type, use the `FromLatestEvent` option:\n\n```csharp\nawait foreach (var @event in client.ObserveEventsAsync(\n    \"/books/42\",\n    new ObserveEventsOptions(\n        Recursive: false,\n        FromLatestEvent: new ObserveFromLatestEvent(\n            Subject: \"/books/42\",\n            Type: \"io.eventsourcingdb.library.book-borrowed\",\n            IfEventIsMissing: ObserveIfEventIsMissing.ReadEverything))))\n{\n    // ...\n}\n```\n\n*Note that `FromLatestEvent` and `LowerBound` cannot be used at the same time.*\n\n## Listing Event Types\n\nTo list all event types, call the `ReadEventTypesAsync` method. The method returns an async stream:\n\n```csharp\nawait foreach (var eventType in client.ReadEventTypesAsync())\n{\n    // ...\n}\n```\n\n## Listing a Specific Event Type\n\nTo list a specific event type, call the `ReadEventTypeAsync` method with the event type as an argument. The method returns the detailed event type, which includes the schema:\n\n```csharp\nvar eventType = await client.ReadEventTypeAsync(\"io.eventsourcingdb.library.book-acquired\");\n```\n\n## Using Testcontainers\n\nImport the `Container` class, create an instance, call the `StartAsync` method to run a test container, get a client, run your test code, and finally call the `StopAsync` method to stop the test container:\n\n```csharp\nusing EventSourcingDb;\n\nvar container = new Container();\nawait container.StartAsync();\n\nvar client = container.GetClient();\n\n// ...\n\nawait container.StopAsync();\n```\n\nOptionally, you might provide a `CancellationToken` to the `StartAsync` and `StopAsync` methods.\n\nTo check if the test container is running, call the `IsRunning` method:\n\n```csharp\nvar isRunning = container.IsRunning();\n```\n\n#### Configuring the Container Instance\n\nBy default, `Container` uses the `latest` tag of the official EventSourcingDB Docker image. To change that, call the `WithImageTag` method:\n\n```csharp\nvar container = new Container()\n    .WithImageTag(\"1.0.0\");\n```\n\nSimilarly, you can configure the port to use and the API token. Call the `WithPort` or the `WithApiToken` method respectively:\n\n```csharp\nvar container = new Container()\n    .WithPort(4000)\n    .WithApiToken(\"secret\");\n```\n\n#### Configuring the Client Manually\n\nIn case you need to set up the client yourself, use the following methods to get details on the container:\n\n- `GetHost()` returns the host name\n- `GetMappedPort()` returns the port\n- `GetBaseUrl()` returns the full URL of the container\n- `GetApiToken()` returns the API token\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthenativeweb%2Feventsourcingdb-client-dotnet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthenativeweb%2Feventsourcingdb-client-dotnet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthenativeweb%2Feventsourcingdb-client-dotnet/lists"}