Basic set up, cant append to stream using .NET client

Hi there.

I am trying to do get up in running with EventStoreDb on my local machine.

I prefer to do just the minimal things at the moment. I don’t want to use Docker and I don’t want to have to deal with certificates as I am only using my local machine just doing prototyping/proof of concept work.

I am using windows and installed EventStoreDb server on my machine using Chocolatey.

Unfortunately I dont get any success when following the documentation.

  1. I installed the server (i tried with 23.1.0 and 24.6)
  2. I followed the instructions on this page Getting started | EventStoreDB Documentation
  3. I followed this instruction: “When connecting to an insecure instance, specify tls=false parameter. For example, for a node running locally use esdb://localhost:2113?tls=false. Note that usernames and passwords aren’t provided there because insecure deployments don’t support authentication and authorisation. Creating a client”

However when I try to append to stream I randomly get one of two errors:

Either a RpcException: Status(StatusCode=“DeadlineExceeded”…

Status(StatusCode=“Unavailable”, Detail="Error starting gRPC call. HttpRequestException: An HTTP/2 connection could not be established because the server did not complete the HTTP/2 handshake. (InvalidResponse) HttpIOException: An HTTP/2 connection could not be established because the server did not complete the HTTP/2 handshake. (InvalidResponse) HttpIOException: The response ended prematurely.

Is someone able to assist me in using EventSotreDb without needing to use docker and even certificates as I am only using my local machine. Is this even possible?

Ok I found the answer here:

Can start with command line --insecure e.g EventStore.ClusterNode.exe --dev --insecure

This is what I did.

Setting the YAML file or environment variable were other options.

You need to use either --dev or --insecure. Dev mode uses a local certificate, which is trusted. You won’t need tls=false in the connection string in dev mode. If you run an insecure server, then you’d need tls=false in the connection string.

1 Like

I am not sure if this is related to the same issue but I am getting

    Test method PortfolioValue.EventStore.Tests.EventStoreRepositoryTests.EventStoreRepository_SaveAndLoadEvents_ShouldPersistAndRetrieveEvents threw exception: 
Grpc.Core.RpcException: Status(StatusCode="DeadlineExceeded", Detail=""

I am running docker for event store


  #https://developers.eventstore.com/server/v23.10/quick-start/installation.html#use-docker-compose
  eventstore.db:
    image: eventstore/eventstore:23.10.0-bookworm-slim
    container_name: eventstore.db 
    environment:
      - EVENTSTORE_CLUSTER_SIZE=1
      - EVENTSTORE_RUN_PROJECTIONS=All
      - EVENTSTORE_START_STANDARD_PROJECTIONS=true
      - EVENTSTORE_EXT_TCP_PORT=1113
      - EVENTSTORE_HTTP_PORT=2113
      - EVENTSTORE_INSECURE=true
      - EVENTSTORE_ENABLE_EXTERNAL_TCP=true
      - EVENTSTORE_ENABLE_ATOM_PUB_OVER_HTTP=true
    ports:
      - "1113:1113"
      - "2113:2113"
    volumes:
      - type: volume
        source: eventstore-volume-data
        target: /var/lib/eventstore
      - type: volume
        source: eventstore-volume-logs
        target: /var/log/eventstore

My wrapper repository is

public class EventStoreRepository : IEventStore
    {
        private readonly EventStoreClient _client;
        private const string EventClrTypeHeader = "EventClrTypeName";

        public EventStoreRepository(string connectionString)
        {
            var settings = EventStoreClientSettings.Create(connectionString);
            settings.ConnectivitySettings = new EventStoreClientConnectivitySettings
            {
                KeepAliveInterval = TimeSpan.FromSeconds(5),
                KeepAliveTimeout = TimeSpan.FromSeconds(6000),
                Insecure = true, // Disable TLS
                TlsVerifyCert = false
            };
            settings.ChannelCredentials = ChannelCredentials.Insecure;

            _client = new EventStoreClient(settings);
        }

        public IEnumerable<Event> LoadEventsFor<TAggregate>(Guid id)
        {
            var streamName = GetStreamName<TAggregate>(id);

            var result = _client.ReadStreamAsync(Direction.Forwards, streamName, StreamPosition.Start);

            var resolvedEvents = new List<ResolvedEvent>();

            try
            {
                // Synchronously wait for the asynchronous read operation to complete
                resolvedEvents = result.ToListAsync().GetAwaiter().GetResult();
            }
            catch (Exception ex) when (IsStreamNotFoundException(ex))
            {
                // Stream does not exist
                return Enumerable.Empty<Event>();
            }

            if (!resolvedEvents.Any())
            {
                // Stream exists but has no events
                return Enumerable.Empty<Event>();
            }

            var events = resolvedEvents.Select(re => DeserializeEvent(re.Event));
            return events;
        }

        public void SaveEventsFor<TAggregate>(Guid id, int eventsLoaded, IEnumerable<Event> newEvents)
        {
            var streamName = GetStreamName<TAggregate>(id);

            var eventData = newEvents.Select(e => ToEventData(e));

            if (eventsLoaded == 0)
            {
                // Synchronously wait for the asynchronous append operation to complete
                _client.AppendToStreamAsync(streamName, StreamState.NoStream, eventData)
                    .GetAwaiter().GetResult();
            }
            else
            {
                var expectedRevision = StreamRevision.FromInt64(eventsLoaded - 1);
                _client.AppendToStreamAsync(streamName, expectedRevision, eventData)
                    .GetAwaiter().GetResult();
            }
        }

        public IEnumerable<Event> GetAllEvents()
        {
            var result = _client.ReadAllAsync(Direction.Forwards, Position.Start);

            // Synchronously wait for the asynchronous read operation to complete
            var resolvedEvents = result.ToListAsync().GetAwaiter().GetResult();

            var events = resolvedEvents.Select(re => DeserializeEvent(re.Event));
            return events;
        }

        private string GetStreamName<TAggregate>(Guid id)
        {
            return $"{typeof(TAggregate).Name}-{id}";
        }

        private Event DeserializeEvent(EventRecord eventRecord)
        {
            var metadataJson = Encoding.UTF8.GetString(eventRecord.Metadata.ToArray());
            var metadata = JsonSerializer.Deserialize<Dictionary<string, string>>(metadataJson);
            var eventClrTypeName = metadata[EventClrTypeHeader];

            var eventType = Type.GetType(eventClrTypeName);
            if (eventType == null)
                throw new InvalidOperationException($"Unable to find type {eventClrTypeName}");

            var dataJson = Encoding.UTF8.GetString(eventRecord.Data.ToArray());
            var ev = JsonSerializer.Deserialize(dataJson, eventType) as Event;

            return ev;
        }

        private EventData ToEventData(Event evnt)
        {
            var data = JsonSerializer.SerializeToUtf8Bytes(evnt);

            var metadata = new Dictionary<string, string>
            {
                { EventClrTypeHeader, evnt.GetType().AssemblyQualifiedName }
            };
            var metadataBytes = JsonSerializer.SerializeToUtf8Bytes(metadata);

            var eventData = new EventData(
                Uuid.NewUuid(),
                evnt.GetType().Name,
                data,
                metadataBytes
            );

            return eventData;
        }

        private bool IsStreamNotFoundException(Exception ex)
        {
            // Adjust this method based on how exceptions are thrown in your version of the client
            return ex is StreamNotFoundException ||
                   ex.InnerException is StreamNotFoundException;
        }
    }

and finally the test class I try to run that is causing the error

 [TestClass]
    public class EventStoreRepositoryTests
    {
     
        private string ConnectionString = $"esdb://admin:changeit@localhost:2113?tls=false&tlsVerifyCert=false";
        private IEventStore eventStore;

        [TestInitialize]
        public void TestInit()
        {
            this.eventStore = new EventStoreRepository(ConnectionString);
        }

        [TestMethod]
        public void EventStoreRepository_SaveAndLoadEvents_ShouldPersistAndRetrieveEvents()
        {
            // Arrange
            var aggregateId = Guid.NewGuid();
            var eventsLoaded = 0; // No events loaded initially
            var newEvents = new List<Event>
            {
                new DealFxRateCapturedEvent(
                    aggregateId,
                    DateTime.Today,
                    new Rate(1 * Identifier.Currency("EUR"), 1.23M * Identifier.Currency("USD")))
            };

            // Act
            // Save events to the event store
            this.eventStore.SaveEventsFor<DealAggregate>(aggregateId, eventsLoaded, newEvents);

            // Load events from the event store
            var loadedEvents = this.eventStore.LoadEventsFor<DealAggregate>(aggregateId).ToList();

            // Assert
            Assert.AreEqual(newEvents.Count, loadedEvents.Count, "The number of loaded events should match the number of saved events.");

            // Compare event data
            for (int i = 0; i < newEvents.Count; i++)
            {
                var savedEvent = newEvents[i];
                var loadedEvent = loadedEvents[i];

                Assert.AreEqual(savedEvent.GetType(), loadedEvent.GetType(), $"Event types should match at index {i}.");

                // Cast to specific event type
                var savedDealEvent = savedEvent as DealFxRateCapturedEvent;
                var loadedDealEvent = loadedEvent as DealFxRateCapturedEvent;

                Assert.IsNotNull(savedDealEvent);
                Assert.IsNotNull(loadedDealEvent);

                // Compare properties
                Assert.AreEqual(savedDealEvent.Id, loadedDealEvent.Id, "Event IDs should match.");
                //Assert.AreEqual(savedDealEvent.DateTime, loadedDealEvent.DateTime, "Capture dates should match.");
                Assert.AreEqual(savedDealEvent.FxRate, loadedDealEvent.FxRate, "Rates should match.");
            }
        }
    }

The documentation seems a bit fragmented and although I’ve looked at the .net samples, I couldn’t get this work…

Thanks for any tips, pointers, or suggestions…

It looks like you are overriding some of the default connectivity settings created by EventStoreClientSettings.Create(connectionString). If you want to change the default values for KeepAliveInterval and KeepAliveTimeout, you can do it as follows:

var settings = EventStoreClientSettings.Create(connectionString);
settings.ConnectivitySettings.KeepAliveInterval = TimeSpan.FromSeconds(5);
settings.ConnectivitySettings.KeepAliveTimeout = TimeSpan.FromSeconds(6000);

_client = new EventStoreClient(settings);

or in your test, you could add those parameters in the connection string:

esdb://admin:changeit@localhost:2113?tls=false&tlsVerifyCert=false&keepAliveInterval=5000&keepAliveTimeout=6000000

Thanks, I am closing this, as I think I had multiple issues… the timeout was caused (I suspect) by my note putting any credentials in the conn string which I think should have been

"esdb://admin:changeit@localhost:2113?tls=false&tlsVerifyCert=false";

Other than that I’ve had variou other serialization related issues… But at least I now connect and this simple test passes.

        [ClassInitialize]

        // in event store docs, it is recommended to reuse client as a singleton across the whole application
        // https://developers.eventstore.com/clients/grpc/getting-started.html#connection-string
        public static void ClassInit(TestContext context)
        {
            var settings = EventStoreClientSettings.Create(ConnectionString);
            settings.ConnectionName = "ES-Test";
            eventStoreClient = new EventStoreClient(settings);
        }

        [TestInitialize]
        public void TestInit()
        {
            if (string.IsNullOrEmpty(ConnectionString))
            {
                Assert.Fail("EventStoreConnectionString is not configured in the App.config or Web.config file.");
            }

        }


        [TestMethod]
        public void EventStoreConnectionTest()
        {
            // Assert that the connection was successfully established.
            Assert.IsNotNull(eventStoreClient);
            Assert.IsTrue(eventStoreClient.ConnectionName.StartsWith("ES-Test"));
        }

Thanks, I am closing this, as I think I had multiple issues… the timeout was caused (I suspect) by my note putting any credentials in the conn string which I think should have been

"esdb://admin:changeit@localhost:2113?tls=false&tlsVerifyCert=false";

Other than that I’ve had variou other serialization related issues… But at least I now connect to the store