diff --git a/CMakeLists.txt b/CMakeLists.txt
index 488878f45d5b6206dfe720256b7a1fef08df0010..f9c6718af235dad3d558d571c0e39dc76984ee69 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -299,7 +299,7 @@ if(_LINTING)
     else()
         message(STATUS "clang-tidy: ${clang_tidy}")
         set(_CMAKE_CXX_CLANG_TIDY_CHECKS
-            "--checks=*,-fuchsia-*,-llvmlibc-*,-readability-convert-member-functions-to-static,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-hicpp-no-array-decay,-llvm-else-after-return,-readability-else-after-return,-modernize-use-trailing-return-type")
+            "--checks=*,-fuchsia-*,-llvmlibc-*,-readability-convert-member-functions-to-static,-cppcoreguidelines-pro-bounds-array-to-pointer-decay,-hicpp-no-array-decay,-llvm-else-after-return,-readability-else-after-return,-modernize-use-trailing-return-type,-bugprone-branch-clone")
         set(_CMAKE_C_CLANG_TIDY_CHECKS "${_CMAKE_CXX_CLANG_TIDY_CHECKS}")
         set(_CMAKE_CXX_CLANG_TIDY "${clang_tidy}"
             "--header-filter=caosdb/.*[^\(\.pb\.h\)]$"
diff --git a/include/caosdb/entity.h b/include/caosdb/entity.h
index d925f62abb5d732a3955b433f69c30b1c6a13a11..eb93b36875bfe7778345152bcbb53777d9509759 100644
--- a/include/caosdb/entity.h
+++ b/include/caosdb/entity.h
@@ -466,12 +466,10 @@ public:
     return this->file_transmission_id != nullptr;
   }
 
-protected:
+private:
   static auto CreateProtoEntity() -> ProtoEntity *;
   auto SetId(const std::string &id) -> void;
   auto SetVersionId(const std::string &id) -> void;
-
-private:
   FileTransmissionId *file_transmission_id = nullptr;
   ProtoEntity *wrapped;
   Properties properties;
diff --git a/include/caosdb/exceptions.h b/include/caosdb/exceptions.h
index d60f7a162b6a36d803a37c1e40dfc43d21a3f172..9ac46d2fd2e0a24fab08c49606b21033893c14e7 100644
--- a/include/caosdb/exceptions.h
+++ b/include/caosdb/exceptions.h
@@ -73,10 +73,22 @@ public:
     : Exception(StatusCode::GENERIC_TRANSACTION_ERROR, what_arg) {}
 };
 
-class TransactionStatusError : public TransactionError {
+/**
+ * @brief The transaction is in the wrong state for your action.
+ */
+class TransactionStatusError : public Exception {
 public:
   explicit TransactionStatusError(const std::string &what_arg)
-    : TransactionError(StatusCode::TRANSACTION_STATUS_ERROR, what_arg) {}
+    : Exception(StatusCode::TRANSACTION_STATUS_ERROR, what_arg) {}
+};
+
+/**
+ * @brief The transaction has a wrong type for your action.
+ */
+class TransactionTypeError : public Exception {
+public:
+  explicit TransactionTypeError(const std::string &what_arg)
+    : Exception(StatusCode::TRANSACTION_TYPE_ERROR, what_arg) {}
 };
 
 /**
diff --git a/include/caosdb/status_code.h b/include/caosdb/status_code.h
index 6e5f7587ea0e114e4eab61c4bd3960f80cdb514d..a6fcda905284ae9dfa9b7e46c6be8da36bb32272 100644
--- a/include/caosdb/status_code.h
+++ b/include/caosdb/status_code.h
@@ -24,6 +24,8 @@
 
 #include <string>
 
+namespace caosdb {
+
 /**
  * StatusCodes represent the status of this client, it's connections,
  * configuration and so on.
@@ -33,9 +35,6 @@
  * GENERIC_TRANSACTION_ERROR indicates that *there are* errors in a
  * transaction).
  */
-
-namespace caosdb {
-
 enum StatusCode {
   READY = -4,
   GO_ON = -3,
diff --git a/include/caosdb/transaction.h b/include/caosdb/transaction.h
index a1295904cc68488568a0f6753bdfded5d9ae5db5..90044e1d8bf8cf99604184bff83fffb9c9b79b27 100644
--- a/include/caosdb/transaction.h
+++ b/include/caosdb/transaction.h
@@ -18,12 +18,8 @@
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
  *
  */
-
 #ifndef CAOSDB_TRANSACTION_H
 #define CAOSDB_TRANSACTION_H
-/**
- * @brief Creation and execution of transactions.
- */
 #include "boost/log/core/record.hpp"                  // for record
 #include "boost/log/sources/record_ostream.hpp"       // for basic_record_o...
 #include "boost/preprocessor/seq/limits/enum_256.hpp" // for BOOST_PP_SEQ_E...
@@ -35,25 +31,28 @@
 #include "caosdb/transaction_status.h"           // for TransactionStatus
 #include "caosdb/status_code.h"                  // for StatusCode
 #include "google/protobuf/util/json_util.h" // for MessageToJsonString, Jso...
-#include <stdexcept>
 #include <iterator>
 // IWYU pragma: no_include <ext/alloc_traits.h>
 #include <memory> // for shared_ptr, unique_ptr
+#include <stdexcept>
 #include <string> // for string
 #include <vector> // for vector
 
-/*
+/**
  * Do all necessary checks and assure that another retrieval (by id or by
  * query) can be added as a sub-request to a transaction.
  */
 #define ASSERT_CAN_ADD_RETRIEVAL                                               \
-  if (!IsStatus(TransactionStatus::INITIAL())) {                               \
+  if (!IsStatus(TransactionStatus::INITIAL()) &&                               \
+      !IsStatus(TransactionStatus::GO_ON())) {                                 \
     return StatusCode::TRANSACTION_STATUS_ERROR;                               \
   }                                                                            \
   switch (this->transaction_type) {                                            \
   case NONE:                                                                   \
     this->transaction_type = TransactionType::READ_ONLY;                       \
+    break;                                                                     \
   case READ_ONLY:                                                              \
+    break;                                                                     \
   case MIXED_READ_AND_WRITE:                                                   \
     break;                                                                     \
   default:                                                                     \
@@ -63,12 +62,26 @@
       "wrong TransactionType.")                                                \
   }
 
-/*
+/**
+ * Do all necessary checks and assure that another retrieval (by id or by
+ * query) can be added as a sub-request to a transaction.
+ */
+#define ASSERT_CAN_ADD_QUERY                                                   \
+  ASSERT_CAN_ADD_RETRIEVAL                                                     \
+  if (this->has_query) {                                                       \
+    CAOSDB_LOG_ERROR_AND_RETURN_STATUS(                                        \
+      logger_name, StatusCode::UNSUPPORTED_FEATURE,                            \
+      "Currently the number of queries which can be processed in a single "    \
+      "transaction is limitted to one.");                                      \
+  }
+
+/**
  * Do all necessary checks and assure that another deletion can be added as a
  * sub-request to a transaction.
  */
 #define ASSERT_CAN_ADD_DELETION                                                \
-  if (!IsStatus(TransactionStatus::INITIAL())) {                               \
+  if (!IsStatus(TransactionStatus::INITIAL()) &&                               \
+      !IsStatus(TransactionStatus::GO_ON())) {                                 \
     return StatusCode::TRANSACTION_STATUS_ERROR;                               \
   }                                                                            \
   switch (this->transaction_type) {                                            \
@@ -85,12 +98,13 @@
       "wrong TransactionType.")                                                \
   }
 
-/*
+/**
  * Do all necessary checks and assure that another insertion can be added as a
  * sub-request to a transaction.
  */
 #define ASSERT_CAN_ADD_INSERTION                                               \
-  if (!IsStatus(TransactionStatus::INITIAL())) {                               \
+  if (!IsStatus(TransactionStatus::INITIAL()) &&                               \
+      !IsStatus(TransactionStatus::GO_ON())) {                                 \
     return StatusCode::TRANSACTION_STATUS_ERROR;                               \
   }                                                                            \
   switch (this->transaction_type) {                                            \
@@ -107,12 +121,13 @@
       "wrong TransactionType.")                                                \
   }
 
-/*
+/**
  * Do all necessary checks and assure that another update can be added as a
  * sub-request to a transaction.
  */
 #define ASSERT_CAN_ADD_UPDATE                                                  \
-  if (!IsStatus(TransactionStatus::INITIAL())) {                               \
+  if (!IsStatus(TransactionStatus::INITIAL()) &&                               \
+      !IsStatus(TransactionStatus::GO_ON())) {                                 \
     return StatusCode::TRANSACTION_STATUS_ERROR;                               \
   }                                                                            \
   switch (this->transaction_type) {                                            \
@@ -136,6 +151,11 @@
       "retrieval of the existing entity which may then be changed.")           \
   }
 
+/**
+ * @brief Creation and execution of transactions.
+ * @author Timm Fitschen
+ * @date 2021-08-05
+ */
 namespace caosdb::transaction {
 using caosdb::entity::Entity;
 using ProtoEntity = caosdb::entity::v1alpha1::Entity;
@@ -154,43 +174,48 @@ using caosdb::transaction::TransactionStatus;
 using WrappedResponseCase =
   caosdb::entity::v1alpha1::TransactionResponse::WrappedResponseCase;
 
+class Transaction;
+
 static const std::string logger_name = "caosdb::transaction";
 
+/**
+ * Abstract base class for the results of a Transaction.
+ */
 class ResultSet {
+  class iterator;
+
 public:
   virtual ~ResultSet() = default;
   [[nodiscard]] virtual auto Size() const noexcept -> int = 0;
   [[nodiscard]] virtual auto At(const int index) const -> const Entity & = 0;
+  auto begin() const -> iterator;
+  auto end() const -> iterator;
+
+private:
+  class iterator : public std::iterator<std::output_iterator_tag, Entity> {
+  public:
+    explicit iterator(const ResultSet *result_set, int index = 0);
+    auto operator*() const -> const Entity &;
+    auto operator++() -> iterator &;
+    auto operator++(int) -> iterator;
+    auto operator!=(const iterator &rhs) const -> bool;
+
+  private:
+    int current_index = 0;
+    const ResultSet *result_set;
+  };
 };
 
+/**
+ * Container with results of a transaction.
+ *
+ * In contrast to UniqueResult, this one can also hold multiple entities or zero
+ * entities.
+ */
 class MultiResultSet : public ResultSet {
 public:
   ~MultiResultSet() = default;
-  explicit inline MultiResultSet(MultiTransactionResponse *response) {
-    auto responses = response->mutable_responses();
-    Entity *entity = nullptr;
-    for (auto sub_response : *responses) {
-      switch (sub_response.wrapped_response_case()) {
-      case WrappedResponseCase::kRetrieveResponse:
-        entity = new Entity(
-          sub_response.mutable_retrieve_response()->release_entity());
-        break;
-      case WrappedResponseCase::kInsertResponse:
-
-        entity = new Entity(sub_response.release_insert_response());
-        break;
-      case WrappedResponseCase::kDeleteResponse:
-        entity = new Entity(sub_response.release_delete_response());
-        break;
-      default:
-        // TODO(tf) Updates
-        break;
-      }
-      if (entity) {
-        this->entities.push_back(std::unique_ptr<Entity>(entity));
-      }
-    }
-  }
+  explicit MultiResultSet(MultiTransactionResponse *response);
   [[nodiscard]] inline auto Size() const noexcept -> int override {
     return this->entities.size();
   }
@@ -201,6 +226,12 @@ public:
   std::vector<std::unique_ptr<Entity>> entities;
 };
 
+/**
+ * Container with the single result of a transaction.
+ *
+ * In contrast to MultiResultSet, this one guarantees to hold exactly one
+ * entity.
+ */
 class UniqueResult : public ResultSet {
 public:
   ~UniqueResult() = default;
@@ -222,6 +253,17 @@ private:
   std::unique_ptr<Entity> entity;
 };
 
+// TODO(fspreck) One possibility for count-queries: Transaction gets a
+// GetQueryCount function that returns exactly this. In case of a
+// single FIND Query this will be equivalent to the Size of the
+// ResultSet, but it may be different in case of Query + RetrieveById,
+// and in case of COUNT queries which have an empty ResultSet. We have
+// to think about how to extend this to transactions with more than
+// one query in the future. My suggestion is to treat a transaction
+// with more than one query as the union of the query results in which
+// case the query count would return the cumulative number of
+// results. This would even allow the combination of FIND, FIND
+// unique, and COUNT, albeit a little strangely.
 /**
  * @brief Create a transaction via `CaosDBConnection.createTransaction()`
  */
@@ -235,14 +277,21 @@ public:
     -> void;
   auto RegisterDownloadFile(const RegisterFileDownloadRequest &request,
                             RegisterFileDownloadResponse *response) -> void;
+  /**
+   * The transaction type restricts the kind of sub-transaction which may be
+   * added to a transaction (insertion, update, deletion, retrieval).
+   *
+   * @note MIXED_READ_AND_WRITE and MIXED_WRITE transaction are not supported
+   * yet.
+   */
   enum TransactionType {
-    NONE,
-    READ_ONLY,
-    INSERT,
-    UPDATE,
-    DELETE,
-    MIXED_WRITE,
-    MIXED_READ_AND_WRITE
+    NONE,                //!< Unspecified or not specified yet.
+    READ_ONLY,           //!< Only retrievals (by id, by query)
+    INSERT,              //!< Only insertions
+    UPDATE,              //!< Only updates
+    DELETE,              //!< Only deletions
+    MIXED_WRITE,         //!< Only insertions, deletions, updates
+    MIXED_READ_AND_WRITE //!< all kind of transaction.
   };
   Transaction(std::shared_ptr<EntityTransactionService::Stub> entity_service,
               std::shared_ptr<FileTransmissionService::Stub> file_service);
@@ -353,6 +402,7 @@ public:
   }
 
 private:
+  bool has_query = false;
   TransactionType transaction_type = TransactionType::NONE;
   mutable std::unique_ptr<ResultSet> result_set;
   mutable TransactionStatus status = TransactionStatus::INITIAL();
@@ -376,7 +426,8 @@ inline auto Transaction::RetrieveById(InputIterator begin,
     next = std::next(next);
   }
 
-  return StatusCode::INITIAL;
+  this->status = TransactionStatus::GO_ON();
+  return this->status.GetCode();
 }
 
 } // namespace caosdb::transaction
diff --git a/include/caosdb/transaction_status.h b/include/caosdb/transaction_status.h
index 8e54c8ba79c944f6b157a577cc9db64be01e1473..6f2f5cefd7b36f8c986726b4955f53381339f0a2 100644
--- a/include/caosdb/transaction_status.h
+++ b/include/caosdb/transaction_status.h
@@ -22,15 +22,6 @@
 #ifndef CAOSDB_TRANSACTION_STATUS_H
 #define CAOSDB_TRANSACTION_STATUS_H
 
-/**
- * TransactionStatus indicates the current status of a transaction and, when it
- * has already terminated, whether the transaction has been successful or not.
- *
- * A status code of 0 denotes a generic success state, positive values indicate
- * errors, and negative values indicate other states, such as different stages
- * of a transaction in process.
- */
-
 #include "caosdb/status_code.h"
 #include "caosdb/exceptions.h"
 #include <memory> // for shared_ptr, unique_ptr
@@ -42,47 +33,85 @@ using caosdb::exceptions::AuthenticationError;
 using caosdb::exceptions::ConnectionError;
 using caosdb::exceptions::Exception;
 using caosdb::exceptions::TransactionError;
+using caosdb::exceptions::TransactionStatusError;
+using caosdb::exceptions::TransactionTypeError;
 
 /**
- * Status of a Request or Transaction.
+ * Define static factory method in the TransactionStatus class.
  */
-class TransactionStatus {
-public:
-  // REFACTORING NEEDED: When you touch this code again consider writing a
-  // macro, because this is a lot of redundant code here...
-  inline static auto INITIAL() -> const TransactionStatus & {
-    static const TransactionStatus initial(
-      StatusCode::INITIAL, caosdb::get_status_description(StatusCode::INITIAL));
-    return initial;
+#define CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(_StatusName, _StatusCode)    \
+  inline static auto _StatusName()->const TransactionStatus & {                \
+    static const TransactionStatus instance(                                   \
+      _StatusCode, caosdb::get_status_description(_StatusCode));               \
+    return instance;                                                           \
   }
-  inline static auto EXECUTING() -> const TransactionStatus & {
-    static const TransactionStatus executing(
-      StatusCode::EXECUTING,
-      caosdb::get_status_description(StatusCode::EXECUTING));
-    return executing;
-  }
-  inline static auto SUCCESS() -> const TransactionStatus & {
-    static const TransactionStatus success(
-      StatusCode::SUCCESS, caosdb::get_status_description(StatusCode::SUCCESS));
-    return success;
-  }
-  inline static auto RPC_ERROR(const std::string &details)
-    -> const TransactionStatus {
-    // We use the GENERIC_RPC_ERROR here because we might want to add further
-    // RPC_ERROR states with different error codes (which stem from GRPC) here
-    // in the future.
-    return TransactionStatus(
-      StatusCode::GENERIC_RPC_ERROR,
-      caosdb::get_status_description(StatusCode::GENERIC_RPC_ERROR) +
-        " Original error: " + details);
-  }
-  inline static auto CONNECTION_ERROR() -> const TransactionStatus & {
-    static const TransactionStatus connection_error(
-      StatusCode::CONNECTION_ERROR,
-      caosdb::get_status_description(StatusCode::CONNECTION_ERROR));
 
-    return connection_error;
-  }
+/**
+ * TransactionStatus indicates the current status of a transaction and, when it
+ * has already terminated, whether the transaction has been successful or not.
+ *
+ * A status code of 0 denotes a generic success state, positive values indicate
+ * errors, and negative values indicate other states, such as different stages
+ * of a transaction in process.
+ */
+class TransactionStatus {
+public:
+  /**
+   * Factory for an INITIAL status.
+   *
+   * This status means that the transaction has not been executed yet and the
+   * transaction does not contain any sub-transactions yet.
+   */
+  CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(INITIAL, StatusCode::INITIAL)
+  /**
+   * Factory for a GO_ON status.
+   *
+   * This status means that the transaction has not been executed yet but it
+   * already contains sub-transaction and more subtransactions may be added.
+   *
+   * However, it also can be executed right now.
+   */
+  CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(GO_ON, StatusCode::GO_ON)
+  /**
+   * Factory for a READY status.
+   *
+   * This status means that the transaction has not been executed yet but it is
+   * ready to be executed and it is not possible anymore to add further
+   * sub-transactions.
+   */
+  CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(READY, StatusCode::READY)
+  /**
+   * Factory for an EXECUTING status.
+   *
+   * This status means that the transaction is currently being executed.
+   */
+  CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(EXECUTING, StatusCode::EXECUTING)
+  /**
+   * Factory for a SUCCESS status.
+   *
+   * This status means that the transaction has been executed successfully.
+   */
+  CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(SUCCESS, StatusCode::SUCCESS)
+  /**
+   * Factory for a CONNECTION_ERROR status.
+   *
+   * This status means that the connection could not be established. This is
+   * possibly due to misconfiguration of the client, errors in the network or
+   * because the server is down.
+   */
+  CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(CONNECTION_ERROR,
+                                            StatusCode::CONNECTION_ERROR)
+  /**
+   * Factory for an AUTHENTICATION_ERROR status.
+   *
+   * This status means that the RPC layer reported an authentication error.
+   */
+  CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(AUTHENTICATION_ERROR,
+                                            StatusCode::AUTHENTICATION_ERROR)
+  /**
+   * Another factory for an TRANSACTION_ERROR Status with a detailed
+   * description.
+   */
   inline static auto AUTHENTICATION_ERROR(const std::string &details)
     -> const TransactionStatus {
     return TransactionStatus(
@@ -90,19 +119,18 @@ public:
       caosdb::get_status_description(StatusCode::AUTHENTICATION_ERROR) +
         " Original error: " + details);
   }
-  inline static auto AUTHENTICATION_ERROR() -> const TransactionStatus & {
-    static const TransactionStatus authentication_error(
-      StatusCode::AUTHENTICATION_ERROR,
-      caosdb::get_status_description(StatusCode::AUTHENTICATION_ERROR));
-
-    return authentication_error;
-  }
-  inline static auto TRANSACTION_ERROR() -> const TransactionStatus & {
-    static const TransactionStatus transaction_error(
-      StatusCode::GENERIC_TRANSACTION_ERROR,
-      caosdb::get_status_description(StatusCode::GENERIC_TRANSACTION_ERROR));
-    return transaction_error;
-  }
+  /**
+   * Factory for a TRANSACTION_ERROR status.
+   *
+   * This status means that the transaction failed due to errors thrown by the
+   * server.
+   */
+  CAOSDB_TRANSACTION_STATUS_DEFAULT_FACTORY(
+    TRANSACTION_ERROR, StatusCode::GENERIC_TRANSACTION_ERROR)
+  /**
+   * Another factory for a TRANSACTION_ERROR status with a detailed
+   * description.
+   */
   inline static auto TRANSACTION_ERROR(const std::string &details)
     -> const TransactionStatus {
     return TransactionStatus(
@@ -110,23 +138,53 @@ public:
       caosdb::get_status_description(StatusCode::GENERIC_TRANSACTION_ERROR) +
         " Original error: " + details);
   }
+  /**
+   * Factory for a RPC_ERROR with a detailed description.
+   *
+   * This status is used for any error on the RPC layer.
+   */
+  inline static auto RPC_ERROR(const std::string &details)
+    -> const TransactionStatus {
+    // We use the GENERIC_RPC_ERROR here because we might want to add further
+    // RPC_ERROR states with different error codes (which stem from GRPC) here
+    // in the future.
+    return TransactionStatus(
+      StatusCode::GENERIC_RPC_ERROR,
+      caosdb::get_status_description(StatusCode::GENERIC_RPC_ERROR) +
+        " Original error: " + details);
+  }
 
   inline auto ThrowExceptionIfError() const -> void {
-    if (!IsError()) {
+    TransactionStatus::ThrowExceptionIfError(this->code, this->description);
+  }
+
+  inline static auto ThrowExceptionIfError(StatusCode code,
+                                           const std::string &description)
+    -> void {
+    if (!IsError(code)) {
       return;
     }
-    switch (this->code) {
+    switch (code) {
     case StatusCode::CONNECTION_ERROR:
-      throw ConnectionError(this->description);
+      throw ConnectionError(description);
     case StatusCode::AUTHENTICATION_ERROR:
-      throw AuthenticationError(this->description);
+      throw AuthenticationError(description);
     case StatusCode::GENERIC_TRANSACTION_ERROR:
-      throw TransactionError(this->description);
+      throw TransactionError(description);
+    case StatusCode::TRANSACTION_STATUS_ERROR:
+      throw TransactionStatusError(description);
+    case StatusCode::TRANSACTION_TYPE_ERROR:
+      throw TransactionTypeError(description);
     default:
-      throw Exception(StatusCode::GENERIC_ERROR, this->description);
+      throw Exception(StatusCode::GENERIC_ERROR, description);
     }
   }
 
+  /**
+   * Return true if the given StatusCode represents an erroneous state.
+   */
+  inline static auto IsError(StatusCode code) -> bool { return code > 0; };
+
   /**
    * Return true if this TransactionStatus represents a terminated state.
    */
@@ -135,7 +193,9 @@ public:
   /**
    * Return true if this TransactionStatus represents an erroneous state.
    */
-  inline auto IsError() const -> bool { return this->code > 0; };
+  inline auto IsError() const -> bool {
+    return TransactionStatus::IsError(this->code);
+  };
 
   /**
    * Return a description of the erroneous state.
diff --git a/src/caosdb/transaction.cpp b/src/caosdb/transaction.cpp
index 1c889fef4d51486e244f20bd65996eedb465a951..c32d0090392e9b8c44cc8d1e0886f69747337066 100644
--- a/src/caosdb/transaction.cpp
+++ b/src/caosdb/transaction.cpp
@@ -75,7 +75,8 @@ auto get_status_description(int code) -> const std::string & {
      "The Transaction has a transaction type which does not allow the "
      "attempted action."},
     {StatusCode::ORIGINAL_ENTITY_MISSING_ID,
-     "The attempt to update this entity failed because this entity does have "
+     "The attempt to update this entity failed because this entity does not "
+     "have "
      "an id. This is the case when you did not retrieve it before applying any "
      "changes and instantiated the Entity class explicitely."},
     {StatusCode::UNSUPPORTED_FEATURE,
@@ -109,6 +110,59 @@ using grpc::ClientAsyncResponseReader;
 using ProtoEntity = caosdb::entity::v1alpha1::Entity;
 using grpc::CompletionQueue;
 
+ResultSet::iterator::iterator(const ResultSet *result_set_param, int index)
+  : current_index(index), result_set(result_set_param) {}
+
+auto ResultSet::iterator::operator*() const -> const Entity & {
+  return this->result_set->At(current_index);
+}
+
+auto ResultSet::iterator::operator++() -> ResultSet::iterator & {
+  current_index++;
+  return *this;
+}
+
+auto ResultSet::iterator::operator++(int) -> ResultSet::iterator {
+  iterator tmp(*this);
+  operator++();
+  return tmp;
+}
+
+auto ResultSet::iterator::operator!=(const iterator &rhs) const -> bool {
+  return this->current_index != rhs.current_index;
+}
+
+auto ResultSet::begin() const -> ResultSet::iterator {
+  return ResultSet::iterator(this, 0);
+}
+
+auto ResultSet::end() const -> ResultSet::iterator {
+  return ResultSet::iterator(this, Size());
+}
+
+MultiResultSet::MultiResultSet(MultiTransactionResponse *response) {
+  auto *responses = response->mutable_responses();
+  for (auto sub_response : *responses) {
+    switch (sub_response.wrapped_response_case()) {
+    case WrappedResponseCase::kRetrieveResponse:
+      this->entities.push_back(std::make_unique<Entity>(
+        sub_response.mutable_retrieve_response()->release_entity()));
+      break;
+    case WrappedResponseCase::kInsertResponse:
+      this->entities.push_back(
+        std::make_unique<Entity>(sub_response.release_insert_response()));
+      break;
+    case WrappedResponseCase::kDeleteResponse:
+      this->entities.push_back(
+        std::make_unique<Entity>(sub_response.release_insert_response()));
+      break;
+    default:
+      // TODO(tf) Updates
+      break;
+    }
+  }
+}
+
 [[nodiscard]] auto UniqueResult::GetEntity() const -> const Entity & {
   const Entity *result = this->entity.get();
   return *result;
@@ -131,16 +185,23 @@ auto Transaction::RetrieveById(const std::string &id) noexcept -> StatusCode {
   auto *sub_request = this->request->add_requests();
   sub_request->mutable_retrieve_request()->set_id(id);
 
-  return StatusCode::INITIAL;
+  this->status = TransactionStatus::GO_ON();
+  return this->status.GetCode();
 }
 
+// TODO(fspreck) What about count queries (mixed with retrievals),
+// what about unique queries?
 auto Transaction::Query(const std::string &query) noexcept -> StatusCode {
-  ASSERT_CAN_ADD_RETRIEVAL
+  ASSERT_CAN_ADD_QUERY
 
+  // currently, the server can only process one query at a time (but mixed with
+  // other retrievals).
+  this->has_query = true;
   auto *sub_request = this->request->add_requests();
   sub_request->mutable_retrieve_request()->mutable_query()->set_query(query);
 
-  return StatusCode::INITIAL;
+  this->status = TransactionStatus::GO_ON();
+  return this->status.GetCode();
 }
 
 auto Transaction::DeleteById(const std::string &id) noexcept -> StatusCode {
@@ -149,7 +210,8 @@ auto Transaction::DeleteById(const std::string &id) noexcept -> StatusCode {
   auto *sub_request = this->request->add_requests();
   sub_request->mutable_delete_request()->set_id(id);
 
-  return StatusCode::INITIAL;
+  this->status = TransactionStatus::READY();
+  return this->status.GetCode();
 }
 
 auto Transaction::InsertEntity(Entity *entity) noexcept -> StatusCode {
@@ -164,7 +226,8 @@ auto Transaction::InsertEntity(Entity *entity) noexcept -> StatusCode {
     sub_request->mutable_insert_request()->mutable_upload_id()->CopyFrom(
       entity->GetFileTransmissionId());
   }
-  return StatusCode::INITIAL;
+  this->status = TransactionStatus::READY();
+  return this->status.GetCode();
 }
 
 auto Transaction::UpdateEntity(Entity *entity) noexcept -> StatusCode {
@@ -173,20 +236,23 @@ auto Transaction::UpdateEntity(Entity *entity) noexcept -> StatusCode {
   auto *sub_request = this->request->add_requests();
   auto *proto_entity = sub_request->mutable_update_request()->mutable_entity();
 
-  // copy the original entity for the transaction
   entity->CopyTo(proto_entity);
-  return StatusCode::INITIAL;
+  this->status = TransactionStatus::READY();
+  return this->status.GetCode();
 }
 
 auto Transaction::Execute() -> TransactionStatus {
-  ExecuteAsynchronously();
+  auto status_code = ExecuteAsynchronously();
+  TransactionStatus::ThrowExceptionIfError(
+    status_code, caosdb::get_status_description(status_code));
   auto status = WaitForIt();
   status.ThrowExceptionIfError();
   return status;
 }
 
 auto Transaction::ExecuteAsynchronously() noexcept -> StatusCode {
-  if (!IsStatus(TransactionStatus::INITIAL())) {
+  if (!IsStatus(TransactionStatus::READY()) &&
+      !IsStatus(TransactionStatus::GO_ON())) {
     return StatusCode::TRANSACTION_STATUS_ERROR;
   }
   switch (this->transaction_type) {
@@ -283,7 +349,7 @@ auto Transaction::WaitForIt() const noexcept -> TransactionStatus {
       this->result_set = std::make_unique<UniqueResult>(deletedIdResponse);
     } break;
     default:
-      // TODO(tf)
+      // TODO(tf) Error and Update
       break;
     }
   } else {
diff --git a/test/test_entity.cpp b/test/test_entity.cpp
index ede68149ea6bf81c0b2517d283300285efdef260..2794e9bf8979f7e74ad7810b70d0eb49a2ea0e7c 100644
--- a/test/test_entity.cpp
+++ b/test/test_entity.cpp
@@ -30,9 +30,7 @@
 #include "gtest/gtest-message.h"                 // for Message
 #include "gtest/gtest-test-part.h"               // for TestPartResult, Sui...
 #include "gtest/gtest_pred_impl.h"               // for Test, EXPECT_EQ
-#include <iostream>                              // for endl, basic_ostream
 #include <memory>                                // for allocator, shared_ptr
-#include <string>                                // for operator<<
 
 namespace caosdb::entity {
 using caosdb::entity::v1alpha1::IdResponse;
@@ -190,7 +188,6 @@ TEST(test_entity, test_insert_with_parent) {
 
   transaction.InsertEntity(&entity);
 
-  std::cout << entity.ToString() << std::endl;
   EXPECT_EQ(entity.GetName(), "entity_name");
   EXPECT_EQ(entity.GetParents().Size(), 1);
   auto inserted_parent = entity.GetParents().At(0);
@@ -255,18 +252,14 @@ TEST(test_entity, test_from_id_response) {
   info->set_description("info_desc");
   info->set_code(MessageCode::UNSPECIFIED);
 
-  std::cout << entity.ToString() << std::endl;
   Entity other_ent(&idr_warnings_and_infos);
 
-  std::cout << entity.ToString() << std::endl;
   EXPECT_EQ(other_ent.GetId(), "other_entity_id");
-  std::cout << entity.ToString() << std::endl;
   EXPECT_EQ(other_ent.GetWarnings().Size(), 1);
   EXPECT_TRUE(other_ent.HasWarnings());
   EXPECT_EQ(other_ent.GetWarnings().At(0).GetDescription(), "warning_desc");
   EXPECT_EQ(other_ent.GetWarnings().At(0).GetCode(),
             MessageCode::ENTITY_HAS_NO_PROPERTIES);
-  std::cout << entity.ToString() << std::endl;
   EXPECT_EQ(other_ent.GetInfos().Size(), 1);
   EXPECT_EQ(other_ent.GetInfos().At(0).GetDescription(), "info_desc");
   EXPECT_EQ(other_ent.GetInfos().At(0).GetCode(), MessageCode::UNSPECIFIED);
diff --git a/test/test_transaction.cpp b/test/test_transaction.cpp
index b1a51ab4aa0a256f902f5ebe833cab21c83e38e4..557ded7b87fe9947391bb47f6c37b49ee1824cfb 100644
--- a/test/test_transaction.cpp
+++ b/test/test_transaction.cpp
@@ -29,10 +29,9 @@
 #include "gtest/gtest-message.h"       // for Message
 #include "gtest/gtest-test-part.h"     // for SuiteApiResolver, TestPa...
 #include "gtest/gtest_pred_impl.h"     // for Test, TestInfo, TEST
-#include <iostream>
-#include <memory> // for allocator, unique_ptr
-#include <string> // for string, basic_string
-#include <vector> // for vector
+#include <memory>                      // for allocator, unique_ptr
+#include <string>                      // for string, basic_string
+#include <vector>                      // for vector
 
 namespace caosdb::transaction {
 using caosdb::configuration::InsecureConnectionConfiguration;
@@ -47,7 +46,7 @@ TEST(test_transaction, create_transaction) {
   Connection connection(configuration);
   auto transaction = connection.CreateTransaction();
 
-  transaction->RetrieveById("100");
+  ASSERT_EQ(StatusCode::GO_ON, transaction->RetrieveById("100"));
   EXPECT_THROW_MESSAGE(
     transaction->Execute(), ConnectionError,
     "The attempt to execute this transaction was not successful because the "
@@ -99,6 +98,33 @@ TEST(test_transaction, test_multi_result_set_empty) {
   EXPECT_EQ(rs.Size(), 0);
 }
 
+TEST(test_transaction, test_multi_result_iterator) {
+  MultiTransactionResponse response;
+
+  response.add_responses()
+    ->mutable_retrieve_response()
+    ->mutable_entity()
+    ->set_id("100");
+
+  MultiResultSet rs(&response);
+  EXPECT_EQ(rs.Size(), 1);
+
+  for (const Entity &entity : rs) {
+    EXPECT_EQ(entity.GetId(), "100");
+  }
+}
+
+TEST(test_transaction, test_unique_result_iterator) {
+  caosdb::entity::v1alpha1::Entity response;
+  response.set_id("100");
+
+  UniqueResult rs(&response);
+  EXPECT_EQ(rs.Size(), 1);
+  for (const Entity &entity : rs) {
+    EXPECT_EQ(entity.GetId(), "100");
+  }
+}
+
 TEST(test_transaction, test_multi_result_set_one) {
   MultiTransactionResponse response;
   response.add_responses()
@@ -129,7 +155,6 @@ TEST(test_transaction, test_multi_result_set_three) {
   MultiResultSet rs(&response);
   EXPECT_EQ(rs.Size(), 3);
   EXPECT_TRUE(rs.At(1).HasErrors());
-  std::cout << rs.At(1).ToString();
 }
 
 TEST(test_transaction, test_update_entity) {