diff --git a/core/lib/Drupal/Core/Config/Schema/Mapping.php b/core/lib/Drupal/Core/Config/Schema/Mapping.php
index 9701df0..efb50b7 100644
--- a/core/lib/Drupal/Core/Config/Schema/Mapping.php
+++ b/core/lib/Drupal/Core/Config/Schema/Mapping.php
@@ -50,27 +50,14 @@ public function get($property_name) {
   }
 
   /**
-   * Implements Drupal\Core\TypedData\ComplexDataInterface::set().
+   * {@inheritdoc}
    */
   public function set($property_name, $value, $notify = TRUE) {
-    // Set the data into the configuration array but behave according to the
-    // interface specification when we've got a null value.
-    if (isset($value)) {
-      $this->value[$property_name] = $value;
-      $property = $this->get($property_name);
-    }
-    else {
-      // In these objects, when clearing the value, the property is gone.
-      // As this needs to return a property, we get it before we delete it.
-      $property = $this->get($property_name);
-      unset($this->value[$property_name]);
-      $property->setValue($value);
-    }
-    // Notify the parent of any changes.
-    if ($notify && isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
-    return $property;
+    $this->value[$property_name] = $value;
+    // Directly notify ourselves, but forward notifications to the parent only
+    // if specified.
+    $this->onChange($property_name, $notify);
+    return $this;
   }
 
   /**
@@ -128,11 +115,14 @@ public function isEmpty() {
   }
 
   /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::onChange().
+   * {@inheritdoc}
+   *
+   * @param bool $notify_parent
+   *   (optional) Whether the notification shall be forwarded to the parent.
    */
-  public function onChange($property_name) {
+  public function onChange($property_name, $notify_parent = TRUE) {
     // Notify the parent of changes.
-    if (isset($this->parent)) {
+    if (isset($this->parent) && $notify_parent) {
       $this->parent->onChange($this->name);
     }
   }
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
index 7e0b383..a0d2591 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -423,12 +423,10 @@ protected function getTranslatedField($property_name, $langcode) {
    */
   public function set($property_name, $value, $notify = TRUE) {
     $this->get($property_name)->setValue($value, FALSE);
-
-    if ($property_name == 'langcode') {
-      // Avoid using unset as this unnecessarily triggers magic methods later
-      // on.
-      $this->language = NULL;
-    }
+    // Directly notify ourselves. We can ignore $notify here as there is no
+    // parent to notify anyway.
+    $this->onChange($property_name);
+    return $this;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Field/FieldItemBase.php b/core/lib/Drupal/Core/Field/FieldItemBase.php
index 4645936..e6575cf 100644
--- a/core/lib/Drupal/Core/Field/FieldItemBase.php
+++ b/core/lib/Drupal/Core/Field/FieldItemBase.php
@@ -131,16 +131,14 @@ public function set($property_name, $value, $notify = TRUE) {
     // value that needs to be updated.
     if (isset($this->properties[$property_name])) {
       $this->properties[$property_name]->setValue($value, FALSE);
-      unset($this->values[$property_name]);
     }
     // Allow setting plain values for not-defined properties also.
     else {
       $this->values[$property_name] = $value;
     }
-    // Directly notify ourselves.
-    if ($notify) {
-      $this->onChange($property_name);
-    }
+    // Directly notify ourselves, but forward notifications to the parent only
+    // if specified.
+    $this->onChange($property_name, $notify);
   }
 
   /**
@@ -171,18 +169,18 @@ public function __unset($name) {
   }
 
   /**
-   * Overrides \Drupal\Core\TypedData\Map::onChange().
+   * {@inheritdoc}
    */
-  public function onChange($property_name) {
-    // Notify the parent of changes.
-    if (isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
+  public function onChange($property_name, $notify_parent = TRUE) {
     // Remove the plain value, such that any further __get() calls go via the
     // updated property object.
     if (isset($this->properties[$property_name])) {
       unset($this->values[$property_name]);
     }
+    // Notify the parent of changes.
+    if (isset($this->parent) && $notify_parent) {
+      $this->parent->onChange($this->name);
+    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
index 61f624f..cd7b3fc 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
@@ -134,14 +134,15 @@ public function setValue($values, $notify = TRUE) {
   /**
    * {@inheritdoc}
    */
-  public function onChange($property_name) {
-    // Make sure that the target ID and the target property stay in sync.
+  public function onChange($property_name, $notify_parent = TRUE) {
+    // Make sure that the target ID and the target property stay in sync, but
+    // avoid triggering notifications again.
     if ($property_name == 'target_id') {
-      $this->properties['entity']->setValue($this->target_id, FALSE);
+      $this->get('entity')->setValue($this->target_id, FALSE);
     }
     elseif ($property_name == 'entity') {
-      $this->set('target_id', $this->properties['entity']->getTargetIdentifier(), FALSE);
+      $this->get('target_id')->setValue($this->properties['entity']->getTargetIdentifier(), FALSE);
     }
-    parent::onChange($property_name);
+    parent::onChange($property_name, $notify_parent);
   }
 }
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LanguageItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LanguageItem.php
index 3ed6127..ee9b4ad 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LanguageItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/LanguageItem.php
@@ -94,7 +94,7 @@ public function applyDefaultValue($notify = TRUE) {
   /**
    * {@inheritdoc}
    */
-  public function onChange($property_name) {
+  public function onChange($property_name, $notify_parent = TRUE) {
     // Make sure that the value and the language property stay in sync.
     if ($property_name == 'value') {
       $this->properties['language']->setValue($this->value, FALSE);
@@ -102,6 +102,6 @@ public function onChange($property_name) {
     elseif ($property_name == 'language') {
       $this->set('value', $this->properties['language']->getTargetIdentifier(), FALSE);
     }
-    parent::onChange($property_name);
+    parent::onChange($property_name, $notify_parent);
   }
 }
diff --git a/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php b/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php
index 2e3d0e7..76212ac 100644
--- a/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php
+++ b/core/lib/Drupal/Core/TypedData/ComplexDataInterface.php
@@ -50,8 +50,8 @@ public function get($property_name);
    * @throws \InvalidArgumentException
    *   If the specified property does not exist.
    *
-   * @return \Drupal\Core\TypedData\TypedDataInterface
-   *   The property object.
+   * @return self
+   *   The object itself for chaining.
    */
   public function set($property_name, $value, $notify = TRUE);
 
diff --git a/core/lib/Drupal/Core/TypedData/Plugin/DataType/Map.php b/core/lib/Drupal/Core/TypedData/Plugin/DataType/Map.php
index 180003c..b1e9576 100644
--- a/core/lib/Drupal/Core/TypedData/Plugin/DataType/Map.php
+++ b/core/lib/Drupal/Core/TypedData/Plugin/DataType/Map.php
@@ -128,20 +128,24 @@ public function get($property_name) {
   }
 
   /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::set().
+   * {@inheritdoc}
    */
   public function set($property_name, $value, $notify = TRUE) {
     if ($this->getPropertyDefinition($property_name)) {
-      $this->get($property_name)->setValue($value, $notify);
+      $this->get($property_name)->setValue($value, FALSE);
     }
     else {
       // Just set the plain value, which allows adding a new entry to the map.
       $this->values[$property_name] = $value;
       // Directly notify ourselves.
       if ($notify) {
-        $this->onChange($property_name, $value);
+        $this->onChange($property_name);
       }
     }
+    // Directly notify ourselves, but forward notifications to the parent only
+    // if specified.
+    $this->onChange($property_name, $notify);
+    return $this;
   }
 
   /**
@@ -228,11 +232,14 @@ public function __clone() {
   }
 
   /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::onChange().
+   * {@inheritdoc}
+   *
+   * @param bool $notify_parent
+   *   (optional) Whether the notification shall be forwarded to the parent.
    */
-  public function onChange($property_name) {
+  public function onChange($property_name, $notify_parent = TRUE) {
     // Notify the parent of changes.
-    if (isset($this->parent)) {
+    if (isset($this->parent) && $notify_parent) {
       $this->parent->onChange($this->name);
     }
   }
diff --git a/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldType/DateTimeItem.php b/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldType/DateTimeItem.php
index e4cad9a..c408463 100644
--- a/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldType/DateTimeItem.php
+++ b/core/modules/datetime/lib/Drupal/datetime/Plugin/Field/FieldType/DateTimeItem.php
@@ -145,13 +145,12 @@ public function isEmpty() {
   /**
    * {@inheritdoc}
    */
-  public function onChange($property_name) {
-    parent::onChange($property_name);
-
+  public function onChange($property_name, $notify_parent = TRUE) {
     // Enforce that the computed date is recalculated.
     if ($property_name == 'value') {
       $this->date = NULL;
     }
+    parent::onChange($property_name, $notify_parent);
   }
 
 }
diff --git a/core/modules/text/lib/Drupal/text/Plugin/Field/FieldType/TextItemBase.php b/core/modules/text/lib/Drupal/text/Plugin/Field/FieldType/TextItemBase.php
index 07e3950..e3c4ae6 100644
--- a/core/modules/text/lib/Drupal/text/Plugin/Field/FieldType/TextItemBase.php
+++ b/core/modules/text/lib/Drupal/text/Plugin/Field/FieldType/TextItemBase.php
@@ -90,12 +90,7 @@ public function getCacheData() {
   /**
    * {@inheritdoc}
    */
-  public function onChange($property_name) {
-    // Notify the parent of changes.
-    if (isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
-
+  public function onChange($property_name, $notify_parent = TRUE) {
     // Unset processed properties that are affected by the change.
     foreach ($this->getPropertyDefinitions() as $property => $definition) {
       if (isset($definition['class']) && ($definition['class'] == '\Drupal\text\TextProcessed')) {
@@ -104,6 +99,7 @@ public function onChange($property_name) {
         }
       }
     }
+    parent::onChange($property_name, $notify_parent);
   }
 
 }
