public TInternalId this[TNaturalKey naturalKey]
        {
            get
            {
                using (var connection = new SqlConnection(_sqlServerPersistenceConfiguration.ConnectionString))
                {
                    var sql = @"SELECT [AggregateId] FROM [NaturalKeyToAggregateIdMap-{aggName}] WHERE [NaturalKey] = @naturalKey"
                              .Replace("{aggName}", AggregateRootUtils.GetAggregateRootName <TAggregate>());

                    var command = new SqlCommand(sql, connection);
                    command.Parameters.Add("@naturalKey", SqlDbTypeUtils.GetSqlDbType <TNaturalKey>()).Value = naturalKey;

                    connection.Open();

                    var result = command.ExecuteScalar();

                    if (result == null)
                    {
                        throw new KeyNotFoundException($"No entry was found for natural key {naturalKey}");
                    }

                    return((TInternalId)result);
                }
            }
        }
        public IEnumerable <IEvent <TId> > LoadEvents(TId aggregateId, Type aggregateType, bool throwIfNotFound)
        {
            using (var connection = new SqlConnection(_sqlServerPersistenceConfiguration.ConnectionString))
            {
                connection.Open();

                var sql = @"  
SELECT [Payload] FROM [dbo].[EventStore-{aggName}] WHERE [AggregateId] = @aggregateId ORDER BY [Version] ASC"
                          .Replace("{aggName}", AggregateRootUtils.GetAggregateRootName(aggregateType));

                using (var command = new SqlCommand(sql, connection))
                {
                    command.Parameters.Add("@aggregateId", SqlDbTypeUtils.GetSqlDbType <TId>()).Value = aggregateId;

                    using (var reader = command.ExecuteReader())
                    {
                        if (!reader.HasRows)
                        {
                            if (throwIfNotFound)
                            {
                                throw new AggregateNotFoundException($"Aggregate '{aggregateId}' not found");
                            }

                            yield break;
                        }

                        while (reader.Read())
                        {
                            yield return((IEvent <TId>)Deserialize((string)reader[0]));
                        }
                    }
                }
            }
        }
        public void Delete(TNaturalKey naturalKey)
        {
            using (var connection = new SqlConnection(_sqlServerPersistenceConfiguration.ConnectionString))
            {
                var sql = @"DELETE FROM [NaturalKeyToAggregateIdMap-{aggName}] WHERE [NaturalKey] = @naturalKey"
                          .Replace("{aggName}", AggregateRootUtils.GetAggregateRootName <TAggregate>());

                var command = new SqlCommand(sql, connection);
                command.Parameters.Add("@naturalKey", SqlDbTypeUtils.GetSqlDbType <TNaturalKey>()).Value = naturalKey;

                connection.Open();

                command.ExecuteNonQuery();
            }
        }
        public TInternalId GetOrCreateNew(TNaturalKey naturalKey)
        {
            using (var connection = new SqlConnection(_sqlServerPersistenceConfiguration.ConnectionString))
            {
                connection.Open();
                using (var transaction = connection.BeginTransaction(IsolationLevel.Serializable))
                {
                    var sql = @"SELECT [AggregateId] FROM [dbo].[NaturalKeyToAggregateIdMap-{aggName}] WHERE [NaturalKey] = @naturalKey"
                              .Replace("{aggName}", AggregateRootUtils.GetAggregateRootName <TAggregate>());

                    var command = new SqlCommand(sql, connection, transaction);
                    command.Parameters.Add("@naturalKey", SqlDbTypeUtils.GetSqlDbType <TNaturalKey>()).Value = naturalKey;

                    object result = command.ExecuteScalar();

                    TInternalId id;

                    if (result != null)
                    {
                        id = (TInternalId)result;
                    }
                    else
                    {
                        sql = @"
INSERT INTO [dbo].[NaturalKeyToAggregateIdMap-{aggName}] (NaturalKey, AggregateId)
VALUES (@naturalKey, @aggregateID)"
                              .Replace("{aggName}", AggregateRootUtils.GetAggregateRootName <TAggregate>());

                        id = _aggregateIdCreator.Create();

                        command = new SqlCommand(sql, connection, transaction);
                        command.Parameters.Add("@naturalKey", SqlDbTypeUtils.GetSqlDbType <TNaturalKey>()).Value  = naturalKey;
                        command.Parameters.Add("@aggregateID", SqlDbTypeUtils.GetSqlDbType <TInternalId>()).Value = id;

                        command.ExecuteNonQuery();
                    }

                    transaction.Commit();

                    return(id);
                }
            }
        }
        public void SaveEvents(TId id, Type aggregateType, IEnumerable <IEvent <TId> > events, int currentVersion, int expectedVersion)
        {
            var dataTable = new DataTable();

            dataTable.Columns.Add("AggregateId", typeof(TId));
            dataTable.Columns.Add("Version", typeof(long));
            dataTable.Columns.Add("EventDateTime", typeof(DateTime));
            dataTable.Columns.Add("Payload", typeof(string));

            foreach (var @event in events)
            {
                dataTable.Rows.Add(@event.Id, @event.Version, DateTime.Now, Serialize(@event));
            }

            using (var connection = new SqlConnection(_sqlServerPersistenceConfiguration.ConnectionString))
            {
                connection.Open();

                using (var command = new SqlCommand())
                {
                    command.Connection  = connection;
                    command.CommandType = CommandType.StoredProcedure;
                    command.CommandText = "dbo.usp_Insert{aggName}Events".Replace("{aggName}", AggregateRootUtils.GetAggregateRootName(aggregateType));
                    command.Parameters.Add("@aggregateId", SqlDbTypeUtils.GetSqlDbType <TId>()).Value = id;
                    command.Parameters.Add("@expectedVersion", SqlDbType.BigInt).Value = expectedVersion;

                    var tableParameter = new SqlParameter();
                    tableParameter.ParameterName = "@eventsToInsert";
                    tableParameter.TypeName      = "EventStoreType";
                    tableParameter.SqlDbType     = SqlDbType.Structured;
                    tableParameter.Value         = dataTable;
                    command.Parameters.Add(tableParameter);

                    try
                    {
                        command.ExecuteNonQuery();
                    }
                    catch (SqlException ex) when(ex.Number == 51000)
                    {
                        throw new EventStoreConcurrencyException(ex.Message);
                    }
                }
            }
        }
        public void DeleteEvents(TId id, Type aggregateType)
        {
            using (var connection = new SqlConnection(_sqlServerPersistenceConfiguration.ConnectionString))
            {
                connection.Open();

                var getLatestVersionSql = @"DELETE FROM [dbo].[EventStore-{aggName}] WHERE [AggregateId] = @aggregateId".Replace("{aggName}", AggregateRootUtils.GetAggregateRootName(aggregateType));

                using (var command = new SqlCommand(getLatestVersionSql, connection))
                {
                    command.Parameters.Add("@aggregateId", SqlDbTypeUtils.GetSqlDbType <TId>()).Value = id;
                    command.ExecuteNonQuery();
                }
            }
        }