diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8dcc7c2f1df03b70fbf48ee48c41fb45be52ef8f..2f454fe5907ea2c6b6da41cd73e89b055ee529c5 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,7 +20,7 @@
 
 cmake_minimum_required(VERSION 3.13)
 
-set(libcaosdb_VERSION 0.0.7)
+set(libcaosdb_VERSION 0.0.8)
 set(libcaosdb_COMPATIBLE_SERVER_VERSION_MAJOR 0)
 set(libcaosdb_COMPATIBLE_SERVER_VERSION_MINOR 5)
 set(libcaosdb_COMPATIBLE_SERVER_VERSION_PATCH 0)
@@ -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/conanfile.py b/conanfile.py
index 23e04a68a1b2a6881edbfe8c30e9612fea908a5f..0e922bc74936d4341c8a574220c6149459ac6b7e 100644
--- a/conanfile.py
+++ b/conanfile.py
@@ -3,7 +3,7 @@ from conans import ConanFile, CMake, tools
 
 class CaosdbConan(ConanFile):
     name = "caosdb"
-    version = "0.0.7"
+    version = "0.0.8"
     license = "AGPL-3.0-or-later"
     author = "Timm C. Fitschen <t.fitschen@indiscale.com>"
     url = "https://gitlab.indiscale.com/caosdb/src/caosdb-cpplib.git"
diff --git a/include/caosdb/entity.h b/include/caosdb/entity.h
index d3eb7846d05d80ce406748799db9141d1bdd76fa..4249b91b275902a5b155484922b552d0d5238d67 100644
--- a/include/caosdb/entity.h
+++ b/include/caosdb/entity.h
@@ -86,10 +86,6 @@ public:
 
 private:
   inline Messages() : wrapped(nullptr){};
-  explicit inline Messages(
-    ::google::protobuf::RepeatedPtrField<caosdb::entity::v1alpha1::Message>
-      *wrapped)
-    : wrapped(wrapped){};
 
   ::google::protobuf::RepeatedPtrField<caosdb::entity::v1alpha1::Message>
     *wrapped;
diff --git a/include/caosdb/logging.h b/include/caosdb/logging.h
index fcebc6df8b5db2eb9f800893f8d296d6174285ce..2f04ec6087cfec19e1a5fbe87483544bd4e144a6 100644
--- a/include/caosdb/logging.h
+++ b/include/caosdb/logging.h
@@ -212,4 +212,10 @@ void caosdb_log_trace(const char *channel, const char *msg);
   BOOST_LOG_CHANNEL_SEV(caosdb::logging::logger::get(), Channel,               \
                         CAOSDB_LOG_LEVEL_TRACE)
 
+#define CAOSDB_LOG_ERROR_AND_RETURN_STATUS(Channel, StatusCode, Message)       \
+  CAOSDB_LOG_ERROR(Channel)                                                    \
+    << "StatusCode (" << StatusCode << ") "                                    \
+    << caosdb::get_status_description(StatusCode) << ": " << Message;          \
+  return StatusCode;
+
 #endif
diff --git a/include/caosdb/status_code.h b/include/caosdb/status_code.h
index 66574df7636e8ce55e2c53bae1dd445f797796ae..26f4abd4ea4c8c256f66e23268dcac744e4db3c0 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 {
   INITIAL = -2,
   EXECUTING = -1,
@@ -48,6 +47,8 @@ enum StatusCode {
   CONFIGURATION_ERROR = 23,
   UNKNOWN_CONNECTION_ERROR = 24,
   TRANSACTION_STATUS_ERROR = 25,
+  TRANSACTION_TYPE_ERROR = 26,
+  UNSUPPORTED_FEATURE = 27,
 };
 
 auto get_status_description(int code) -> const std::string &;
diff --git a/include/caosdb/transaction.h b/include/caosdb/transaction.h
index 8b7dd9593f3d8b3ada661bf02e5e141ab1f7b0c4..9d957f08e3f2db4d7e4fb0e52d8351629c2c0f94 100644
--- a/include/caosdb/transaction.h
+++ b/include/caosdb/transaction.h
@@ -18,21 +18,98 @@
  * 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 "caosdb/entity.h"                       // for Entity
+#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...
+#include "boost/preprocessor/seq/limits/size_256.hpp" // for BOOST_PP_SEQ_S...
+#include "caosdb/entity.h"                            // for Entity
+#include "caosdb/logging.h"
 #include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for EntityTransactionSe...
 #include "caosdb/entity/v1alpha1/main.pb.h"      // for Entity, RetrieveReq...
 #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 <memory>                           // for shared_ptr, unique_ptr
-#include <string>                           // for string
+#include <stdexcept>
+#include <iterator>
+// IWYU pragma: no_include <ext/alloc_traits.h>
+#include <memory> // for shared_ptr, unique_ptr
+#include <string> // for string
+#include <vector> // for vector
 
+/**
+ * @brief Creation and execution of transactions.
+ * @author Timm Fitschen
+ * @date 2021-08-05
+ */
 namespace caosdb::transaction {
+
+/**
+ * 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())) {                               \
+    return StatusCode::TRANSACTION_STATUS_ERROR;                               \
+  }                                                                            \
+  switch (this->transaction_type) {                                            \
+  case NONE:                                                                   \
+    this->transaction_type = TransactionType::READ_ONLY;                       \
+  case READ_ONLY:                                                              \
+  case MIXED_READ_AND_WRITE:                                                   \
+    break;                                                                     \
+  default:                                                                     \
+    CAOSDB_LOG_ERROR_AND_RETURN_STATUS(                                        \
+      logger_name, StatusCode::TRANSACTION_TYPE_ERROR,                         \
+      "You cannot add a retrieval to this transaction because it has the "     \
+      "wrong TransactionType.")                                                \
+  }
+
+/**
+ * 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())) {                               \
+    return StatusCode::TRANSACTION_STATUS_ERROR;                               \
+  }                                                                            \
+  switch (this->transaction_type) {                                            \
+  case NONE:                                                                   \
+    this->transaction_type = TransactionType::DELETE;                          \
+  case DELETE:                                                                 \
+  case MIXED_WRITE:                                                            \
+  case MIXED_READ_AND_WRITE:                                                   \
+    break;                                                                     \
+  default:                                                                     \
+    CAOSDB_LOG_ERROR_AND_RETURN_STATUS(                                        \
+      logger_name, StatusCode::TRANSACTION_TYPE_ERROR,                         \
+      "You cannot add a deletion to this transaction because it has the "      \
+      "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())) {                               \
+    return StatusCode::TRANSACTION_STATUS_ERROR;                               \
+  }                                                                            \
+  switch (this->transaction_type) {                                            \
+  case NONE:                                                                   \
+    this->transaction_type = TransactionType::INSERT;                          \
+  case INSERT:                                                                 \
+  case MIXED_WRITE:                                                            \
+  case MIXED_READ_AND_WRITE:                                                   \
+    break;                                                                     \
+  default:                                                                     \
+    CAOSDB_LOG_ERROR_AND_RETURN_STATUS(                                        \
+      logger_name, StatusCode::TRANSACTION_TYPE_ERROR,                         \
+      "You cannot add an insertion to this transaction because it has the "    \
+      "wrong TransactionType.")                                                \
+  }
+
 using caosdb::entity::Entity;
 using ProtoEntity = caosdb::entity::v1alpha1::Entity;
 using caosdb::entity::v1alpha1::EntityTransactionService;
@@ -40,20 +117,65 @@ using caosdb::entity::v1alpha1::IdResponse;
 using caosdb::entity::v1alpha1::MultiTransactionRequest;
 using caosdb::entity::v1alpha1::MultiTransactionResponse;
 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 {
 public:
   virtual ~ResultSet() = default;
+  [[nodiscard]] virtual auto Size() const noexcept -> int = 0;
+  [[nodiscard]] virtual auto At(const int index) const -> const Entity & = 0;
+};
+
+/**
+ * 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 MultiResultSet(MultiTransactionResponse *response);
+  [[nodiscard]] inline auto Size() const noexcept -> int override {
+    return this->entities.size();
+  }
+  [[nodiscard]] inline auto At(const int index) const
+    -> const Entity & override {
+    return *(this->entities.at(index));
+  }
+  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(){};
+  ~UniqueResult() = default;
   explicit inline UniqueResult(ProtoEntity *protoEntity)
     : entity(new Entity(protoEntity)){};
   explicit inline UniqueResult(IdResponse *idResponse)
     : entity(new Entity(idResponse)){};
   [[nodiscard]] auto GetEntity() const -> const Entity &;
+  [[nodiscard]] inline auto Size() const noexcept -> int override { return 1; }
+  [[nodiscard]] inline auto At(const int index) const
+    -> const Entity & override {
+    if (index != 0) {
+      throw std::out_of_range("Index out of range. Length is 1.");
+    }
+    return *(this->entity);
+  }
 
 private:
   std::unique_ptr<Entity> entity;
@@ -63,15 +185,23 @@ private:
  * @brief Create a transaction via `CaosDBConnection.createTransaction()`
  */
 class Transaction {
-private:
-  mutable std::unique_ptr<ResultSet> result_set;
-  mutable TransactionStatus status = TransactionStatus::INITIAL();
-  std::shared_ptr<EntityTransactionService::Stub> service_stub;
-  MultiTransactionRequest *request;
-  mutable MultiTransactionResponse *response;
-  std::string error_message;
-
 public:
+  /**
+   * 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,                /// 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> service_stub);
 
   /**
@@ -80,7 +210,21 @@ public:
    * The retrieval is being processed when the Execute() or
    * ExecuteAsynchronously() methods of this transaction are called.
    */
-  auto RetrieveById(const std::string &id) -> void;
+  auto RetrieveById(const std::string &id) noexcept -> StatusCode;
+
+  /**
+   * Add all entity ids to this transaction for retrieval.
+   */
+  template <class InputIterator>
+  inline auto RetrieveById(InputIterator begin, InputIterator end) noexcept
+    -> StatusCode;
+
+  /**
+   * Add a query to this transaction.
+   *
+   * Currently, only FIND and COUNT queries are supported.
+   */
+  auto Query(const std::string &query) noexcept -> StatusCode;
 
   /**
    * Add the entity to this transaction for an insertion.
@@ -90,7 +234,7 @@ public:
    *
    * Changing the entity afterwards results in undefined behavior.
    */
-  auto InsertEntity(Entity *entity) -> void;
+  auto InsertEntity(Entity *entity) noexcept -> StatusCode;
 
   /**
    * Add an entity id to this transaction for deletion.
@@ -98,7 +242,7 @@ public:
    * The deletion is being processed when the Execute() or
    * ExecuteAsynchronously() methods of this transaction are called.
    */
-  auto DeleteById(const std::string &id) -> void;
+  auto DeleteById(const std::string &id) noexcept -> StatusCode;
 
   inline auto IsStatus(const TransactionStatus &status) const noexcept -> bool {
     return this->status.GetCode() == status.GetCode();
@@ -116,7 +260,7 @@ public:
    *
    * Use WaitForIt() to join the back-ground execution of this transaction.
    */
-  auto ExecuteAsynchronously() noexcept -> void;
+  auto ExecuteAsynchronously() noexcept -> StatusCode;
 
   /**
    * Join the background execution and return the status when the execution
@@ -138,13 +282,48 @@ public:
     return *result_set;
   }
 
+  /**
+   * Return the number of sub-requests in this transaction.
+   *
+   * This is meant for debugging because the number of sub-requests is a
+   * GRPC-API detail.
+   */
+  [[nodiscard]] inline auto GetRequestCount() const -> int {
+    return this->request->requests_size();
+  }
+
   inline auto RequestToString() const -> const std::string {
     google::protobuf::util::JsonOptions options;
     std::string out;
     google::protobuf::util::MessageToJsonString(*this->request, &out, options);
     return out;
   }
+
+private:
+  TransactionType transaction_type = TransactionType::NONE;
+  mutable std::unique_ptr<ResultSet> result_set;
+  mutable TransactionStatus status = TransactionStatus::INITIAL();
+  std::shared_ptr<EntityTransactionService::Stub> service_stub;
+  MultiTransactionRequest *request;
+  mutable MultiTransactionResponse *response;
+  std::string error_message;
 };
 
+template <class InputIterator>
+inline auto Transaction::RetrieveById(InputIterator begin,
+                                      InputIterator end) noexcept
+  -> StatusCode {
+  ASSERT_CAN_ADD_RETRIEVAL
+
+  auto next = begin;
+  while (next != end) {
+    auto *sub_request = this->request->add_requests();
+    sub_request->mutable_retrieve_request()->set_id(*next);
+    next = std::next(next);
+  }
+
+  return StatusCode::INITIAL;
+}
+
 } // namespace caosdb::transaction
 #endif
diff --git a/include/caosdb/transaction_status.h b/include/caosdb/transaction_status.h
index 8e54c8ba79c944f6b157a577cc9db64be01e1473..56c6b005112fd15c76f3c2741803a58837baed9e 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
@@ -44,7 +35,12 @@ using caosdb::exceptions::Exception;
 using caosdb::exceptions::TransactionError;
 
 /**
- * Status of a Request or Transaction.
+ * 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:
diff --git a/src/caosdb/configuration.cpp b/src/caosdb/configuration.cpp
index ac78a257f49137a165e7083233dfb7c5f6e7ac88..76c437234a48fb339f1e04d71afe3ae84ce94ceb 100644
--- a/src/caosdb/configuration.cpp
+++ b/src/caosdb/configuration.cpp
@@ -35,15 +35,16 @@
 #include "caosdb/log_level.h"                          // for CAOSDB_DEFAULT...
 #include "caosdb/status_code.h"                        // for StatusCode
 #include "caosdb/utility.h"                            // for get_home_direc...
-#include <bits/exception.h>                            // for exception
 #include <cassert>                                     // for assert
 #include <cstdlib>                                     // for getenv
 #include <cstring>                                     // for strcmp
-#include <grpcpp/security/credentials.h>               // for SslCredentials
-#include <iterator>                                    // for next
-#include <map>                                         // for map
-#include <stdexcept>                                   // for out_of_range
-#include <string>                                      // for string, operator+
+#include <exception>                                   // IWYU pragma: keep
+// IWYU pragma: no_include <bits/exception.h>
+#include <grpcpp/security/credentials.h> // for SslCredentials
+#include <iterator>                      // for next
+#include <map>                           // for map
+#include <stdexcept>                     // for out_of_range
+#include <string>                        // for string, operator+
 
 namespace caosdb::configuration {
 using boost::filesystem::exists;
diff --git a/src/caosdb/transaction.cpp b/src/caosdb/transaction.cpp
index 66bfac7b98e6f404076f6fbcab82dde563c8be12..d0b9025b7b1a5b48741e8687db9c21136864c683 100644
--- a/src/caosdb/transaction.cpp
+++ b/src/caosdb/transaction.cpp
@@ -18,9 +18,9 @@
  *
  */
 #include "caosdb/transaction.h"
-#include "caosdb/entity/v1alpha1/main.grpc.pb.h"  // for EntityTransactionS...
-#include "caosdb/entity/v1alpha1/main.pb.h"       // for SingleRetrieveRequest
-#include "caosdb/exceptions.h"                    // for TransactionError, ...
+#include "caosdb/entity/v1alpha1/main.grpc.pb.h" // for EntityTransactionS...
+#include "caosdb/entity/v1alpha1/main.pb.h"      // for SingleRetrieveRequest
+#include "caosdb/logging.h"
 #include "caosdb/protobuf_helper.h"               // for get_arena
 #include "caosdb/status_code.h"                   // for StatusCode, AUTHEN...
 #include "google/protobuf/arena.h"                // for Arena
@@ -64,7 +64,12 @@ auto get_status_description(int code) -> const std::string & {
     {StatusCode::UNKNOWN_CONNECTION_ERROR,
      "The ConnectionManager does not know any connection of this name."},
     {StatusCode::TRANSACTION_STATUS_ERROR,
-     "The Transaction is in a wrong state for the attempted action."}};
+     "The Transaction is in a wrong state for the attempted action."},
+    {StatusCode::TRANSACTION_TYPE_ERROR,
+     "The Transaction has a transaction type which does not allow the "
+     "attempted action."},
+    {StatusCode::UNSUPPORTED_FEATURE,
+     "This feature is not available in the this client implementation."}};
   try {
     return descriptions.at(code);
   } catch (const std::out_of_range &exc) {
@@ -80,13 +85,34 @@ using caosdb::entity::v1alpha1::MultiTransactionRequest;
 using caosdb::entity::v1alpha1::MultiTransactionResponse;
 using WrappedResponseCase =
   caosdb::entity::v1alpha1::TransactionResponse::WrappedResponseCase;
-using caosdb::exceptions::TransactionError;
-using caosdb::exceptions::TransactionStatusError;
 using caosdb::utility::get_arena;
 using grpc::ClientAsyncResponseReader;
 using ProtoEntity = caosdb::entity::v1alpha1::Entity;
 using grpc::CompletionQueue;
 
+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;
@@ -101,55 +127,42 @@ Transaction::Transaction(
   this->service_stub = std::move(service_stub);
 }
 
-auto Transaction::RetrieveById(const std::string &id) -> void {
-  if (!IsStatus(TransactionStatus::INITIAL())) {
-    throw TransactionStatusError(
-      caosdb::get_status_description(StatusCode::TRANSACTION_STATUS_ERROR));
-  }
-
-  // TODO(tf) remove checks when the server is ready
-  if (this->request->requests_size() > 0) {
-    throw TransactionError(
-      "This request object cannot handle another RetrieveById sub-request");
-  }
+auto Transaction::RetrieveById(const std::string &id) noexcept -> StatusCode {
+  ASSERT_CAN_ADD_RETRIEVAL
 
   auto *sub_request = this->request->add_requests();
   sub_request->mutable_retrieve_request()->set_id(id);
+
+  return StatusCode::INITIAL;
 }
 
-auto Transaction::DeleteById(const std::string &id) -> void {
-  if (!IsStatus(TransactionStatus::INITIAL())) {
-    throw TransactionStatusError(
-      caosdb::get_status_description(StatusCode::TRANSACTION_STATUS_ERROR));
-  }
+auto Transaction::Query(const std::string &query) noexcept -> StatusCode {
+  ASSERT_CAN_ADD_RETRIEVAL
 
-  // TODO(tf) remove checks when the server is ready
-  if (this->request->requests_size() > 0) {
-    throw TransactionError(
-      "This request object cannot handle another DeleteById sub-request");
-  }
+  auto *sub_request = this->request->add_requests();
+  sub_request->mutable_retrieve_request()->mutable_query()->set_query(query);
+
+  return StatusCode::INITIAL;
+}
+
+auto Transaction::DeleteById(const std::string &id) noexcept -> StatusCode {
+  ASSERT_CAN_ADD_DELETION
 
   auto *sub_request = this->request->add_requests();
   sub_request->mutable_delete_request()->set_id(id);
-}
 
-auto Transaction::InsertEntity(Entity *entity) -> void {
-  if (!IsStatus(TransactionStatus::INITIAL())) {
-    throw TransactionStatusError(
-      caosdb::get_status_description(StatusCode::TRANSACTION_STATUS_ERROR));
-  }
+  return StatusCode::INITIAL;
+}
 
-  // TODO(tf) remove checks when the server is ready
-  if (this->request->requests_size() > 0) {
-    throw TransactionError(
-      "This request object cannot handle another DeleteById sub-request");
-  }
+auto Transaction::InsertEntity(Entity *entity) noexcept -> StatusCode {
+  ASSERT_CAN_ADD_INSERTION
 
   auto *sub_request = this->request->add_requests();
   auto *proto_entity = sub_request->mutable_insert_request();
 
   // copy the original entity for the transaction
   entity->CopyTo(proto_entity);
+  return StatusCode::INITIAL;
 }
 
 auto Transaction::Execute() -> TransactionStatus {
@@ -159,7 +172,26 @@ auto Transaction::Execute() -> TransactionStatus {
   return status;
 }
 
-auto Transaction::ExecuteAsynchronously() noexcept -> void {
+auto Transaction::ExecuteAsynchronously() noexcept -> StatusCode {
+  if (!IsStatus(TransactionStatus::INITIAL())) {
+    return StatusCode::TRANSACTION_STATUS_ERROR;
+  }
+  switch (this->transaction_type) {
+  case MIXED_WRITE:
+    CAOSDB_LOG_ERROR_AND_RETURN_STATUS(
+      logger_name, StatusCode::UNSUPPORTED_FEATURE,
+      "MIXED_WRITE UNSUPPORTED: The current implementation does not support "
+      "mixed write transactions (containing insertions, deletions, and updates "
+      "in one transaction).")
+  case MIXED_READ_AND_WRITE:
+    CAOSDB_LOG_ERROR_AND_RETURN_STATUS(
+      logger_name, StatusCode::UNSUPPORTED_FEATURE,
+      "MIXED_WRITE UNSUPPORTED: The current implementation does not support "
+      "mixed read and write transactions (containing retrievals, insertions, "
+      "deletions, and updates in one transaction).")
+  default:
+    break;
+  }
   this->status = TransactionStatus::EXECUTING();
 
   grpc::Status grpc_status;
@@ -198,6 +230,7 @@ auto Transaction::ExecuteAsynchronously() noexcept -> void {
   } else {
     this->status = TransactionStatus::SUCCESS();
   }
+  return StatusCode::EXECUTING;
 }
 
 auto Transaction::WaitForIt() const noexcept -> TransactionStatus {
@@ -229,11 +262,11 @@ 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 {
-    // TODO(tf)
+    this->result_set = std::make_unique<MultiResultSet>(this->response);
   }
 
   return this->status;
diff --git a/test/test_transaction.cpp b/test/test_transaction.cpp
index ab1979f7e455d8b1b9fd7e8d672bd1b8ab1f5775..118208595ea5d100dacb8302be555772c8fcd80e 100644
--- a/test/test_transaction.cpp
+++ b/test/test_transaction.cpp
@@ -1,5 +1,4 @@
 /*
- *
  * This file is a part of the CaosDB Project.
  *
  * Copyright (C) 2021 Timm Fitschen <t.fitschen@indiscale.com>
@@ -17,7 +16,6 @@
  *
  * You should have received a copy of the GNU Affero General Public License
  * along with this program. If not, see <https://www.gnu.org/licenses/>.
- *
  */
 #include "caosdb/configuration.h"           // for InsecureConnectionConfig...
 #include "caosdb/connection.h"              // for Connection
@@ -31,7 +29,10 @@
 #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 <memory>                      // for allocator, unique_ptr
+#include <iostream>
+#include <memory> // for allocator, unique_ptr
+#include <string> // for string, basic_string
+#include <vector> // for vector
 
 namespace caosdb::transaction {
 using caosdb::configuration::InsecureConnectionConfiguration;
@@ -73,9 +74,62 @@ TEST(test_transaction, test_unavailable) {
 
   transaction->RetrieveById("100");
   transaction->ExecuteAsynchronously();
+  EXPECT_EQ(transaction->GetRequestCount(), 1);
 
   auto status = transaction->WaitForIt();
   EXPECT_EQ(status.GetCode(), StatusCode::CONNECTION_ERROR);
 }
 
+TEST(test_transaction, test_retrieve_by_ids) {
+  const auto *host = "localhost";
+  auto configuration = InsecureConnectionConfiguration(host, 8000);
+  Connection connection(configuration);
+  auto transaction = connection.CreateTransaction();
+
+  std::vector<std::string> ids = {"100", "101", "102"};
+  transaction->RetrieveById(ids.begin(), ids.end());
+
+  EXPECT_EQ(transaction->GetRequestCount(), 3);
+}
+
+TEST(test_transaction, test_multi_result_set_empty) {
+  MultiTransactionResponse response;
+
+  MultiResultSet rs(&response);
+  EXPECT_EQ(rs.Size(), 0);
+}
+
+TEST(test_transaction, test_multi_result_set_one) {
+  MultiTransactionResponse response;
+  response.add_responses()
+    ->mutable_retrieve_response()
+    ->mutable_entity()
+    ->set_id("100");
+
+  MultiResultSet rs(&response);
+  EXPECT_EQ(rs.Size(), 1);
+  EXPECT_EQ(rs.At(0).GetId(), "100");
+}
+
+TEST(test_transaction, test_multi_result_set_three) {
+  MultiTransactionResponse response;
+  response.add_responses()
+    ->mutable_retrieve_response()
+    ->mutable_entity()
+    ->set_id("100");
+  auto *entity_with_error =
+    response.add_responses()->mutable_retrieve_response()->mutable_entity();
+  entity_with_error->set_id("101");
+  entity_with_error->add_errors()->set_code(1);
+  response.add_responses()
+    ->mutable_retrieve_response()
+    ->mutable_entity()
+    ->set_id("102");
+
+  MultiResultSet rs(&response);
+  EXPECT_EQ(rs.Size(), 3);
+  EXPECT_TRUE(rs.At(1).HasErrors());
+  std::cout << rs.At(1).ToString();
+}
+
 } // namespace caosdb::transaction