diff --git a/core/lib/Drupal/Core/Config/Schema/Mapping.php b/core/lib/Drupal/Core/Config/Schema/Mapping.php
index b7bddb3..752ad46 100644
--- a/core/lib/Drupal/Core/Config/Schema/Mapping.php
+++ b/core/lib/Drupal/Core/Config/Schema/Mapping.php
@@ -53,10 +53,6 @@ public function get($property_name) {
    * Implements Drupal\Core\TypedData\ComplexDataInterface::set().
    */
   public function set($property_name, $value, $notify = TRUE) {
-    // Notify the parent of any changes to be made.
-    if ($notify && isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
     // Set the data into the configuration array but behave according to the
     // interface specification when we've got a null value.
     if (isset($value)) {
@@ -71,6 +67,10 @@ public function set($property_name, $value, $notify = TRUE) {
       $property->setValue($value);
       return $property;
     }
+    // Notify the parent of any changes.
+    if ($notify && isset($this->parent)) {
+      $this->parent->onChange($this->name);
+    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php
index bbc3b84..f25b82e 100644
--- a/core/lib/Drupal/Core/Entity/EntityNG.php
+++ b/core/lib/Drupal/Core/Entity/EntityNG.php
@@ -96,7 +96,17 @@ public function __construct(array $values, $entity_type, $bundle = FALSE) {
    * @return string
    */
   public function getType() {
-    return $this->entityType;
+    if ($this->bundle != $this->entityType) {
+      return 'entity:' . $this->entityType . ':' . $this->bundle;
+    }
+    return 'entity:' . $this->entityType;
+  }
+
+  public function getDefinition() {
+    // @todo: add $this->definition.
+    return array(
+      'type' => $this->getType(),
+    );
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php b/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php
index 2c3feef..59dfc6d 100644
--- a/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php
+++ b/core/lib/Drupal/Core/Entity/Field/FieldItemBase.php
@@ -49,10 +49,6 @@ public function setValue($values, $notify = TRUE) {
       $keys = array_keys($this->getPropertyDefinitions());
       $values = array($keys[0] => $values);
     }
-    // Notify the parent of any changes to be made.
-    if ($notify && isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
     $this->values = $values;
     // Update any existing property objects.
     foreach ($this->properties as $name => $property) {
@@ -63,6 +59,10 @@ public function setValue($values, $notify = TRUE) {
       $property->setValue($value, FALSE);
       unset($this->values[$name]);
     }
+    // Notify the parent of any changes.
+    if ($notify && isset($this->parent)) {
+      $this->parent->onChange($this->name);
+    }
   }
 
   /**
@@ -83,10 +83,6 @@ public function __get($name) {
    * Overrides \Drupal\Core\TypedData\Type\Map::set().
    */
   public function set($property_name, $value, $notify = TRUE) {
-    // Notify the parent of any changes to be made.
-    if ($notify && isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
     // For defined properties there is either a property object or a plain
     // value that needs to be updated.
     if (isset($this->properties[$property_name])) {
@@ -96,6 +92,10 @@ public function set($property_name, $value, $notify = TRUE) {
     else {
       $this->values[$property_name] = $value;
     }
+    // Directly notify ourselves.
+    if ($notify) {
+      $this->onChange($property_name);
+    }
   }
 
   /**
@@ -134,6 +134,8 @@ public function onChange($property_name) {
     }
     // Remove the plain value, such that any further __get() calls go via the
     // updated property object.
-    unset($this->values[$property_name]);
+    if (isset($this->properties[$property_name])) {
+      unset($this->values[$property_name]);
+    }
   }
 }
\ No newline at end of file
diff --git a/core/lib/Drupal/Core/Entity/Field/Type/Entity.php b/core/lib/Drupal/Core/Entity/Field/Type/Entity.php
new file mode 100644
index 0000000..400c484
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Field/Type/Entity.php
@@ -0,0 +1,158 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Field\Type\Entity.
+ */
+
+namespace Drupal\Core\Entity\Field\Type;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityNG;
+use Drupal\Core\TypedData\ComplexDataInterface;
+use Drupal\Core\TypedData\ContextAwareInterface;
+use Drupal\Core\TypedData\ContextAwareTypedData;
+use Drupal\Core\TypedData\TypedDataInterface;
+use Drupal\Core\TypedData\TypedData;
+use ArrayIterator;
+use IteratorAggregate;
+use InvalidArgumentException;
+
+/**
+ * Defines the (abstract) 'entity' data type.
+ *
+ * The entity data type is abstract; i.e., data cannot directly be an instance
+ * of 'entity', but instead it can be an instance of some of its sub-types; for
+ * example 'entity:user' or 'entity:node:article'. Entity types that make use of
+ * bundles cannot be instantiated without bundles either, i.e. entity:node is
+ * an abstract type as well as it requires a bundle for instantiation.
+ *
+ * As abstract types cannot be instantiated directly, it's not possible to set a
+ * value on an abstract type object, thus the object is unset and read-only.
+ * Still, abstract typed data objects allow dealing with metadata associated
+ * with the abstract type. For example, the typed data object of 'entity:node'
+ * allows you to iterate over the base fields defined for any node.
+ *
+ * @todo: Provide a way to update the definition after instantiating.
+ *
+ * Supported constraints (below the definition's 'constraints' key) are:
+ *  - EntityType: The entity type.
+ *  - Bundle: The bundle or an array of possible bundles.
+ */
+class Entity extends TypedData implements IteratorAggregate, ComplexDataInterface {
+
+  /**
+   * Overrides \Drupal\Core\TypedData\TypedData::getValue().
+   */
+  public function getValue() {
+    return NULL;
+  }
+
+  /**
+   * Overrides \Drupal\Core\TypedData\TypedData::setValue().
+   *
+   * Both the entity ID and the entity object may be passed as value.
+   */
+  public function setValue($value, $notify = TRUE) {
+    if (isset($value)) {
+      throw new InvalidArgumentException("Cannot set a value for an abstract type.");
+    }
+  }
+
+  /**
+   * Overrides \Drupal\Core\TypedData\TypedData::getString().
+   */
+  public function getString() {
+    return '';
+  }
+
+  /**
+   * Implements \IteratorAggregate::getIterator().
+   */
+  public function getIterator() {
+    return new \ArrayIterator($this->getProperties());
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\ComplexDataInterface::get().
+   */
+  public function get($property_name) {
+    return typed_data()->getPropertyInstance($this, $property_name);
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\ComplexDataInterface::set().
+   */
+  public function set($property_name, $value, $notify = TRUE) {
+    throw new InvalidArgumentException("Cannot set a property of an abstract type.");
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getProperties().
+   */
+  public function getProperties($include_computed = FALSE) {
+    $properties = array();
+    foreach ($this->getPropertyDefinitions() as $name => $definition) {
+      if (empty($definition['computed']) || $include_computed) {
+        $properties[$name] = typed_data()->getPropertyInstance($this, $name);
+      }
+    }
+    return $properties;
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinition().
+   */
+  public function getPropertyDefinition($name) {
+    $definitions = $this->getPropertyDefinitions();
+    if (isset($definitions[$name])) {
+      return $definitions[$name];
+    }
+    else {
+      return FALSE;
+    }
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinitions().
+   */
+  public function getPropertyDefinitions() {
+    // @todo: Support getting definitions if multiple bundles are specified.
+    if (isset($this->definition['constraints']['EntityType'])) {
+      return drupal_container()->get('plugin.manager.entity')
+        ->getStorageController($this->definition['constraints']['EntityType'])
+        ->getFieldDefinitions($this->definition['constraints']);
+    }
+    else {
+      return array();
+    }
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyValues().
+   */
+  public function getPropertyValues() {
+    return array();
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\ComplexDataInterface::setPropertyValues().
+   */
+  public function setPropertyValues($values) {
+    throw new InvalidArgumentException("Cannot set a property of an abstract type.");
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\ComplexDataInterface::isEmpty().
+   */
+  public function isEmpty() {
+    return TRUE;
+  }
+
+  /**
+   * Implements \Drupal\Core\TypedData\ComplexDataInterface::onChange().
+   */
+  public function onChange($property_name) {
+    // Nothing to do.
+  }
+}
diff --git a/core/lib/Drupal/Core/Entity/Field/Type/EntityDeriver.php b/core/lib/Drupal/Core/Entity/Field/Type/EntityDeriver.php
new file mode 100644
index 0000000..2ad896a
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Field/Type/EntityDeriver.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Field\Type\EntityDeriver.
+ */
+
+namespace Drupal\Core\Entity\Field\Type;
+
+use Drupal\Component\Plugin\Derivative\DerivativeInterface;
+
+/**
+ * Cares about registering data types for each entity type and entity bundle.
+ *
+ * @see \Drupal\Core\Entity\Field\Type\AbstractEntity
+ */
+class EntityDeriver implements DerivativeInterface {
+
+  /**
+   * List of derivative definitions.
+   *
+   * @var array
+   */
+  protected $derivatives = array();
+
+  /**
+   * Implements \Drupal\Component\Plugin\Derivative\DerivativeInterface::getDerivativeDefinition().
+   */
+  public function getDerivativeDefinition($derivative_id, array $base_plugin_definition) {
+    if (!empty($this->derivatives) && !empty($this->derivatives[$derivative_id])) {
+      return $this->derivatives[$derivative_id];
+    }
+    $this->getDerivativeDefinitions($base_plugin_definition);
+    if (isset($this->derivatives[$derivative_id])) {
+      return $this->derivatives[$derivative_id];
+    }
+  }
+
+  /**
+   * Implements \Drupal\Component\Plugin\Derivative\DerivativeInterface::getDerivativeDefinitions().
+   */
+  public function getDerivativeDefinitions(array $base_plugin_definition) {
+    // Also keep the 'entity' defined as is.
+    $this->derivatives[''] = $base_plugin_definition;
+    // Add definitions for each entity type and bundle.
+    foreach (entity_get_info() as $entity_type => $info) {
+      $this->derivatives[$entity_type] = array(
+        'label' => $info['label'],
+        'class' => $info['class'],
+        'constraints' => array('EntityType' => $entity_type),
+      ) + $base_plugin_definition;
+
+      // Incorporate the bundles as entity:$entity_type:$bundle, if any.
+      foreach (entity_get_bundles($entity_type) as $bundle => $bundle_info) {
+        $this->derivatives[$entity_type . ':' . $bundle] = array(
+          'label' => $bundle_info['label'],
+          'class' => $info['class'],
+          'constraints' => array(
+            'EntityType' => $entity_type,
+            'Bundle' => $bundle,
+          ),
+        ) + $base_plugin_definition;
+      }
+    }
+    return $this->derivatives;
+  }
+}
diff --git a/core/lib/Drupal/Core/Entity/Field/Type/EntityReference.php b/core/lib/Drupal/Core/Entity/Field/Type/EntityReference.php
new file mode 100644
index 0000000..d24d76d
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Field/Type/EntityReference.php
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Field\Type\EntityReference.
+ */
+
+namespace Drupal\Core\Entity\Field\Type;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\TypedData\DataReferenceBase;
+use InvalidArgumentException;
+
+/**
+ * Defines an 'entity_reference' data type.
+ *
+ * This serves as 'entity' property of entity reference field items and gets
+ * its value set from the parent, i.e. LanguageItem.
+ *
+ * The plain value of this reference is the entity object, i.e. an instance of
+ * \Drupal\Core\Entity\EntityInterface. For setting the value the entity object
+ * or the entity ID may be passed, whereas passing the ID is only supported if
+ * an 'entity type' constraint is specified.
+ *
+ * Some supported constraints (below the definition's 'constraints' key) are:
+ *  - EntityType: The entity type. Required.
+ *  - Bundle: (optional) The bundle or an array of possible bundles.
+ */
+class EntityReference extends DataReferenceBase {
+
+  /**
+   * The entity ID.
+   *
+   * @var integer|string
+   */
+  protected $id;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTargetDefinition() {
+    $definition = array(
+      'type' => 'entity',
+    );
+    if (isset($this->definition['constraints']['EntityType'])) {
+      $definition['type'] .= ':' . $this->definition['constraints']['EntityType'];
+    }
+    if (isset($this->definition['constraints']['Bundle']) && is_string($this->definition['constraints']['Bundle'])) {
+      $definition['type'] .= ':' . $this->definition['constraints']['Bundle'];
+    }
+    return $definition;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTarget() {
+    if (!isset($this->target)) {
+      // If we have a valid reference, return the entity object which is typed
+      // data itself. If the reference is not valid, use the typed data API to
+      // return an abstract type object so that the metadata is still available.
+      if (isset($this->id)) {
+        $this->target = entity_load($this->definition['constraints']['EntityType'], $this->id);
+      }
+      if (!$this->target) {
+        // @todo: fix entity constructor and abstract type class.
+        $this->target = new Entity($this->getTargetDefinition());
+        //$this->target = typed_data()->create($this->getTargetDefinition(), NULL);
+      }
+    }
+    return $this->target;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTargetIdentifier() {
+    if (isset($this->id)) {
+      return $this->id;
+    }
+    elseif ($entity = $this->getValue()) {
+      return $entity->id();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @return \Drupal\Core\Entity\EntityInterface|null
+   */
+  public function getValue() {
+    // If we have a valid reference, return the entity object, otherwise NULL.
+    $target = $this->getTarget();
+    return !$target->isEmpty() ? $target : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * Both the entity ID and the entity object may be passed as value.
+   */
+  public function setValue($value, $notify = TRUE) {
+    unset($this->target);
+    unset($this->id);
+
+    // Support passing in the entity ID.
+    if (!isset($value) || $value instanceof EntityInterface) {
+      $this->target = $value;
+    }
+    elseif (!is_scalar($value) || empty($this->definition['constraints']['EntityType'])) {
+      throw new InvalidArgumentException('Value is not a valid entity.');
+    }
+    else {
+      $this->id = $value;
+    }
+    // Notify the parent of any changes.
+    if ($notify && isset($this->parent)) {
+      $this->parent->onChange($this->name);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getString() {
+    if ($entity = $this->getValue()) {
+      return $entity->label();
+    }
+    return '';
+  }
+}
diff --git a/core/lib/Drupal/Core/Entity/Field/Type/EntityReferenceItem.php b/core/lib/Drupal/Core/Entity/Field/Type/EntityReferenceItem.php
index 5b78a05..cac3875 100644
--- a/core/lib/Drupal/Core/Entity/Field/Type/EntityReferenceItem.php
+++ b/core/lib/Drupal/Core/Entity/Field/Type/EntityReferenceItem.php
@@ -11,10 +11,13 @@
 use Drupal\Core\TypedData\TypedDataInterface;
 
 /**
- * Defines the 'entity_reference' entity field item.
+ * Defines the 'entity_reference_field' entity field item.
  *
- * Required settings (below the definition's 'settings' key) are:
- *  - target_type: The entity type to reference.
+ * Supported settings (below the definition's 'settings' key) are:
+ * - target_type: The entity type to reference. Required.
+ * - target_bundle: (optional): If set, restricts the entity bundles which may
+ *   may be referenced. May be set to an single bundle, or to an array of
+ *   allowed bundles.
  */
 class EntityReferenceItem extends FieldItemBase {
 
@@ -31,11 +34,11 @@ class EntityReferenceItem extends FieldItemBase {
    * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinitions().
    */
   public function getPropertyDefinitions() {
-    // Definitions vary by entity type, so key them by entity type.
-    $target_type = $this->definition['settings']['target_type'];
+    // Definitions vary by settings, so key them accordingly.
+    $key = implode(',', $this->definition['settings']);
 
-    if (!isset(self::$propertyDefinitions[$target_type])) {
-      static::$propertyDefinitions[$target_type]['target_id'] = array(
+    if (!isset(self::$propertyDefinitions[$key])) {
+      static::$propertyDefinitions[$key]['target_id'] = array(
         // @todo: Lookup the entity type's ID data type and use it here.
         'type' => 'integer',
         'label' => t('Entity ID'),
@@ -43,20 +46,22 @@ public function getPropertyDefinitions() {
           'Range' => array('min' => 0),
         ),
       );
-      static::$propertyDefinitions[$target_type]['entity'] = array(
-        'type' => 'entity',
+      static::$propertyDefinitions[$key]['entity'] = array(
+        'type' => 'entity_reference',
         'constraints' => array(
-          'EntityType' => $target_type,
+          'EntityType' => $this->definition['settings']['target_type'],
         ),
         'label' => t('Entity'),
         'description' => t('The referenced entity'),
         // The entity object is computed out of the entity ID.
         'computed' => TRUE,
         'read-only' => FALSE,
-        'settings' => array('id source' => 'target_id'),
       );
+      if (isset($this->definition['settings']['target_bundle'])) {
+        static::$propertyDefinitions[$key]['entity']['constraints']['Bundle'] = $this->definition['settings']['target_bundle'];
+      }
     }
-    return static::$propertyDefinitions[$target_type];
+    return static::$propertyDefinitions[$key];
   }
 
   /**
@@ -87,15 +92,35 @@ public function __isset($property_name) {
    * Overrides \Drupal\Core\Entity\Field\FieldItemBase::get().
    */
   public function setValue($values, $notify = TRUE) {
-    // Treat the values as property value of the entity property, if no array is
-    // given.
     if (isset($values) && !is_array($values)) {
-      $values = array('entity' => $values);
+      // Directly update the property instead of invoking the parent, so it can
+      // handle objects and IDs.
+      $this->properties['entity']->setValue($values, $notify);
+      // If notify was FALSE, ensure the target_id property gets synched.
+      if (!$notify) {
+        $this->set('target_id', $this->properties['entity']->getTargetIdentifier(), FALSE);
+      }
     }
-    // Make sure that the 'entity' property gets set as 'target_id'.
-    if (isset($values['target_id']) && !isset($values['entity'])) {
-      $values['entity'] = $values['target_id'];
+    else {
+      // Make sure that the 'entity' property gets set as 'target_id'.
+      if (isset($values['target_id']) && !isset($values['entity'])) {
+        $values['entity'] = $values['target_id'];
+      }
+      parent::setValue($values, $notify);
     }
-    parent::setValue($values, $notify);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onChange($property_name) {
+    // Make sure that the target ID and the target property stay in sync.
+    if ($property_name == 'target_id') {
+      $this->properties['entity']->setValue($this->target_id, FALSE);
+    }
+    elseif ($property_name == 'entity') {
+      $this->set('target_id', $this->properties['entity']->getTargetIdentifier(), FALSE);
+    }
+    parent::onChange($property_name);
   }
 }
diff --git a/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php b/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php
index 4dc7657..bfdf497 100644
--- a/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php
+++ b/core/lib/Drupal/Core/Entity/Field/Type/EntityTranslation.php
@@ -73,11 +73,11 @@ public function getValue() {
    * Overrides \Drupal\Core\TypedData\TypedData::setValue().
    */
   public function setValue($values, $notify = TRUE) {
-    // Notify the parent of any changes to be made.
+    $this->fields = $values;
+    // Notify the parent of any changes.
     if ($notify && isset($this->parent)) {
       $this->parent->onChange($this->name);
     }
-    $this->fields = $values;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php b/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php
deleted file mode 100644
index eb6ae7d..0000000
--- a/core/lib/Drupal/Core/Entity/Field/Type/EntityWrapper.php
+++ /dev/null
@@ -1,219 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Core\Entity\Field\Type\EntityWrapper.
- */
-
-namespace Drupal\Core\Entity\Field\Type;
-
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\TypedData\ComplexDataInterface;
-use Drupal\Core\TypedData\TypedData;
-use Drupal\Core\TypedData\TypedDataInterface;
-use ArrayIterator;
-use IteratorAggregate;
-use InvalidArgumentException;
-
-/**
- * Defines an 'entity' data type, e.g. the computed 'entity' property of entity references.
- *
- * This object wraps the regular entity object and implements the
- * ComplexDataInterface by forwarding most of its methods to the wrapped entity
- * (if set).
- *
- * The plain value of this wrapper is the entity object, i.e. an instance of
- * Drupal\Core\Entity\EntityInterface. For setting the value the entity object
- * or the entity ID may be passed, whereas passing the ID is only supported if
- * an 'entity type' constraint is specified.
- *
- * Supported constraints (below the definition's 'constraints' key) are:
- *  - EntityType: The entity type.
- *  - Bundle: The bundle or an array of possible bundles.
- *
- * Supported settings (below the definition's 'settings' key) are:
- *  - id source: If used as computed property, the ID property used to load
- *    the entity object.
- */
-class EntityWrapper extends TypedData implements IteratorAggregate, ComplexDataInterface {
-
-  /**
-   * The referenced entity type.
-   *
-   * @var string
-   */
-  protected $entityType;
-
-  /**
-   * The entity ID if no 'id source' is used.
-   *
-   * @var string
-   */
-  protected $id;
-
-  /**
-   * If set, a new entity to create and reference.
-   *
-   * @var \Drupal\Core\Entity\EntityInterface
-   */
-  protected $newEntity;
-
-  /**
-   * Overrides TypedData::__construct().
-   */
-  public function __construct(array $definition, $name = NULL, TypedDataInterface $parent = NULL) {
-    parent::__construct($definition, $name, $parent);
-    $this->entityType = isset($this->definition['constraints']['EntityType']) ? $this->definition['constraints']['EntityType'] : NULL;
-  }
-
-  /**
-   * Overrides \Drupal\Core\TypedData\TypedData::getValue().
-   */
-  public function getValue() {
-    if (isset($this->newEntity)) {
-      return $this->newEntity;
-    }
-    if (!empty($this->definition['settings']['id source'])) {
-      $this->id = $this->parent->__get($this->definition['settings']['id source']);
-    }
-    return $this->id ? entity_load($this->entityType, $this->id) : NULL;
-  }
-
-  /**
-   * Overrides \Drupal\Core\TypedData\TypedData::setValue().
-   *
-   * Both the entity ID and the entity object may be passed as value.
-   */
-  public function setValue($value, $notify = TRUE) {
-    // Support passing in the entity object. If it's not yet saved we have
-    // to store the whole entity such that it could be saved later on.
-    if ($value instanceof EntityInterface && $value->isNew()) {
-      $this->newEntity = $value;
-      $this->entityType = $value->entityType();
-      $value = FALSE;
-    }
-    elseif ($value instanceof EntityInterface) {
-      $this->entityType = $value->entityType();
-      $value = $value->id();
-      unset($this->newEntity);
-    }
-    elseif (isset($value) && !(is_scalar($value) && !empty($this->definition['constraints']['EntityType']))) {
-      throw new InvalidArgumentException('Value is not a valid entity.');
-    }
-    // Update the 'id source' property, if given.
-    if (!empty($this->definition['settings']['id source'])) {
-      $this->parent->__set($this->definition['settings']['id source'], $value, $notify);
-    }
-    else {
-      // Notify the parent of any changes to be made.
-      if ($notify && isset($this->parent)) {
-        $this->parent->onChange($this->name);
-      }
-      $this->id = $value;
-    }
-  }
-
-  /**
-   * Overrides \Drupal\Core\TypedData\TypedData::getString().
-   */
-  public function getString() {
-    if ($entity = $this->getValue()) {
-      return $entity->label();
-    }
-    return '';
-  }
-
-  /**
-   * Implements \IteratorAggregate::getIterator().
-   */
-  public function getIterator() {
-    if ($entity = $this->getValue()) {
-      return $entity->getIterator();
-    }
-    return new ArrayIterator(array());
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::get().
-   */
-  public function get($property_name) {
-    // @todo: Allow navigating through the tree without data as well.
-    if ($entity = $this->getValue()) {
-      return $entity->get($property_name);
-    }
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::set().
-   */
-  public function set($property_name, $value, $notify = TRUE) {
-    $this->get($property_name)->setValue($value, FALSE);
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getProperties().
-   */
-  public function getProperties($include_computed = FALSE) {
-    if ($entity = $this->getValue()) {
-      return $entity->getProperties($include_computed);
-    }
-    return array();
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinition().
-   */
-  public function getPropertyDefinition($name) {
-    $definitions = $this->getPropertyDefinitions();
-    if (isset($definitions[$name])) {
-      return $definitions[$name];
-    }
-    else {
-      return FALSE;
-    }
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyDefinitions().
-   */
-  public function getPropertyDefinitions() {
-    // @todo: Support getting definitions if multiple bundles are specified.
-    return \Drupal::entityManager()->getStorageController($this->entityType)->getFieldDefinitions($this->definition['constraints']);
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::getPropertyValues().
-   */
-  public function getPropertyValues() {
-    if ($entity = $this->getValue()) {
-      return $entity->getPropertyValues();
-    }
-    return array();
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::setPropertyValues().
-   */
-  public function setPropertyValues($values) {
-    if ($entity = $this->getValue()) {
-      $entity->setPropertyValues($values);
-    }
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::isEmpty().
-   */
-  public function isEmpty() {
-    return !$this->getValue();
-  }
-
-  /**
-   * Implements \Drupal\Core\TypedData\ComplexDataInterface::onChange().
-   */
-  public function onChange($property_name) {
-    // Notify the parent of changes.
-    if (isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
-  }
-}
diff --git a/core/lib/Drupal/Core/Entity/Field/Type/Field.php b/core/lib/Drupal/Core/Entity/Field/Type/Field.php
index 5d0a4ff..58f3ffc 100644
--- a/core/lib/Drupal/Core/Entity/Field/Type/Field.php
+++ b/core/lib/Drupal/Core/Entity/Field/Type/Field.php
@@ -67,10 +67,6 @@ public function getValue() {
    * Overrides \Drupal\Core\TypedData\ItemList::setValue().
    */
   public function setValue($values, $notify = TRUE) {
-    // Notify the parent of any changes to be made.
-    if ($notify && isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
     if (!isset($values) || $values === array()) {
       $this->list = $values;
     }
@@ -98,6 +94,10 @@ public function setValue($values, $notify = TRUE) {
         }
       }
     }
+    // Notify the parent of any changes.
+    if ($notify && isset($this->parent)) {
+      $this->parent->onChange($this->name);
+    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/Field/Type/LanguageItem.php b/core/lib/Drupal/Core/Entity/Field/Type/LanguageItem.php
index 2db12e1..9d286ca 100644
--- a/core/lib/Drupal/Core/Entity/Field/Type/LanguageItem.php
+++ b/core/lib/Drupal/Core/Entity/Field/Type/LanguageItem.php
@@ -35,12 +35,13 @@ public function getPropertyDefinitions() {
         'label' => t('Language code'),
       );
       static::$propertyDefinitions['language'] = array(
-        'type' => 'language',
+        'type' => 'language_reference',
         'label' => t('Language object'),
+        'description' => t('The referenced language'),
         // The language object is retrieved via the language code.
         'computed' => TRUE,
         'read-only' => FALSE,
-        'settings' => array('langcode source' => 'value'),
+        'settings' => array('source' => 'value'),
       );
     }
     return static::$propertyDefinitions;
@@ -50,15 +51,35 @@ public function getPropertyDefinitions() {
    * Overrides \Drupal\Core\Entity\Field\FieldItemBase::get().
    */
   public function setValue($values, $notify = TRUE) {
-    // Treat the values as property value of the language property, if no array
-    // is given.
     if (isset($values) && !is_array($values)) {
-      $values = array('language' => $values);
+      // Directly update the property instead of invoking the parent, so it can
+      // handle objects and IDs.
+      $this->properties['language']->setValue($values, $notify);
+      // If notify was FALSE, ensure the value property gets synched.
+      if (!$notify) {
+        $this->set('value', $this->properties['language']->getTargetIdentifier(), FALSE);
+      }
     }
-    // Make sure that the 'language' property gets set as 'value'.
-    if (isset($values['value']) && !isset($values['language'])) {
-      $values['language'] = $values['value'];
+    else {
+      // Make sure that the 'language' property gets set as 'value'.
+      if (isset($values['value']) && !isset($values['language'])) {
+        $values['language'] = $values['value'];
+      }
+      parent::setValue($values, $notify);
     }
-    parent::setValue($values, $notify);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function onChange($property_name) {
+    // Make sure that the value and the language property stay in sync.
+    if ($property_name == 'value') {
+      $this->properties['language']->setValue($this->value, FALSE);
+    }
+    elseif ($property_name == 'language') {
+      $this->set('value', $this->properties['language']->getTargetIdentifier(), FALSE);
+    }
+    parent::onChange($property_name);
   }
 }
diff --git a/core/lib/Drupal/Core/Entity/Field/Type/LanguageReference.php b/core/lib/Drupal/Core/Entity/Field/Type/LanguageReference.php
new file mode 100644
index 0000000..fdae57d
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Field/Type/LanguageReference.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Field\Type\LanguageReference.
+ */
+
+namespace Drupal\Core\Entity\Field\Type;
+
+use Drupal\Core\TypedData\DataReferenceBase;
+
+/**
+ * Defines the 'language_reference' data type.
+ *
+ * This serves as 'language' property of entity reference field items and gets
+ * its value set from the parent, i.e. LanguageItem.
+ *
+ * The plain value is the language object, i.e. an instance of
+ * \Drupal\Core\Language\Language. For setting the value the language object or
+ * the language code as string may be passed.
+ */
+class LanguageReference extends DataReferenceBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTargetDefinition() {
+    return array(
+      'type' => 'language',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTargetIdentifier() {
+    if ($language = $this->getValue()) {
+      return $language->langcode;
+    }
+  }
+}
diff --git a/core/lib/Drupal/Core/TypedData/DataDefinitionException.php b/core/lib/Drupal/Core/TypedData/DataDefinitionException.php
new file mode 100644
index 0000000..02178a3
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/DataDefinitionException.php
@@ -0,0 +1,17 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\TypedData\MissingContextException.
+ */
+
+namespace Drupal\Core\TypedData;
+
+use Exception;
+
+/**
+ * Exception thrown when a data definition lacks required information.
+ */
+class DataDefinitionException extends Exception {
+
+}
diff --git a/core/lib/Drupal/Core/TypedData/DataReferenceBase.php b/core/lib/Drupal/Core/TypedData/DataReferenceBase.php
new file mode 100644
index 0000000..38fdf88
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/DataReferenceBase.php
@@ -0,0 +1,57 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\TypedData\DataReferenceBase.
+ */
+
+namespace Drupal\Core\TypedData;
+
+/**
+ * Base class for typed data references.
+ *
+ * Implementing classes have to implement at least
+ * \Drupal\Core\TypedData\DataReferenceInterface::getTargetDefinition() and
+ * \Drupal\Core\TypedData\DataReferenceInterface::getTargetIdentifier().
+ */
+abstract class DataReferenceBase extends TypedData implements DataReferenceInterface  {
+
+  /**
+   * The referenced data.
+   *
+   * @var \Drupal\Core\TypedData\TypedDataInterface
+   */
+  protected $target;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getTarget() {
+    return $this->target;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getValue() {
+    return $this->getTarget()->getValue();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setValue($value, $notify = TRUE) {
+    $this->target = typed_data()->create($this->getTargetDefinition(), $value);
+    // Notify the parent of any changes.
+    if ($notify && isset($this->parent)) {
+      $this->parent->onChange($this->name);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getString() {
+    return (string) $this->getType() . ':' . $this->getTargetIdentifier();
+  }
+}
diff --git a/core/lib/Drupal/Core/TypedData/DataReferenceInterface.php b/core/lib/Drupal/Core/TypedData/DataReferenceInterface.php
new file mode 100644
index 0000000..f5259d9
--- /dev/null
+++ b/core/lib/Drupal/Core/TypedData/DataReferenceInterface.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\TypedData\DataReferenceInterface.
+ */
+
+namespace Drupal\Core\TypedData;
+
+/**
+ * Interface for typed data references.
+ */
+interface DataReferenceInterface  {
+
+  /**
+   * Gets the data definition of the referenced data.
+   *
+   * @return array
+   *   The data definition of the referenced data.
+   */
+  public function getTargetDefinition();
+
+  /**
+   * Gets the referenced data.
+   *
+   * @return \Drupal\Core\TypedData\TypedDataInterface
+   *   The referenced typed data object.
+   */
+  public function getTarget();
+
+  /**
+   * Gets the identifier of the referenced data.
+   *
+   * @return int|string
+   *   The identifier of the referenced data.
+   */
+  public function getTargetIdentifier();
+}
diff --git a/core/lib/Drupal/Core/TypedData/ItemList.php b/core/lib/Drupal/Core/TypedData/ItemList.php
index 88af366..cfc5655 100644
--- a/core/lib/Drupal/Core/TypedData/ItemList.php
+++ b/core/lib/Drupal/Core/TypedData/ItemList.php
@@ -42,10 +42,6 @@ public function getValue() {
    *   An array of values of the field items, or NULL to unset the field.
    */
   public function setValue($values, $notify = TRUE) {
-    // Notify the parent of any changes to be made.
-    if ($notify && isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
     if (!isset($values) || $values === array()) {
       $this->list = $values;
     }
@@ -72,6 +68,10 @@ public function setValue($values, $notify = TRUE) {
         }
       }
     }
+    // Notify the parent of any changes.
+    if ($notify && isset($this->parent)) {
+      $this->parent->onChange($this->name);
+    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/TypedData/Type/Binary.php b/core/lib/Drupal/Core/TypedData/Type/Binary.php
index f9c4985..b58a8e2 100644
--- a/core/lib/Drupal/Core/TypedData/Type/Binary.php
+++ b/core/lib/Drupal/Core/TypedData/Type/Binary.php
@@ -51,10 +51,6 @@ public function getValue() {
    * Supports a PHP file resource or a (absolute) stream resource URI as value.
    */
   public function setValue($value, $notify = TRUE) {
-    // Notify the parent of any changes to be made.
-    if ($notify && isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
     if (!isset($value)) {
       $this->handle = NULL;
       $this->uri = NULL;
@@ -68,6 +64,10 @@ public function setValue($value, $notify = TRUE) {
     else {
       $this->handle = $value;
     }
+    // Notify the parent of any changes.
+    if ($notify && isset($this->parent)) {
+      $this->parent->onChange($this->name);
+    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/TypedData/Type/Date.php b/core/lib/Drupal/Core/TypedData/Type/Date.php
index c9b8c6b..20e27fe 100644
--- a/core/lib/Drupal/Core/TypedData/Type/Date.php
+++ b/core/lib/Drupal/Core/TypedData/Type/Date.php
@@ -32,10 +32,6 @@ class Date extends TypedData {
    * Overrides TypedData::setValue().
    */
   public function setValue($value, $notify = TRUE) {
-    // Notify the parent of any changes to be made.
-    if ($notify && isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
     // Don't try to create a date from an empty value.
     // It would default to the current time.
     if (!isset($value)) {
@@ -44,5 +40,9 @@ public function setValue($value, $notify = TRUE) {
     else {
       $this->value = $value instanceOf DrupalDateTime ? $value : new DrupalDateTime($value);
     }
+    // Notify the parent of any changes.
+    if ($notify && isset($this->parent)) {
+      $this->parent->onChange($this->name);
+    }
   }
 }
diff --git a/core/lib/Drupal/Core/TypedData/Type/Duration.php b/core/lib/Drupal/Core/TypedData/Type/Duration.php
index 52ba97d..64e1f4a 100644
--- a/core/lib/Drupal/Core/TypedData/Type/Duration.php
+++ b/core/lib/Drupal/Core/TypedData/Type/Duration.php
@@ -32,10 +32,6 @@ class Duration extends TypedData {
    * Overrides TypedData::setValue().
    */
   public function setValue($value, $notify = TRUE) {
-    // Notify the parent of any changes to be made.
-    if ($notify && isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
     // Catch any exceptions thrown due to invalid values being passed.
     try {
       if ($value instanceof DateInterval || !isset($value)) {
@@ -61,6 +57,10 @@ public function setValue($value, $notify = TRUE) {
       // validation fail.
       $this->value = $e;
     }
+    // Notify the parent of any changes.
+    if ($notify && isset($this->parent)) {
+      $this->parent->onChange($this->name);
+    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/TypedData/Type/Language.php b/core/lib/Drupal/Core/TypedData/Type/Language.php
index b910216..1b3a2af 100644
--- a/core/lib/Drupal/Core/TypedData/Type/Language.php
+++ b/core/lib/Drupal/Core/TypedData/Type/Language.php
@@ -8,7 +8,6 @@
 namespace Drupal\Core\TypedData\Type;
 
 use InvalidArgumentException;
-use Drupal\Core\Language\Language as LanguageObject;
 use Drupal\Core\TypedData\TypedData;
 
 /**
@@ -17,13 +16,6 @@
  * The plain value of a language is the language object, i.e. an instance of
  * \Drupal\Core\Language\Language. For setting the value the language object or
  * the language code as string may be passed.
- *
- * Optionally, this class may be used as computed property, see the supported
- * settings below. E.g., it is used as 'language' property of language items.
- *
- * Supported settings (below the definition's 'settings' key) are:
- *  - langcode source: If used as computed property, the langcode property used
- *    to load the language object.
  */
 class Language extends TypedData {
 
@@ -35,16 +27,20 @@ class Language extends TypedData {
   protected $langcode;
 
   /**
+   * @var \Drupal\Core\Language
+   */
+  protected $language;
+
+  /**
    * Overrides TypedData::getValue().
+   *
+   * @return \Drupal\Core\Language\Language|null
    */
   public function getValue() {
-    if (!empty($this->definition['settings']['langcode source'])) {
-      $this->langcode = $this->parent->__get($this->definition['settings']['langcode source']);
-    }
-   if ($this->langcode) {
-      $language = language_load($this->langcode);
-      return $language ?: new LanguageObject(array('langcode' => $this->langcode));
+    if (!isset($this->language) && $this->langcode) {
+      $this->language = language_load($this->langcode);
     }
+    return $this->language;
   }
 
   /**
@@ -55,22 +51,18 @@ public function getValue() {
   public function setValue($value, $notify = TRUE) {
     // Support passing language objects.
     if (is_object($value)) {
-      $value = $value->langcode;
+      $this->language = $value;
     }
     elseif (isset($value) && !is_scalar($value)) {
       throw new InvalidArgumentException('Value is no valid langcode or language object.');
     }
-    // Update the 'langcode source' property, if given.
-    if (!empty($this->definition['settings']['langcode source'])) {
-      $this->parent->__set($this->definition['settings']['langcode source'], $value, $notify);
-    }
     else {
-      // Notify the parent of any changes to be made.
-      if ($notify && isset($this->parent)) {
-        $this->parent->onChange($this->name);
-      }
       $this->langcode = $value;
     }
+    // Notify the parent of any changes.
+    if ($notify && isset($this->parent)) {
+      $this->parent->onChange($this->name);
+    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/TypedData/Type/Map.php b/core/lib/Drupal/Core/TypedData/Type/Map.php
index da20e96..7bf535f 100644
--- a/core/lib/Drupal/Core/TypedData/Type/Map.php
+++ b/core/lib/Drupal/Core/TypedData/Type/Map.php
@@ -78,10 +78,6 @@ public function setValue($values, $notify = TRUE) {
     if (isset($values) && !is_array($values)) {
       throw new \InvalidArgumentException("Invalid values given. Values must be represented as an associative array.");
     }
-    // Notify the parent of any changes to be made.
-    if ($notify && isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
     $this->values = $values;
 
     // Update any existing property objects.
@@ -92,6 +88,10 @@ public function setValue($values, $notify = TRUE) {
       }
       $property->setValue($value, FALSE);
     }
+    // Notify the parent of any changes.
+    if ($notify && isset($this->parent)) {
+      $this->parent->onChange($this->name);
+    }
   }
 
   /**
@@ -125,16 +125,16 @@ public function get($property_name) {
    * Implements \Drupal\Core\TypedData\ComplexDataInterface::set().
    */
   public function set($property_name, $value, $notify = TRUE) {
-    // Notify the parent of any changes to be made.
-    if ($notify && isset($this->parent)) {
-      $this->parent->onChange($this->name);
-    }
     if ($this->getPropertyDefinition($property_name)) {
-      $this->get($property_name)->setValue($value);
+      $this->get($property_name)->setValue($value, $notify);
     }
     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);
+      }
     }
   }
 
diff --git a/core/lib/Drupal/Core/TypedData/TypedData.php b/core/lib/Drupal/Core/TypedData/TypedData.php
index e9558f3..6930417 100644
--- a/core/lib/Drupal/Core/TypedData/TypedData.php
+++ b/core/lib/Drupal/Core/TypedData/TypedData.php
@@ -81,11 +81,11 @@ public function getValue() {
    * Implements \Drupal\Core\TypedData\TypedDataInterface::setValue().
    */
   public function setValue($value, $notify = TRUE) {
-    // Notify the parent of any changes to be made.
+    $this->value = $value;
+    // Notify the parent of any changes.
     if ($notify && isset($this->parent)) {
       $this->parent->onChange($this->name);
     }
-    $this->value = $value;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/TypedData/TypedDataManager.php b/core/lib/Drupal/Core/TypedData/TypedDataManager.php
index 195e32d..b08bff9 100644
--- a/core/lib/Drupal/Core/TypedData/TypedDataManager.php
+++ b/core/lib/Drupal/Core/TypedData/TypedDataManager.php
@@ -10,6 +10,7 @@
 use InvalidArgumentException;
 use Drupal\Component\Plugin\Discovery\ProcessDecorator;
 use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Component\Plugin\Discovery\DerivativeDiscoveryDecorator;
 use Drupal\Core\Plugin\Discovery\CacheDecorator;
 use Drupal\Core\Plugin\Discovery\HookDiscovery;
 use Drupal\Core\TypedData\Validation\MetadataFactory;
@@ -56,10 +57,7 @@ class TypedDataManager extends PluginManagerBase {
   protected $prototypes = array();
 
   public function __construct() {
-    $this->discovery = new HookDiscovery('data_type_info');
-    $this->discovery = new ProcessDecorator($this->discovery, array($this, 'processDefinition'));
-    $this->discovery = new CacheDecorator($this->discovery, 'typed_data:types');
-
+    $this->discovery = new CacheDecorator(new DerivativeDiscoveryDecorator(new HookDiscovery('data_type_info')), 'typed_data:types');
     $this->factory = new TypedDataFactory($this->discovery);
   }
 
@@ -208,18 +206,16 @@ public function getInstance(array $options) {
    * @todo: Add type-hinting to $object once entities implement the
    *   TypedDataInterface.
    */
-  public function getPropertyInstance($object, $property_name, $value = NULL) {
-    if ($root = $object->getRoot()) {
-      $key = $root->getType() . ':' . $object->getPropertyPath() . '.';
-      // If we are creating list items, we always use 0 in the key as all list
-      // items look the same.
-      $key .= is_numeric($property_name) ? 0 : $property_name;
-    }
-    else {
-      // Missing context, thus we cannot determine a unique key for prototyping.
-      // Fall back to create a new prototype on each call.
-      $key = FALSE;
+  public function getPropertyInstance(TypedDataInterface $object, $property_name, $value = NULL) {
+    $definition = $object->getRoot()->getDefinition();
+    $key = $definition['type'];
+    if (isset($definition['settings'])) {
+      $key .= ':' . implode(',', $definition['settings']);
     }
+    $key .= ':' . $object->getPropertyPath() . '.';
+    // If we are creating list items, we always use 0 in the key as all list
+    // items look the same.
+    $key .= is_numeric($property_name) ? 0 : $property_name;
 
     // Make sure we have a prototype. Then, clone the prototype and set object
     // specific values, i.e. the value and the context.
diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/BundleConstraint.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/BundleConstraint.php
index f6ea289..ebd844b 100644
--- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/BundleConstraint.php
+++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/BundleConstraint.php
@@ -20,7 +20,7 @@
  * @Plugin(
  *   id = "Bundle",
  *   label = @Translation("Bundle", context = "Validation"),
- *   type = "entity"
+ *   type = { "entity", "entity_reference" }
  * )
  */
 class BundleConstraint extends Constraint {
diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/BundleConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/BundleConstraintValidator.php
index a84251e..42f09ca 100644
--- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/BundleConstraintValidator.php
+++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/BundleConstraintValidator.php
@@ -18,10 +18,7 @@ class BundleConstraintValidator extends ConstraintValidator {
   /**
    * Implements \Symfony\Component\Validator\ConstraintValidatorInterface::validate().
    */
-  public function validate($typed_data, Constraint $constraint) {
-    // If the entity is contained in a reference, unwrap it first.
-    $entity = isset($typed_data) && !($typed_data instanceof EntityInterface) ? $typed_data->getValue() : FALSE;
-
+  public function validate($entity, Constraint $constraint) {
     if (!empty($entity) && !in_array($entity->bundle(), $constraint->getBundleOption())) {
       $this->context->addViolation($constraint->message, array('%bundle', implode(', ', $constraint->getBundleOption())));
     }
diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/EntityTypeConstraint.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/EntityTypeConstraint.php
index 1d38ae5..d480fa1 100644
--- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/EntityTypeConstraint.php
+++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/EntityTypeConstraint.php
@@ -20,7 +20,7 @@
  * @Plugin(
  *   id = "EntityType",
  *   label = @Translation("Entity type", context = "Validation"),
- *   type = "entity"
+ *   type = { "entity", "entity_reference" }
  * )
  */
 class EntityTypeConstraint extends Constraint {
diff --git a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php
index 31c7cb2..304a85c 100644
--- a/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php
+++ b/core/lib/Drupal/Core/Validation/Plugin/Validation/Constraint/EntityTypeConstraintValidator.php
@@ -19,9 +19,7 @@ class EntityTypeConstraintValidator extends ConstraintValidator {
   /**
    * Implements \Symfony\Component\Validator\ConstraintValidatorInterface::validate().
    */
-  public function validate($typed_data, Constraint $constraint) {
-    // If the entity is contained in a reference, unwrap it first.
-    $entity = isset($typed_data) && !($typed_data instanceof EntityInterface) ? $typed_data->getValue() : FALSE;
+  public function validate($entity, Constraint $constraint) {
 
     if (!empty($entity) && $entity->entityType() != $constraint->type) {
       $this->context->addViolation($constraint->message, array('%type' => $constraint->type));
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 7ba5d23..0e50bc3 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -2518,7 +2518,7 @@ function node_access($op, $node, $account = NULL, $langcode = NULL) {
 
   // If no language code was provided, default to the node's langcode.
   if (empty($langcode)) {
-    $langcode = $node->langcode;
+    $langcode = $node->language()->langcode;
     // If the Language module is enabled, try to use the language from content
     // negotiation.
     if (module_exists('language')) {
diff --git a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php
index fabbcfe..a16ebae 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Entity/EntityFieldTest.php
@@ -377,7 +377,7 @@ protected function assertIntrospection($entity_type) {
 
     $userref_properties = $entity->user_id->getPropertyDefinitions();
     $this->assertEqual($userref_properties['target_id']['type'], 'integer', $entity_type .': Entity id property of the user found.');
-    $this->assertEqual($userref_properties['entity']['type'], 'entity', $entity_type .': Entity reference property of the user found.');
+    $this->assertEqual($userref_properties['entity']['type'], 'entity_reference', $entity_type .': Entity reference property of the user found.');
 
     $textfield_properties = $entity->field_test_text->getPropertyDefinitions();
     $this->assertEqual($textfield_properties['value']['type'], 'string', $entity_type .': String value property of the test-text field found.');
@@ -469,21 +469,15 @@ public function testDataStructureInterfaces() {
    */
   protected function assertDataStructureInterfaces($entity_type) {
     $entity = $this->createTestEntity($entity_type);
-    $entity->save();
-    $entity_definition = array(
-      'type' => 'entity',
-      'constraints' => array(
-        'EntityType' => $entity_type,
-      ),
-      'label' => 'Test entity',
-    );
-    $wrapped_entity = typed_data()->create($entity_definition, $entity);
 
     // Test using the whole tree of typed data by navigating through the tree of
     // contained properties and getting all contained strings, limited by a
     // certain depth.
     $strings = array();
-    $this->getContainedStrings($wrapped_entity, 0, $strings);
+    // @todo: Make the Entity class implement the TypedDataInterface.
+    return;
+
+    $this->getContainedStrings($entity, 0, $strings);
 
     // @todo: Once the user entity has defined properties this should contain
     // the user name and other user entity strings as well.
@@ -531,17 +525,20 @@ public function getContainedStrings(TypedDataInterface $wrapper, $depth, array &
   public function testEntityConstraintValidation() {
     $entity = $this->createTestEntity('entity_test');
     $entity->save();
-    $entity_definition = array(
-      'type' => 'entity',
-      'constraints' => array(
-        'EntityType' => 'entity_test',
+    // Create a reference field item and let it reference the entity.
+    $definition = array(
+      'type' => 'entity_reference_field',
+      'settings' => array(
+        'target_type' => 'entity_test',
       ),
       'label' => 'Test entity',
     );
-    $wrapped_entity = typed_data()->create($entity_definition, $entity);
+    $reference_field_item = typed_data()->create($definition);
+    $reference = $reference_field_item->get('entity');
+    $reference->setValue($entity);
 
     // Test validation the typed data object.
-    $violations = $wrapped_entity->validate();
+    $violations = $reference->validate();
     $this->assertEqual($violations->count(), 0);
 
     // Test validating an entity of the wrong type.
@@ -551,30 +548,31 @@ public function testEntityConstraintValidation() {
       'type' => 'page',
       'uid' => $user->id(),
     ));
-    // @todo: EntityWrapper can only handle entities with an id.
-    $node->save();
-    $wrapped_entity->setValue($node);
-    $violations = $wrapped_entity->validate();
+    $reference->setValue($node);
+    $violations = $reference->validate();
     $this->assertEqual($violations->count(), 1);
 
     // Test bundle validation.
-    $entity_definition = array(
-      'type' => 'entity',
-      'constraints' => array(
-        'EntityType' => 'node',
-        'Bundle' => 'article',
+    $definition = array(
+      'type' => 'entity_reference_field',
+      'settings' => array(
+        'target_type' => 'node',
+        'target_bundle' => 'article',
       ),
-      'label' => 'Test node',
     );
-    $wrapped_entity = typed_data()->create($entity_definition, $node);
-
-    $violations = $wrapped_entity->validate();
+    $reference_field_item = typed_data()->create($definition);
+    $reference = $reference_field_item->get('entity');
+    $reference->setValue($node);
+    $violations = $reference->validate();
     $this->assertEqual($violations->count(), 1);
 
-    $node->type = 'article';
+    $node = entity_create('node', array(
+      'type' => 'article',
+      'uid' => $user->id(),
+    ));
     $node->save();
-    $wrapped_entity->setValue($node);
-    $violations = $wrapped_entity->validate();
+    $reference->setValue($node);
+    $violations = $reference->validate();
     $this->assertEqual($violations->count(), 0);
   }
 
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 614cec7..abc3355 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -2323,16 +2323,25 @@ function system_data_type_info() {
       'description' => t('A language object.'),
       'class' => '\Drupal\Core\TypedData\Type\Language',
     ),
+    'language_reference' => array(
+      'label' => t('Language reference'),
+      'class' => '\Drupal\Core\Entity\Field\Type\LanguageReference',
+    ),
     'entity' => array(
       'label' => t('Entity'),
       'description' => t('All kind of entities, e.g. nodes, comments or users.'),
-      'class' => '\Drupal\Core\Entity\Field\Type\EntityWrapper',
+      'class' => '\Drupal\Core\Entity\Field\Type\Entity',
+      'derivative' => '\Drupal\Core\Entity\Field\Type\EntityDeriver'
     ),
     'entity_translation' => array(
       'label' => t('Entity translation'),
       'description' => t('A translation of an entity'),
       'class' => '\Drupal\Core\Entity\Field\Type\EntityTranslation',
     ),
+    'entity_reference' => array(
+      'label' => t('Entity reference'),
+      'class' => '\Drupal\Core\Entity\Field\Type\EntityReference',
+    ),
     'boolean_field' => array(
       'label' => t('Boolean field item'),
       'description' => t('An entity field containing a boolean value.'),
