diff --git a/include/caosdb/value.h b/include/caosdb/value.h
index 6031e75dbd69c262cc69079c63323d916cec223d..48e8580cee0e3b2a008d00cdc44d69e5fb6b3713 100644
--- a/include/caosdb/value.h
+++ b/include/caosdb/value.h
@@ -25,9 +25,10 @@
 #include "caosdb/entity/v1alpha1/main.pb.h" // for RepeatedPtrField, Message
 #include "caosdb/logging.h"
 #include <google/protobuf/util/json_util.h> // for MessageToJson...
-#include <memory>                           // for unique_ptr
-#include <string>                           // for string
-#include <vector>                           // for vector
+#include <map>
+#include <memory> // for unique_ptr
+#include <string> // for string
+#include <vector> // for vector
 
 #define LIST_VALUE_CONSTRUCTOR(TYPE, SETTER)                                                       \
   explicit inline Value(const std::vector<TYPE> &values) : ProtoMessageWrapper<ProtoValue>() {     \
@@ -37,7 +38,9 @@
   }
 
 namespace caosdb::entity {
+using caosdb::utility::get_arena;
 using caosdb::utility::ProtoMessageWrapper;
+using google::protobuf::Arena;
 using ProtoSpecialValue = caosdb::entity::v1alpha1::SpecialValue;
 using ProtoValue = caosdb::entity::v1alpha1::Value;
 using ProtoScalarValue = caosdb::entity::v1alpha1::ScalarValue;
@@ -46,6 +49,8 @@ using ScalarValueCase = caosdb::entity::v1alpha1::ScalarValue::ScalarValueCase;
 
 class Entity;
 class Property;
+class ScalarValue;
+class Value;
 
 // Represents special values which are otherwise hard to tranfer via protobuf.
 enum SpecialValue {
@@ -55,46 +60,225 @@ enum SpecialValue {
   EMPTY_STRING = ProtoSpecialValue::SPECIAL_VALUE_EMPTY_STRING,
 };
 
-class ScalarValue : public ProtoMessageWrapper<ProtoScalarValue> {
+class AbstractValue {
 public:
+  /**
+   * Virtual destructor.
+   */
+  virtual ~AbstractValue(){};
+  /**
+   * Return true iff the value is a NULL value (NULL in the CaosDB sense).
+   */
+  [[nodiscard]] virtual auto IsNull() const noexcept -> bool = 0;
+  /**
+   * Return true iff the value is best represented in C++ types as a
+   * std::string.
+   *
+   * If true, clients may call GetAsString to receive a std::string
+   * representation of the value.
+   */
+  [[nodiscard]] virtual auto IsString() const noexcept -> bool = 0;
+  /**
+   * Return true iff the value is best represented in C++ types as a
+   * bool.
+   *
+   * If true, clients may call GetAsBool to receive a bool
+   * representation of the value.
+   */
+  [[nodiscard]] virtual auto IsBool() const noexcept -> bool = 0;
+  /**
+   * Return true iff the value is best represented in C++ types as a
+   * double.
+   *
+   * If true, clients may call GetAsDouble to receive a double
+   * representation of the value.
+   */
+  [[nodiscard]] virtual auto IsDouble() const noexcept -> bool = 0;
+  /**
+   * Return true iff the value is best represented in C++ types as an
+   * int64_t.
+   *
+   * If true, clients may call GetAsInt64 to receive an int64_t
+   * representation of the value.
+   */
+  [[nodiscard]] virtual auto IsInt64() const noexcept -> bool = 0;
+  /**
+   * Return true iff the value is best represented in C++ types as a
+   * std::vector<ScalarValue>.
+   *
+   * If true, clients may call GetAsVector to receive a
+   * std::vector<ScalarValue> representation of the value.
+   */
+  [[nodiscard]] virtual auto IsVector() const noexcept -> bool = 0;
+
+  /**
+   * Return a std::string representation of this value.
+   *
+   * Clients should call IsString before calling this function in order to
+   * assure that this value is indeed best represented by a std::string.
+   *
+   * The return value is undefined if IsString is false.
+   */
+  [[nodiscard]] virtual auto GetAsString() const noexcept -> const std::string & = 0;
+  /**
+   * Return a bool representation of this value.
+   *
+   * Clients should call IsBool before calling this function in order to
+   * assure that this value is indeed best represented by a bool.
+   *
+   * The return value is undefined if IsBool is false.
+   */
+  [[nodiscard]] virtual auto GetAsBool() const noexcept -> bool = 0;
+  /**
+   * Return a double representation of this value.
+   *
+   * Clients should call IsDouble before calling this function in order to
+   * assure that this value is indeed best represented by a double.
+   *
+   * The return value is undefined if IsDouble is false.
+   */
+  [[nodiscard]] virtual auto GetAsDouble() const noexcept -> double = 0;
+  /**
+   * Return an int64_t representation of this value.
+   *
+   * Clients should call IsInt64 before calling this function in order to
+   * assure that this value is indeed best represented by an int64_t.
+   *
+   * The return value is undefined if IsInt64 is false.
+   */
+  [[nodiscard]] virtual auto GetAsInt64() const noexcept -> int64_t = 0;
+  /**
+   * Return a std::vector<ScalarValue> representation of this value.
+   *
+   * Clients should call IsVector before calling this function in order to
+   * assure that this value is indeed best represented by a
+   * std::vector<ScalarValue>.
+   *
+   * The return value is undefined if IsVector is false.
+   */
+  [[nodiscard]] virtual auto GetAsVector() const noexcept -> const std::vector<ScalarValue> & = 0;
+  friend class Value;
+
+protected:
+  [[nodiscard]] virtual auto GetProtoValue() const noexcept -> const ProtoValue * = 0;
+};
+
+class ScalarValue : public AbstractValue, public ProtoMessageWrapper<ProtoScalarValue> {
+public:
+  /**
+   * Destructor.
+   */
+  inline ~ScalarValue(){};
+  /**
+   * Copy constructor.
+   */
+  inline ScalarValue(const ScalarValue &original) : ScalarValue() {
+    this->wrapped->CopyFrom(*original.wrapped);
+  }
+  /**
+   * Move constructor.
+   */
+  inline ScalarValue(ScalarValue &&other) : ScalarValue(std::move(other.wrapped)){};
+  /**
+   * Copy assignment operator.
+   */
+  inline auto operator=(const ScalarValue &original) -> ScalarValue & {
+    if (&original != this) {
+      this->wrapped->CopyFrom(*original.wrapped);
+    }
+    return *this;
+  }
+  /**
+   * Move assignment operator.
+   */
+  inline auto operator=(ScalarValue &&other) -> ScalarValue & {
+    if (&other != this) {
+      this->wrapped = std::move(other.wrapped);
+    }
+    return *this;
+  }
+
   inline ScalarValue(ProtoScalarValue *wrapped) : ProtoMessageWrapper<ProtoScalarValue>(wrapped) {}
 
+  [[nodiscard]] inline auto IsNull() const noexcept -> bool {
+    return (this->wrapped->scalar_value_case() == ScalarValueCase::kSpecialValue &&
+            this->wrapped->special_value() == ProtoSpecialValue::SPECIAL_VALUE_UNSPECIFIED);
+  }
   [[nodiscard]] inline auto IsString() const noexcept -> bool {
     return (this->wrapped->scalar_value_case() == ScalarValueCase::kStringValue) ||
            (this->wrapped->scalar_value_case() == ScalarValueCase::kSpecialValue &&
             this->wrapped->special_value() == ProtoSpecialValue::SPECIAL_VALUE_EMPTY_STRING);
   }
-  [[nodiscard]] inline auto AsString() const noexcept -> const std::string & {
+  [[nodiscard]] inline auto GetAsString() const noexcept -> const std::string & {
     return this->wrapped->string_value();
   }
 
   [[nodiscard]] inline auto IsDouble() const noexcept -> bool {
     return (this->wrapped->scalar_value_case() == ScalarValueCase::kDoubleValue);
   }
-  [[nodiscard]] inline auto AsDouble() const noexcept -> double {
+  [[nodiscard]] inline auto GetAsDouble() const noexcept -> double {
     return this->wrapped->double_value();
   }
 
-  [[nodiscard]] inline auto IsInteger() const noexcept -> bool {
+  [[nodiscard]] inline auto IsInt64() const noexcept -> bool {
     return (this->wrapped->scalar_value_case() == ScalarValueCase::kIntegerValue);
   }
-  [[nodiscard]] inline auto AsInteger() const noexcept -> int64_t {
+  [[nodiscard]] inline auto GetAsInt64() const noexcept -> int64_t {
     return this->wrapped->integer_value();
   }
 
   [[nodiscard]] inline auto IsBool() const noexcept -> bool {
     return (this->wrapped->scalar_value_case() == ScalarValueCase::kBooleanValue);
   }
-  [[nodiscard]] inline auto AsBool() const noexcept -> bool {
+  [[nodiscard]] inline auto GetAsBool() const noexcept -> bool {
     return this->wrapped->boolean_value();
   }
+  [[nodiscard]] auto IsVector() const noexcept -> bool {
+    // Always false b/c a scalar value is never a collection.
+    return false;
+  }
+  [[nodiscard]] auto GetAsVector() const noexcept -> const std::vector<ScalarValue> & {
+    // Always return an empty vector.
+    static const std::vector<ScalarValue> empty_collection;
+    return empty_collection;
+  }
+  friend class Value;
+
+protected:
+  [[nodiscard]] auto GetProtoValue() const noexcept -> const ProtoValue * {
+    if (this->proto_value == nullptr) {
+      this->proto_value = Arena::CreateMessage<ProtoValue>(get_arena());
+      this->proto_value->mutable_scalar_value()->CopyFrom(*this->wrapped);
+    }
+    return this->proto_value;
+  };
+  inline ScalarValue() : ProtoMessageWrapper<ProtoScalarValue>() {}
+
+private:
+  mutable ProtoValue *proto_value;
 };
 
-class Value : public ProtoMessageWrapper<ProtoValue> {
+class Value : public AbstractValue, public ProtoMessageWrapper<ProtoValue> {
 public:
+  /**
+   * Copy constructor.
+   */
+  inline Value(const Value &original) : Value() { this->wrapped->CopyFrom(*original.wrapped); }
+  /**
+   * Move constructor.
+   */
+  inline Value(Value &&other) : Value(std::move(other.wrapped)) {}
+  /**
+   * Destructor.
+   */
+  inline ~Value() {}
   inline Value() : ProtoMessageWrapper<ProtoValue>() {
     // has NULL_VALUE now
   }
+  explicit inline Value(const ScalarValue &value) : Value() {
+    this->wrapped->mutable_scalar_value()->CopyFrom(*value.wrapped);
+  }
+  explicit inline Value(const AbstractValue &value) : Value(value.GetProtoValue()) {}
   explicit inline Value(ProtoValue *wrapped) : ProtoMessageWrapper<ProtoValue>(wrapped) {}
   explicit inline Value(const std::string &value) : ProtoMessageWrapper<ProtoValue>() {
     this->wrapped->mutable_scalar_value()->set_string_value(value);
@@ -121,7 +305,10 @@ public:
   LIST_VALUE_CONSTRUCTOR(bool, set_boolean_value)
 
   [[nodiscard]] inline auto IsNull() const noexcept -> bool {
-    return this->wrapped->value_case() == ValueCase::VALUE_NOT_SET;
+    return this->wrapped->value_case() == ValueCase::VALUE_NOT_SET ||
+           (this->wrapped->scalar_value().scalar_value_case() == ScalarValueCase::kSpecialValue &&
+            this->wrapped->scalar_value().special_value() ==
+              ProtoSpecialValue::SPECIAL_VALUE_UNSPECIFIED);
   }
 
   [[nodiscard]] inline auto IsString() const noexcept -> bool {
@@ -134,7 +321,7 @@ public:
     }
     return false;
   }
-  [[nodiscard]] inline auto AsString() const noexcept -> const std::string & {
+  [[nodiscard]] inline auto GetAsString() const noexcept -> const std::string & {
     return this->wrapped->scalar_value().string_value();
     ;
   }
@@ -146,18 +333,18 @@ public:
     }
     return false;
   }
-  [[nodiscard]] inline auto AsDouble() const noexcept -> double {
+  [[nodiscard]] inline auto GetAsDouble() const noexcept -> double {
     return this->wrapped->scalar_value().double_value();
   }
 
-  [[nodiscard]] inline auto IsInteger() const noexcept -> bool {
+  [[nodiscard]] inline auto IsInt64() const noexcept -> bool {
     if (this->wrapped->value_case() == ValueCase::kScalarValue) {
 
       return (this->wrapped->scalar_value().scalar_value_case() == ScalarValueCase::kIntegerValue);
     }
     return false;
   }
-  [[nodiscard]] inline auto AsInteger() const noexcept -> int64_t {
+  [[nodiscard]] inline auto GetAsInt64() const noexcept -> int64_t {
     return this->wrapped->scalar_value().integer_value();
   }
 
@@ -168,31 +355,55 @@ public:
     }
     return false;
   }
-  [[nodiscard]] inline auto AsBool() const noexcept -> bool {
+  [[nodiscard]] inline auto GetAsBool() const noexcept -> bool {
     return this->wrapped->scalar_value().boolean_value();
   }
 
-  [[nodiscard]] inline auto IsList() const noexcept -> bool {
+  [[nodiscard]] inline auto IsVector() const noexcept -> bool {
     return this->wrapped->value_case() == ValueCase::kListValues;
   }
-  [[nodiscard]] inline auto AsList() const noexcept -> const std::vector<ScalarValue> & {
-    if (!IsList()) {
+  [[nodiscard]] inline auto GetAsVector() const noexcept -> const std::vector<ScalarValue> & {
+    if (!IsVector()) {
       // create empty list
-      this->list_values = std::make_unique<std::vector<ScalarValue>>();
+      this->collection_values = std::make_unique<std::vector<ScalarValue>>();
     }
-    if (this->list_values == nullptr) {
-      this->list_values = std::make_unique<std::vector<ScalarValue>>();
+    if (this->collection_values == nullptr) {
+      this->collection_values = std::make_unique<std::vector<ScalarValue>>();
       for (auto &scalar : *(this->wrapped->mutable_list_values()->mutable_values())) {
-        this->list_values->push_back(ScalarValue(&scalar));
+        this->collection_values->push_back(ScalarValue(&scalar));
       }
     }
-    return *(this->list_values);
+    return *(this->collection_values);
   }
 
+  /**
+   * Return true if the underlying Protobuf messages have the same
+   * serialization.
+   */
   inline auto operator==(const Value &other) const noexcept -> bool {
     return this->wrapped->SerializeAsString() == other.wrapped->SerializeAsString();
   }
 
+  /**
+   * Copy assignment operator.
+   */
+  inline auto operator=(const Value &other) -> Value & {
+    if (&other != this) {
+      this->wrapped->CopyFrom(*other.wrapped);
+    }
+    return *this;
+  }
+
+  /**
+   * Move assignment operator.
+   */
+  inline auto operator=(Value &&other) -> Value & {
+    if (&other != this) {
+      this->wrapped = std::move(other.wrapped);
+    }
+    return *this;
+  }
+
   inline auto ToString() const noexcept -> const std::string {
     CAOSDB_DEBUG_MESSAGE_STRING(*wrapped, out)
     return out;
@@ -201,8 +412,11 @@ public:
   friend class Entity;
   friend class Property;
 
+protected:
+  [[nodiscard]] auto GetProtoValue() const noexcept -> const ProtoValue * { return this->wrapped; };
+
 private:
-  mutable std::unique_ptr<std::vector<ScalarValue>> list_values;
+  mutable std::unique_ptr<std::vector<ScalarValue>> collection_values;
 };
 
 } // namespace caosdb::entity
diff --git a/src/ccaosdb.cpp b/src/ccaosdb.cpp
index 7dd555bcada9df80623f43511f75b1c9f2e64ee5..cb0278f83791efbe9558bd512baa69388ba50bb7 100644
--- a/src/ccaosdb.cpp
+++ b/src/ccaosdb.cpp
@@ -23,6 +23,7 @@
 #include "caosdb/connection.h"
 #include "caosdb/constants.h"
 #include "caosdb/data_type.h" // for DataType, AtomicDat...
+#include "caosdb/entity.h"
 #include "caosdb/value.h"
 #include "caosdb/utility.h"
 #include "caosdb/status_code.h"
@@ -47,7 +48,7 @@ extern "C" {
 
 #define WRAPPED_MESSAGE_CAST(name) static_cast<caosdb::entity::Message *>(name->wrapped_message)
 
-#define WRAPPED_VALUE_CAST(name) static_cast<caosdb::entity::Value *>(name->wrapped_value)
+#define WRAPPED_VALUE_CAST(name) static_cast<caosdb::entity::AbstractValue *>(name->wrapped_value)
 
 #define ENUM_NAME_FROM_VALUE(arg, etype)                                                           \
   caosdb::utility::getEnumNameFromValue<caosdb::entity::etype>(arg)