diff --git a/.htaccess b/.htaccess
index 5ca7305..faa3a80 100644
--- a/.htaccess
+++ b/.htaccess
@@ -110,7 +110,7 @@ DirectoryIndex index.php index.html index.htm
   #
   # If your site is running in a VirtualDocumentRoot at http://example.com/,
   # uncomment the following line:
-  # RewriteBase /
+  RewriteBase /
 
   # Redirect common PHP files to their new locations.
   RewriteCond %{REQUEST_URI} ^(.*)?/(update.php) [OR]
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityBase.php b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
index 2ece2b5..f3a8f78 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityBase.php
@@ -158,7 +158,14 @@ public function __construct(array $values, $entity_type, $bundle = FALSE, $trans
     foreach ($this->getEntityType()->getKeys() as $key => $field_name) {
       if (isset($this->values[$field_name])) {
         if (is_array($this->values[$field_name]) && isset($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT])) {
-          $this->entityKeys[$key] = $this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT];
+          if (is_array($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT])) {
+            if (isset($this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT][0]['value'])) {
+              $this->entityKeys[$key] = $this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT][0]['value'];
+            }
+          }
+          else {
+            $this->entityKeys[$key] = $this->values[$field_name][LanguageInterface::LANGCODE_DEFAULT];
+          }
         }
       }
     }
@@ -563,6 +570,11 @@ public function language() {
       $language = $this->languages[$this->activeLangcode];
     }
     else {
+      // @todo Avoid this check by getting the language from the language
+      //   manager directly.
+      if (!isset($this->languages[$this->defaultLangcode])) {
+        $this->languages += $this->languageManager()->getLanguages(LanguageInterface::STATE_ALL);
+      }
       $language = $this->languages[$this->defaultLangcode];
     }
     return $language;
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php
index 6f246f7..ca4d862 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php
@@ -8,6 +8,7 @@
 namespace Drupal\Core\Entity;
 
 use Drupal\Component\Utility\String;
+use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Database\Database;
 use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
@@ -112,13 +113,21 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase implements S
   protected $schemaHandler;
 
   /**
+   * Cache backend.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface
+   */
+  protected $cacheBackend;
+
+  /**
    * {@inheritdoc}
    */
   public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
     return new static(
       $entity_type,
       $container->get('database'),
-      $container->get('entity.manager')
+      $container->get('entity.manager'),
+      $container->get('cache.entity')
     );
   }
 
@@ -142,12 +151,15 @@ public function getFieldStorageDefinitions() {
    *   The database connection to be used.
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   The cache backend to be used.
    */
-  public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager) {
+  public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager, CacheBackendInterface $cache) {
     parent::__construct($entity_type);
 
     $this->database = $database;
     $this->entityManager = $entity_manager;
+    $this->cacheBackend = $cache;
 
     // @todo Remove table names from the entity type definition in
     //   https://drupal.org/node/2232465
@@ -342,13 +354,158 @@ public function getTableMapping() {
    * {@inheritdoc}
    */
   protected function doLoadMultiple(array $ids = NULL) {
-    // Build and execute the query.
-    $records = $this
-      ->buildQuery($ids)
-      ->execute()
-      ->fetchAllAssoc($this->idKey);
+    // Attempt to load entities from the persistent cache. This will remove IDs
+    // that were loaded from $ids.
+    $entities_from_cache = $this->getFromPersistentCache($ids);
 
-    return $this->mapFromStorageRecords($records);
+    // Load any remaining entities from the database.
+    $entities_from_storage = $this->getFromStorage($ids);
+    $this->setPersistentCache($entities_from_storage);
+
+    return $entities_from_cache + $entities_from_storage;
+  }
+
+  /**
+   * Gets entities from the storage.
+   *
+   * @param array|null $ids
+   *   If not empty, return entities that match these IDs. Return all entities
+   *   when NULL.
+   *
+   * @return \Drupal\Core\Entity\ContentEntityInterface[]
+   *   Array of entities from the storage.
+   */
+  protected function getFromStorage(array $ids = NULL) {
+    $entities = array();
+
+    if ($ids === NULL || $ids) {
+      // Build and execute the query.
+      $query_result = $this->buildQuery($ids)->execute();
+      $records = $query_result->fetchAllAssoc($this->idKey);
+
+      // Map the loaded records into entity objects and according fields.
+      if ($records) {
+        $entities = $this->mapFromStorageRecords($records);
+
+        // Call hook_entity_storage_load().
+        foreach ($this->moduleHandler()->getImplementations('entity_storage_load') as $module) {
+          $function = $module . '_entity_storage_load';
+          $function($entities, $this->entityTypeId);
+        }
+        // Call hook_TYPE_storage_load().
+        foreach ($this->moduleHandler()->getImplementations($this->entityTypeId . '_storage_load') as $module) {
+          $function = $module . '_' . $this->entityTypeId . '_storage_load';
+          $function($entities);
+        }
+      }
+    }
+
+    return $entities;
+  }
+
+  /**
+   * Gets entities from the persistent cache backend.
+   *
+   * @param array|null &$ids
+   *   If not empty, return entities that match these IDs. IDs that were found
+   *   will be removed from the list.
+   *
+   * @return \Drupal\Core\Entity\ContentEntityInterface[]
+   *   Array of entities from the persistent cache.
+   */
+  protected function getFromPersistentCache(array &$ids = NULL) {
+    if (!$this->entityType->isPersistentlyCacheable() || empty($ids)) {
+      return array();
+    }
+    $entities = array();
+    // Build the list of cache entries to retrieve.
+    $cids = array();
+    foreach ($ids as $id) {
+      $cids[] = $this->buildCacheId($id);
+    }
+    if ($cache = $this->cacheBackend->getMultiple($cids)) {
+      // Get the entities that were found in the cache.
+      foreach ($ids as $index => $id) {
+        $cid = $this->buildCacheId($id);
+        if (isset($cache[$cid])) {
+          $entities[$id] = $cache[$cid]->data;
+          unset($ids[$index]);
+        }
+      }
+    }
+    return $entities;
+  }
+
+  /**
+   * Stores entities in the persistent cache backend.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
+   *   Entities to store in the cache.
+   */
+  protected function setPersistentCache($entities) {
+    if (!$this->entityType->isPersistentlyCacheable()) {
+      return;
+    }
+
+    foreach ($entities as $id => $entity) {
+      $this->cacheBackend->set($this->buildCacheId($id), $entity, CacheBackendInterface::CACHE_PERMANENT, array($this->entityTypeId . '_values' => TRUE, 'entity_field_info' => TRUE));
+    }
+  }
+
+  /**
+   * Invokes hook_entity_load_uncached().
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
+   *   List of entities, keyed on the entity ID.
+   */
+  protected function invokeLoadUncachedHook(array &$entities) {
+    if (!empty($entities)) {
+      // Call hook_entity_load_uncached().
+      foreach ($this->moduleHandler()->getImplementations('entity_load_uncached') as $module) {
+        $function = $module . '_entity_load_uncached';
+        $function($entities, $this->entityTypeId);
+      }
+      // Call hook_TYPE_load_uncached().
+      foreach ($this->moduleHandler()->getImplementations($this->entityTypeId . '_load_uncached') as $module) {
+        $function = $module . '_' . $this->entityTypeId . '_load_uncached';
+        $function($entities);
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function resetCache(array $ids = NULL) {
+    if ($ids) {
+      $cids = array();
+      foreach ($ids as $id) {
+        unset($this->entities[$id]);
+        $cids[] = $this->buildCacheId($id);
+      }
+      if ($this->entityType->isPersistentlyCacheable()) {
+        $this->cacheBackend->deleteMultiple($cids);
+      }
+    }
+    else {
+      $this->entities = array();
+      if ($this->entityType->isPersistentlyCacheable()) {
+        $this->cacheBackend->deleteTags(array($this->entityTypeId . '_values' => TRUE));
+      }
+    }
+  }
+
+  /**
+   * Returns the cache ID for the passed in entity ID.
+   *
+   * @param int $id
+   *   Entity ID for which the cache ID should be built.
+   *
+   * @return string
+   *   Cache ID that can be passed to the cache backend.
+   */
+  protected function buildCacheId($id) {
+    return "values:{$this->entityTypeId}:$id";
   }
 
   /**
@@ -931,9 +1088,26 @@ public function getQueryServiceName() {
   }
 
   /**
-   * {@inheritdoc}
+   * Loads values of configurable fields for a group of entities.
+   *
+   * Loads all fields for each entity object in a group of a single entity type.
+   * The loaded field values are added directly to the entity objects.
+   *
+   * @param \Drupal\Core\Entity\ContentEntityInterface[] $entities
+   *   An array of entities keyed by entity ID.
    */
-  protected function doLoadFieldItems($entities, $age) {
+  protected function loadFieldItems(array $entities) {
+    if (empty($entities) || !$this->entityType->isFieldable()) {
+      return;
+    }
+
+    $age = static::FIELD_LOAD_CURRENT;
+    foreach ($entities as $entity) {
+      if (!$entity->isDefaultRevision()) {
+        $age = static::FIELD_LOAD_REVISION;
+        break;
+      }
+    }
     $load_current = $age == static::FIELD_LOAD_CURRENT;
 
     // Collect entities ids, bundles and languages.
@@ -1006,9 +1180,14 @@ protected function doLoadFieldItems($entities, $age) {
   }
 
   /**
-   * {@inheritdoc}
+   * Saves values of configurable fields for an entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
+   * @param bool $update
+   *   TRUE if the entity is being updated, FALSE if it is being inserted.
    */
-  protected function doSaveFieldItems(EntityInterface $entity, $update) {
+  protected function saveFieldItems(EntityInterface $entity, $update = TRUE) {
     $vid = $entity->getRevisionId();
     $id = $entity->id();
     $bundle = $entity->bundle();
@@ -1094,9 +1273,12 @@ protected function doSaveFieldItems(EntityInterface $entity, $update) {
   }
 
   /**
-   * {@inheritdoc}
+   * Deletes values of configurable fields for all revisions of an entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity.
    */
-  protected function doDeleteFieldItems(EntityInterface $entity) {
+  protected function deleteFieldItems(EntityInterface $entity) {
     foreach ($this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()) as $field_definition) {
       $storage_definition = $field_definition->getFieldStorageDefinition();
       if (!$this->usesDedicatedTable($storage_definition)) {
@@ -1114,9 +1296,12 @@ protected function doDeleteFieldItems(EntityInterface $entity) {
   }
 
   /**
-   * {@inheritdoc}
+   * Deletes values of configurable fields for a single revision of an entity.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity. It must have a revision ID.
    */
-  protected function doDeleteFieldItemsRevision(EntityInterface $entity) {
+  protected function deleteFieldItemsRevision(EntityInterface $entity) {
     $vid = $entity->getRevisionId();
     if (isset($vid)) {
       foreach ($this->entityManager->getFieldDefinitions($entity->getEntityTypeId(), $entity->bundle()) as $field_definition) {
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityInterface.php b/core/lib/Drupal/Core/Entity/ContentEntityInterface.php
index f07936c..8906239 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityInterface.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityInterface.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Entity;
 
+use Drupal\Core\Field\PrepareCacheInterface;
 use Drupal\Core\TypedData\ComplexDataInterface;
 use Drupal\Core\TypedData\TranslatableInterface;
 
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
index dad9674..a4849be 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
@@ -76,196 +76,6 @@ protected function doCreate(array $values) {
   }
 
   /**
-   * Loads values of configurable fields for a group of entities.
-   *
-   * Loads all fields for each entity object in a group of a single entity type.
-   * The loaded field values are added directly to the entity objects.
-   *
-   * This method is a wrapper that handles the field data cache. Subclasses
-   * need to implement the doLoadFieldItems() method with the actual storage
-   * logic.
-   *
-   * @param array $entities
-   *   An array of entities keyed by entity ID.
-   */
-  protected function loadFieldItems(array $entities) {
-    if (empty($entities)) {
-      return;
-    }
-
-    $age = static::FIELD_LOAD_CURRENT;
-    foreach ($entities as $entity) {
-      if (!$entity->isDefaultRevision()) {
-        $age = static::FIELD_LOAD_REVISION;
-        break;
-      }
-    }
-
-    // Only the most current revision of non-deleted fields for cacheable entity
-    // types can be cached.
-    $load_current = $age == static::FIELD_LOAD_CURRENT;
-    $use_cache = $load_current && $this->entityType->isFieldDataCacheable();
-
-    // Assume all entities will need to be queried. Entities found in the cache
-    // will be removed from the list.
-    $queried_entities = $entities;
-
-    // Fetch available entities from cache, if applicable.
-    if ($use_cache) {
-      // Build the list of cache entries to retrieve.
-      $cids = array();
-      foreach ($entities as $id => $entity) {
-        $cids[] = "field:{$this->entityTypeId}:$id";
-      }
-      $cache = \Drupal::cache('entity')->getMultiple($cids);
-      // Put the cached field values back into the entities and remove them from
-      // the list of entities to query.
-      foreach ($entities as $id => $entity) {
-        $cid = "field:{$this->entityTypeId}:$id";
-        if (isset($cache[$cid])) {
-          unset($queried_entities[$id]);
-          foreach ($cache[$cid]->data as $langcode => $values) {
-            $translation = $entity->getTranslation($langcode);
-            // We do not need to worry about field translatability here, the
-            // translation object will manage that automatically.
-            foreach ($values as $field_name => $items) {
-              $translation->$field_name = $items;
-            }
-          }
-        }
-      }
-    }
-
-    // Fetch other entities from their storage location.
-    if ($queried_entities) {
-      // Let the storage actually load the values.
-      $this->doLoadFieldItems($queried_entities, $age);
-
-      // Build cache data.
-      // @todo: Improve this logic to avoid instantiating field objects once
-      // the field logic is improved to not do that anyway.
-      if ($use_cache) {
-        foreach ($queried_entities as $id => $entity) {
-          $data = array();
-          foreach ($entity->getTranslationLanguages() as $langcode => $language) {
-            $translation = $entity->getTranslation($langcode);
-            foreach ($translation as $field_name => $items) {
-              if ($items->getFieldDefinition() instanceof FieldInstanceConfigInterface && !$items->isEmpty()) {
-                $data[$langcode][$field_name] = $items->getValue();
-              }
-            }
-          }
-          $cid = "field:{$this->entityTypeId}:$id";
-          \Drupal::cache('entity')->set($cid, $data, Cache::PERMANENT, array('entity_field_info' => TRUE));
-        }
-      }
-    }
-  }
-
-  /**
-   * Saves values of configurable fields for an entity.
-   *
-   * This method is a wrapper that handles the field data cache. Subclasses
-   * need to implement the doSaveFieldItems() method with the actual storage
-   * logic.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity.
-   * @param bool $update
-   *   TRUE if the entity is being updated, FALSE if it is being inserted.
-   */
-  protected function saveFieldItems(EntityInterface $entity, $update = TRUE) {
-    $this->doSaveFieldItems($entity, $update);
-
-    if ($update) {
-      $entity_type = $entity->getEntityType();
-      if ($entity_type->isFieldDataCacheable()) {
-        \Drupal::cache('entity')->delete('field:' . $entity->getEntityTypeId() . ':' . $entity->id());
-      }
-    }
-  }
-
-  /**
-   * Deletes values of configurable fields for all revisions of an entity.
-   *
-   * This method is a wrapper that handles the field data cache. Subclasses
-   * need to implement the doDeleteFieldItems() method with the actual storage
-   * logic.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity.
-   */
-  protected function deleteFieldItems(EntityInterface $entity) {
-    $this->doDeleteFieldItems($entity);
-
-    $entity_type = $entity->getEntityType();
-    if ($entity_type->isFieldDataCacheable()) {
-      \Drupal::cache('entity')->delete('field:' . $entity->getEntityTypeId() . ':' . $entity->id());
-    }
-  }
-
-  /**
-   * Deletes values of configurable fields for a single revision of an entity.
-   *
-   * This method is a wrapper that handles the field data cache. Subclasses
-   * need to implement the doDeleteFieldItemsRevision() method with the actual
-   * storage logic.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity. It must have a revision ID attribute.
-   */
-  protected function deleteFieldItemsRevision(EntityInterface $entity) {
-    $this->doDeleteFieldItemsRevision($entity);
-  }
-
-  /**
-   * Loads values of configurable fields for a group of entities.
-   *
-   * This is the method that holds the actual storage logic.
-   *
-   * @param array $entities
-   *   An array of entities keyed by entity ID.
-   * @param int $age
-   *   EntityStorageInterface::FIELD_LOAD_CURRENT to load the most
-   *   recent revision for all fields, or
-   *   EntityStorageInterface::FIELD_LOAD_REVISION to load the version
-   *   indicated by each entity.
-   */
-  abstract protected function doLoadFieldItems($entities, $age);
-
-  /**
-   * Saves values of configurable fields for an entity.
-   *
-   * This is the method that holds the actual storage logic.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity.
-   * @param bool $update
-   *   TRUE if the entity is being updated, FALSE if it is being inserted.
-   */
-  abstract protected function doSaveFieldItems(EntityInterface $entity, $update);
-
-  /**
-   * Deletes values of configurable fields for all revisions of an entity.
-   *
-   * This is the method that holds the actual storage logic.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity.
-   */
-  abstract protected function doDeleteFieldItems(EntityInterface $entity);
-
-  /**
-   * Deletes values of configurable fields for a single revision of an entity.
-   *
-   * This is the method that holds the actual storage logic.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The entity.
-   */
-  abstract protected function doDeleteFieldItemsRevision(EntityInterface $entity);
-
-  /**
    * {@inheritdoc}
    */
   public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $storage_definition) { }
diff --git a/core/lib/Drupal/Core/Entity/EntityType.php b/core/lib/Drupal/Core/Entity/EntityType.php
index 728b3ec..5ebb0b6 100644
--- a/core/lib/Drupal/Core/Entity/EntityType.php
+++ b/core/lib/Drupal/Core/Entity/EntityType.php
@@ -40,7 +40,7 @@ class EntityType implements EntityTypeInterface {
    *
    * @var bool
    */
-  protected $field_cache;
+  protected $persistent_cache;
 
   /**
    * An array of entity keys.
@@ -260,8 +260,8 @@ public function isRenderCacheable() {
   /**
    * {@inheritdoc}
    */
-  public function isFieldDataCacheable() {
-    return isset($this->field_cache) ? $this->field_cache: TRUE;
+  public function isPersistentlyCacheable() {
+    return isset($this->persistent_cache) ? $this->persistent_cache: TRUE;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
index a6e39aa..d92e00b 100644
--- a/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityTypeInterface.php
@@ -153,7 +153,7 @@ public function isRenderCacheable();
    *
    * @return bool
    */
-  public function isFieldDataCacheable();
+  public function isPersistentlyCacheable();
 
   /**
    * Sets the name of the entity type class.
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 03c32db..bb95be4 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -682,11 +682,11 @@ function comment_form_field_ui_field_edit_form_alter(&$form, $form_state) {
 }
 
 /**
- * Implements hook_entity_load().
+ * Implements hook_entity_storage_load().
  *
  * @see \Drupal\comment\Plugin\Field\FieldType\CommentItem::propertyDefinitions()
  */
-function comment_entity_load($entities, $entity_type) {
+function comment_entity_storage_load($entities, $entity_type) {
   // Comments can only be attached to content entities, so skip others.
   if (!\Drupal::entityManager()->getDefinition($entity_type)->isSubclassOf('Drupal\Core\Entity\ContentEntityInterface')) {
     return;
diff --git a/core/modules/comment/src/CommentStatistics.php b/core/modules/comment/src/CommentStatistics.php
index c2ac73d..164e8aa 100644
--- a/core/modules/comment/src/CommentStatistics.php
+++ b/core/modules/comment/src/CommentStatistics.php
@@ -251,6 +251,10 @@ public function update(CommentInterface $comment) {
         ->condition('field_name', $comment->getFieldName())
         ->execute();
     }
+
+    // Reset the cache of the commented entity so that when the entity is loaded
+    // the next time, the statistics will be loaded again.
+    $this->entityManager->getStorage($comment->getCommentedEntityTypeId())->resetCache(array($comment->getCommentedEntityId()));
   }
 
 }
diff --git a/core/modules/comment/src/CommentStorage.php b/core/modules/comment/src/CommentStorage.php
index 945ff9d..089def5 100644
--- a/core/modules/comment/src/CommentStorage.php
+++ b/core/modules/comment/src/CommentStorage.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\comment;
 
+use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
@@ -46,13 +47,15 @@ class CommentStorage extends ContentEntityDatabaseStorage implements CommentStor
    *   The database connection to be used.
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache
+   *   Cache backend instance to use.
    * @param \Drupal\comment\CommentStatisticsInterface $comment_statistics
    *   The comment statistics service.
    * @param \Drupal\Core\Session\AccountInterface $current_user
    *   The current user.
    */
-  public function __construct(EntityTypeInterface $entity_info, Connection $database, EntityManagerInterface $entity_manager, CommentStatisticsInterface $comment_statistics, AccountInterface $current_user) {
-    parent::__construct($entity_info, $database, $entity_manager);
+  public function __construct(EntityTypeInterface $entity_info, Connection $database, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, CommentStatisticsInterface $comment_statistics, AccountInterface $current_user) {
+    parent::__construct($entity_info, $database, $entity_manager, $cache);
     $this->statistics = $comment_statistics;
     $this->currentUser = $current_user;
   }
@@ -65,6 +68,7 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
       $entity_info,
       $container->get('database'),
       $container->get('entity.manager'),
+      $container->get('cache.entity'),
       $container->get('comment.statistics'),
       $container->get('current_user')
     );
diff --git a/core/modules/content_translation/content_translation.module b/core/modules/content_translation/content_translation.module
index 858ffba..f717941 100644
--- a/core/modules/content_translation/content_translation.module
+++ b/core/modules/content_translation/content_translation.module
@@ -506,9 +506,9 @@ function content_translation_language_fallback_candidates_entity_view_alter(&$ca
 }
 
 /**
- * Implements hook_entity_load().
+ * Implements hook_entity_storage_load().
  */
-function content_translation_entity_load(array $entities, $entity_type) {
+function content_translation_entity_storage_load(array $entities, $entity_type) {
   $enabled_entities = array();
 
   if (content_translation_enabled($entity_type)) {
diff --git a/core/modules/field/src/Tests/FieldAttachOtherTest.php b/core/modules/field/src/Tests/FieldAttachOtherTest.php
index a058dd6..b396eda 100644
--- a/core/modules/field/src/Tests/FieldAttachOtherTest.php
+++ b/core/modules/field/src/Tests/FieldAttachOtherTest.php
@@ -170,17 +170,18 @@ function testEntityDisplayViewMultiple() {
   }
 
   /**
-   * Test field cache.
+   * Test entity cache.
+   *
+   * @todo Remove this when there is equal unit test coverage.
    */
-  function testFieldAttachCache() {
+  function testEntityCache() {
     // Initialize random values and a test entity.
     $entity_init = entity_create('entity_test', array('type' => $this->instance->bundle));
-    $langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
     $values = $this->_generateTestFieldValues($this->field->getCardinality());
 
     // Non-cacheable entity type.
     $entity_type = 'entity_test';
-    $cid = "field:$entity_type:" . $entity_init->id();
+    $cid = "values:$entity_type:" . $entity_init->id();
 
     // Check that no initial cache entry is present.
     $this->assertFalse(\Drupal::cache('entity')->get($cid), 'Non-cached: no initial cache entry');
@@ -189,7 +190,7 @@ function testFieldAttachCache() {
     $entity = clone($entity_init);
     $entity->{$this->field_name}->setValue($values);
     $entity = $this->entitySaveReload($entity);
-    $cid = "field:$entity_type:" . $entity->id();
+    $cid = "values:$entity_type:" . $entity->id();
     $this->assertFalse(\Drupal::cache('entity')->get($cid), 'Non-cached: no cache entry on insert and load');
 
     // Cacheable entity type.
@@ -201,22 +202,22 @@ function testFieldAttachCache() {
     ));
 
     // Check that no initial cache entry is present.
-    $cid = "field:$entity_type:" . $entity->id();
+    $cid = "values:$entity_type:" . $entity->id();
     $this->assertFalse(\Drupal::cache('entity')->get($cid), 'Cached: no initial cache entry');
 
     // Save, and check that no cache entry is present.
     $entity = clone($entity_init);
     $entity->{$this->field_name_2} = $values;
     $entity->save();
-    $cid = "field:$entity_type:" . $entity->id();
+    $cid = "values:$entity_type:" . $entity->id();
 
     $this->assertFalse(\Drupal::cache('entity')->get($cid), 'Cached: no cache entry on insert');
     // Load, and check that a cache entry is present with the expected values.
     $controller = $this->container->get('entity.manager')->getStorage($entity->getEntityTypeId());
     $controller->resetCache();
-    $controller->load($entity->id());
+    $cached_entity = $controller->load($entity->id());
     $cache = \Drupal::cache('entity')->get($cid);
-    $this->assertEqual($cache->data[$langcode][$this->field_name_2], $values, 'Cached: correct cache entry on load');
+    $this->assertEqual($cache->data, $cached_entity, 'Cached: correct cache entry on load');
 
     // Update with different values, and check that the cache entry is wiped.
     $values = $this->_generateTestFieldValues($this->field_2->getCardinality());
@@ -226,9 +227,9 @@ function testFieldAttachCache() {
 
     // Load, and check that a cache entry is present with the expected values.
     $controller->resetCache();
-    $controller->load($entity->id());
+    $cached_entity = $controller->load($entity->id());
     $cache = \Drupal::cache('entity')->get($cid);
-    $this->assertEqual($cache->data[$langcode][$this->field_name_2], $values, 'Cached: correct cache entry on load');
+    $this->assertEqual($cache->data, $cached_entity, 'Cached: correct cache entry on load');
 
     // Create a new revision, and check that the cache entry is wiped.
     $values = $this->_generateTestFieldValues($this->field_2->getCardinality());
@@ -239,9 +240,9 @@ function testFieldAttachCache() {
 
     // Load, and check that a cache entry is present with the expected values.
     $controller->resetCache();
-    $controller->load($entity->id());
+    $cached_entity = $controller->load($entity->id());
     $cache = \Drupal::cache('entity')->get($cid);
-    $this->assertEqual($cache->data[$langcode][$this->field_name_2], $values, 'Cached: correct cache entry on load');
+    $this->assertEqual($cache->data, $cached_entity, 'Cached: correct cache entry on load');
 
     // Delete, and check that the cache entry is wiped.
     $entity->delete();
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 97fa6ff..750dd35 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -1892,15 +1892,14 @@ function file_get_file_references(FileInterface $file, FieldDefinitionInterface
           $file_fields[$entity_type_id][$bundle] = array();
           // This contains the possible field names.
           foreach ($entity->getFieldDefinitions() as $field_name => $field_definition) {
-            $field_type = $field_definition->getType();
             // If this is the first time this field type is seen, check
             // whether it references files.
-            if (!isset($field_columns[$field_type])) {
-              $field_columns[$field_type] = file_field_find_file_reference_column($field_definition);
+            if (!isset($field_columns[$field_definition->getType()])) {
+              $field_columns[$field_definition->getType()] = file_field_find_file_reference_column($field_definition);
             }
             // If the field type does reference files then record it.
-            if ($field_columns[$field_type]) {
-              $file_fields[$entity_type_id][$bundle][$field_name] = $field_columns[$field_type];
+            if ($field_columns[$field_definition->getType()]) {
+              $file_fields[$entity_type_id][$bundle][$field_name] = $field_columns[$field_definition->getType()];
             }
           }
         }
diff --git a/core/modules/file/src/Tests/FilePrivateTest.php b/core/modules/file/src/Tests/FilePrivateTest.php
index dcdf910..0159a59 100644
--- a/core/modules/file/src/Tests/FilePrivateTest.php
+++ b/core/modules/file/src/Tests/FilePrivateTest.php
@@ -23,6 +23,7 @@ class FilePrivateTest extends FileFieldTestBase {
 
   public function setUp() {
     parent::setUp();
+    node_access_test_add_field(entity_load('node_type', 'article'));
     node_access_rebuild();
     \Drupal::state()->set('node_access_test.private', TRUE);
   }
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index 23293fe..2399d10 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -233,7 +233,7 @@ function forum_node_validate(EntityInterface $node, $form, &$form_state) {
 }
 
 /**
- * Implements hook_ENTITY_TYPE_presave() for node entities.
+ * Implements hook_node_presave().
  *
  * Assigns the forum taxonomy when adding a topic from within a forum.
  */
@@ -256,7 +256,7 @@ function forum_node_presave(EntityInterface $node) {
 }
 
 /**
- * Implements hook_ENTITY_TYPE_update() for node entities.
+ * Implements hook_node_update().
  */
 function forum_node_update(EntityInterface $node) {
   if (\Drupal::service('forum_manager')->checkNodeType($node)) {
@@ -298,7 +298,7 @@ function forum_node_update(EntityInterface $node) {
 }
 
 /**
- * Implements hook_ENTITY_TYPE_insert() for node entities.
+ * Implements hook_node_insert().
  */
 function forum_node_insert(EntityInterface $node) {
   if (\Drupal::service('forum_manager')->checkNodeType($node)) {
@@ -316,7 +316,7 @@ function forum_node_insert(EntityInterface $node) {
 }
 
 /**
- * Implements hook_ENTITY_TYPE_predelete() for node entities.
+ * Implements hook_node_predelete().
  */
 function forum_node_predelete(EntityInterface $node) {
   if (\Drupal::service('forum_manager')->checkNodeType($node)) {
@@ -358,7 +358,7 @@ function forum_permission() {
 }
 
 /**
- * Implements hook_ENTITY_TYPE_update() for comment entities.
+ * Implements hook_comment_update().
  */
 function forum_comment_update(CommentInterface $comment) {
   if ($comment->getCommentedEntityTypeId() == 'node') {
@@ -367,7 +367,7 @@ function forum_comment_update(CommentInterface $comment) {
 }
 
 /**
- * Implements hook_ENTITY_TYPE_insert() for comment entities.
+ * Implements hook_comment_insert().
  */
 function forum_comment_insert(CommentInterface $comment) {
   if ($comment->getCommentedEntityTypeId() == 'node') {
@@ -376,7 +376,7 @@ function forum_comment_insert(CommentInterface $comment) {
 }
 
 /**
- * Implements hook_ENTITY_TYPE_delete() for comment entities.
+ * Implements hook_comment_delete().
  */
 function forum_comment_delete(CommentInterface $comment) {
   if ($comment->getCommentedEntityTypeId() == 'node') {
diff --git a/core/modules/forum/src/Tests/ForumNodeAccessTest.php b/core/modules/forum/src/Tests/ForumNodeAccessTest.php
index 7e23ae0..ad51808 100644
--- a/core/modules/forum/src/Tests/ForumNodeAccessTest.php
+++ b/core/modules/forum/src/Tests/ForumNodeAccessTest.php
@@ -26,6 +26,7 @@ class ForumNodeAccessTest extends WebTestBase {
   function setUp() {
     parent::setUp();
     node_access_rebuild();
+    node_access_test_add_field(entity_load('node_type', 'forum'));
     \Drupal::state()->set('node_access_test.private', TRUE);
   }
 
@@ -48,7 +49,7 @@ function testForumNodeAccess() {
     $edit = array(
       'title[0][value]' => $private_node_title,
       'body[0][value]' => $this->randomName(200),
-      'private' => TRUE,
+      'private[0][value]' => TRUE,
     );
     $this->drupalPostForm('node/add/forum', $edit, t('Save'), array('query' => array('forum_id' => 1)));
     $private_node = $this->drupalGetNodeByTitle($private_node_title);
diff --git a/core/modules/node/src/Tests/NodeAccessBaseTableTest.php b/core/modules/node/src/Tests/NodeAccessBaseTableTest.php
index ec05c8c..0a008e1 100644
--- a/core/modules/node/src/Tests/NodeAccessBaseTableTest.php
+++ b/core/modules/node/src/Tests/NodeAccessBaseTableTest.php
@@ -33,6 +33,8 @@ class NodeAccessBaseTableTest extends NodeTestBase {
   public function setUp() {
     parent::setUp();
 
+    node_access_test_add_field(entity_load('node_type', 'article'));
+
     node_access_rebuild();
     \Drupal::state()->set('node_access_test.private', TRUE);
   }
@@ -69,7 +71,7 @@ function testNodeAccessBasic() {
           'title[0][value]' => t('@private_public Article created by @user', array('@private_public' => $type, '@user' => $this->webUser->getUsername())),
         );
         if ($is_private) {
-          $edit['private'] = TRUE;
+          $edit['private[0][value]'] = TRUE;
           $edit['body[0][value]'] = 'private node';
           $edit['field_tags'] = 'private';
         }
@@ -79,14 +81,13 @@ function testNodeAccessBasic() {
         }
 
         $this->drupalPostForm('node/add/article', $edit, t('Save'));
-        $nid = db_query('SELECT nid FROM {node_field_data} WHERE title = :title', array(':title' => $edit['title[0][value]']))->fetchField();
-        $private_status = db_query('SELECT private FROM {node_access_test} where nid = :nid', array(':nid' => $nid))->fetchField();
-        $this->assertTrue($is_private == $private_status, 'The private status of the node was properly set in the node_access_test table.');
+        $node = $this->drupalGetNodeByTitle($edit['title[0][value]']);
+        $this->assertEqual($is_private, (int)$node->private->value, 'The private status of the node was properly set in the node_access_test table.');
         if ($is_private) {
-          $private_nodes[] = $nid;
+          $private_nodes[] = $node->id();
         }
-        $titles[$nid] = $edit['title[0][value]'];
-        $this->nodesByUser[$this->webUser->id()][$nid] = $is_private;
+        $titles[$node->id()] = $edit['title[0][value]'];
+        $this->nodesByUser[$this->webUser->id()][$node->id()] = $is_private;
       }
     }
     $this->publicTid = db_query('SELECT tid FROM {taxonomy_term_data} WHERE name = :name', array(':name' => 'public'))->fetchField();
diff --git a/core/modules/node/src/Tests/NodeAccessLanguageAwareCombinationTest.php b/core/modules/node/src/Tests/NodeAccessLanguageAwareCombinationTest.php
index 1eeb584..ace0d5d 100644
--- a/core/modules/node/src/Tests/NodeAccessLanguageAwareCombinationTest.php
+++ b/core/modules/node/src/Tests/NodeAccessLanguageAwareCombinationTest.php
@@ -48,6 +48,8 @@ class NodeAccessLanguageAwareCombinationTest extends NodeTestBase {
   public function setUp() {
     parent::setUp();
 
+    node_access_test_add_field(entity_load('node_type', 'page'));
+
     // Create the 'private' field, which allows the node to be marked as private
     // (restricted access) in a given translation.
     $field_private = entity_create('field_config', array(
diff --git a/core/modules/node/src/Tests/NodeAccessLanguageTest.php b/core/modules/node/src/Tests/NodeAccessLanguageTest.php
index 526439f..9683cca 100644
--- a/core/modules/node/src/Tests/NodeAccessLanguageTest.php
+++ b/core/modules/node/src/Tests/NodeAccessLanguageTest.php
@@ -28,6 +28,8 @@ class NodeAccessLanguageTest extends NodeTestBase {
   function setUp() {
     parent::setUp();
 
+    node_access_test_add_field(entity_load('node_type', 'page'));
+
     // After enabling a node access module, the access table has to be rebuild.
     node_access_rebuild();
 
diff --git a/core/modules/node/tests/modules/node_access_test/node_access_test.install b/core/modules/node/tests/modules/node_access_test/node_access_test.install
deleted file mode 100644
index 6b3ef5d..0000000
--- a/core/modules/node/tests/modules/node_access_test/node_access_test.install
+++ /dev/null
@@ -1,42 +0,0 @@
-<?php
-
-/**
- * @file
- * Install, update and uninstall functions for the node_access_test module.
- */
-
-/**
- * Implements hook_schema().
- */
-function node_access_test_schema() {
-  $schema['node_access_test'] = array(
-    'description' => 'The base table for node_access_test.',
-    'fields' => array(
-      'nid' => array(
-        'description' => 'The {node}.nid this record affects.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'private' => array(
-        'description' => 'Boolean indicating whether the node is private (visible to administrator) or not (visible to non-administrators).',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-    ),
-    'indexes' => array(
-      'nid' => array('nid'),
-    ),
-    'primary key' => array('nid'),
-    'foreign keys' => array(
-      'versioned_node' => array(
-        'table' => 'node',
-        'columns' => array('nid' => 'nid'),
-      ),
-    ),
-  );
-
-  return $schema;
-}
\ No newline at end of file
diff --git a/core/modules/node/tests/modules/node_access_test/node_access_test.module b/core/modules/node/tests/modules/node_access_test/node_access_test.module
index 64c7476..f98cc4a 100644
--- a/core/modules/node/tests/modules/node_access_test/node_access_test.module
+++ b/core/modules/node/tests/modules/node_access_test/node_access_test.module
@@ -49,7 +49,7 @@
 function node_access_test_node_grants($account, $op) {
   $grants = array();
   $grants['node_access_test_author'] = array($account->id());
-  if ($op == 'view' && $account->hasPermission('node test view', $account)) {
+  if ($op == 'view' && user_access('node test view', $account)) {
     $grants['node_access_test'] = array(8888, 8889);
   }
 
@@ -127,17 +127,32 @@ function node_access_test_permission() {
 }
 
 /**
- * Implements hook_entity_base_field_info().
+ * Adds the private field to a node type.
+ *
+ * @param \Drupal\node\NodeTypeInterface $type
+ *   A node type entity.
  */
-function node_access_test_entity_base_field_info(EntityTypeInterface $entity_type) {
-  if ($entity_type->id() === 'node') {
-    $fields['private'] = FieldDefinition::create('boolean')
-      ->setLabel(t('Private'))
-      ->setComputed(TRUE)
-      ->setCustomStorage(TRUE);
+function node_access_test_add_field(NodeTypeInterface $type) {
+  $field = FieldConfig::create(array(
+    'name' => 'private',
+    'entity_type' => 'node',
+    'type' => 'integer',
+  ));
+  $field->save();
+  $instance = FieldInstanceConfig::create(array(
+    'field_name' => 'private',
+    'entity_type' => 'node',
+    'bundle' => $type->id(),
+    'label' => 'Private',
+  ));
+  $instance->save();
 
-    return $fields;
-  }
+  // Assign widget settings for the 'default' form mode.
+  entity_get_form_display('node', $type->id(), 'default')
+    ->setComponent('private', array(
+      'type' => 'number',
+    ))
+    ->save();
 }
 
 /**
diff --git a/core/modules/system/entity.api.php b/core/modules/system/entity.api.php
index 5388dab..da1d53e 100644
--- a/core/modules/system/entity.api.php
+++ b/core/modules/system/entity.api.php
@@ -11,231 +11,6 @@
 use Drupal\Core\Render\Element;
 
 /**
- * @defgroup entity_crud Entity CRUD, editing, and view hooks
- * @{
- * Hooks used in various entity operations.
- *
- * Entity create, read, update, and delete (CRUD) operations are performed by
- * entity storage classes; see the
- * @link entity_api Entity API topic @endlink for more information. Most
- * entities use or extend the default classes:
- * \Drupal\Core\Entity\ContentEntityDatabaseStorage for content entities, and
- * \Drupal\Core\Config\Entity\ConfigEntityStorage for configuration entities.
- * For these entities, there is a set of hooks that is invoked for each
- * CRUD operation, which module developers can implement to affect these
- * operations; these hooks are actually invoked from methods on
- * \Drupal\Core\Entity\EntityStorageBase.
- *
- * For content entities, viewing and rendering are handled by a view builder
- * class; see the @link entity_api Entity API topic @endlink for more
- * information. Most view builders extend or use the default class
- * \Drupal\Core\Entity\EntityViewBuilder.
- *
- * Entity editing (including adding new entities) is handled by entity form
- * classes; see the @link entity_api Entity API topic @endlink for more
- * information. Most entity editing forms extend base classes
- * \Drupal\Core\Entity\EntityForm or \Drupal\Core\Entity\ContentEntityForm.
- * Note that many other operations, such as confirming deletion of entities,
- * also use entity form classes.
- *
- * This topic lists all of the entity CRUD and view operations, and the hooks
- * and other operations that are invoked (in order) for each operation. Some
- * notes:
- * - Whenever an entity hook is invoked, there is both a type-specific entity
- *   hook, and a generic entity hook. For instance, during a create operation on
- *   a node, first hook_node_create() and then hook_entity_create() would be
- *   invoked.
- * - The entity-type-specific hooks are represented in the list below as
- *   hook_ENTITY_TYPE_... (hook_ENTITY_TYPE_create() in this example). To
- *   implement one of these hooks for an entity whose machine name is "foo",
- *   define a function called mymodule_foo_create(), for instance. Also note
- *   that the entity or array of entities that are passed into a specific-type
- *   hook are of the specific entity class, not the generic Entity class, so in
- *   your implementation, you can make the $entity argument something like $node
- *   and give it a specific type hint (which should normally be to the specific
- *   interface, such as \Drupal\Node\NodeInterface for nodes).
- * - $storage in the code examples is assumed to be an entity storage
- *   class. See the @link entity_api Entity API topic @endlink for
- *   information on how to instantiate the correct storage class for an
- *   entity type.
- * - $view_builder in the code examples is assumed to be an entity view builder
- *   class. See the @link entity_api Entity API topic @endlink for
- *   information on how to instantiate the correct view builder class for
- *   an entity type.
- * - During many operations, static methods are called on the entity class,
- *   which implements \Drupal\Entity\EntityInterface.
- *
- * @section create Create operations
- * To create an entity:
- * @code
- * $entity = $storage->create();
- *
- * // Add code here to set properties on the entity.
- *
- * // Until you call save(), the entity is just in memory.
- * $entity->save();
- * @endcode
- * There is also a shortcut method on entity classes, which creates an entity
- * with an array of provided property values: \Drupal\Core\Entity::create().
- *
- * Hooks invoked during the create operation:
- * - hook_ENTITY_TYPE_create()
- * - hook_entity_create()
- *
- * See @ref save below for the save portion of the operation.
- *
- * @section load Read/Load operations
- * To load (read) a single entity:
- * @code
- * $entity = $storage->load($id);
- * @endcode
- * To load multiple entities:
- * @code
- * $entities = $storage->loadMultiple($ids);
- * @endcode
- * Since load() calls loadMultiple(), these are really the same operation.
- * Here is the order of hooks and other operations that take place during
- * entity loading:
- * - Entity is loaded from storage.
- * - postLoad() is called on the entity class, passing in all of the loaded
- *   entities.
- * - hook_entity_load()
- * - hook_ENTITY_TYPE_load()
- *
- * When an entity is loaded, normally the default entity revision is loaded.
- * It is also possible to load a different revision, for entities that support
- * revisions, with this code:
- * @code
- * $entity = $storage->loadRevision($revision_id);
- * @endcode
- * This involves the same hooks and operations as regular entity loading.
- *
- * @section save Save operations
- * To update an existing entity, you will need to load it, change properties,
- * and then save; as described above, when creating a new entity, you will also
- * need to save it. Here is the order of hooks and other events that happen
- * during an entity save:
- * - preSave() is called on the entity object, and field objects.
- * - hook_ENTITY_TYPE_presave()
- * - hook_entity_presave()
- * - Entity is saved to storage.
- * - For updates on content entities, if there is a translation added that
- *   was not previously present:
- *   - hook_ENTITY_TYPE_translation_insert()
- *   - hook_entity_translation_insert()
- * - For updates on content entities, if there was a translation removed:
- *   - hook_ENTITY_TYPE_translation_delete()
- *   - hook_entity_translation_delete()
- * - postSave() is called on the entity object.
- * - hook_ENTITY_TYPE_insert() (new) or hook_ENTITY_TYPE_update() (update)
- * - hook_entity_insert() (new) or hook_entity_update() (update)
- *
- * Some specific entity types invoke hooks during preSave() or postSave()
- * operations. Examples:
- * - Field configuration preSave(): hook_field_config_update_forbid()
- * - Node postSave(): hook_node_access_records() and
- *   hook_node_access_records_alter()
- * - Config entities that are acting as entity bundles, in postSave():
- *   hook_entity_bundle_create() or hook_entity_bundle_rename() as appropriate
- * - Comment: hook_comment_publish() and hook_comment_unpublish() as
- *   appropriate.
- *
- * @section edit Editing operations
- * When an entity's add/edit form is used to add or edit an entity, there
- * are several hooks that are invoked:
- * - hook_entity_prepare_form()
- * - hook_ENTITY_TYPE_prepare_form()
- * - hook_entity_form_display_alter() (for content entities only)
- *
- * Some specific entity types have additional hooks that are run during
- * various steps in the process:
- * - Node entities: hook_node_validate() and hook_submit().
- *
- * @section delete Delete operations
- * To delete one or more entities, load them and then delete them:
- * @code
- * $entities = $storage->loadMultiple($ids);
- * $storage->delete($entities);
- * @endcode
- *
- * During the delete operation, the following hooks and other events happen:
- * - preDelete() is called on the entity class.
- * - hook_ENTITY_TYPE_predelete()
- * - hook_entity_predelete()
- * - Entity and field information is removed from storage.
- * - postDelete() is called on the entity class.
- * - hook_ENTITY_TYPE_delete()
- * - hook_entity_delete()
- *
- * Some specific entity types invoke hooks during the delete process. Examples:
- * - Entity bundle postDelete(): hook_entity_bundle_delete()
- *
- * Individual revisions of an entity can also be deleted:
- * @code
- * $storage->deleteRevision($revision_id);
- * @endcode
- * This operation invokes the following operations and hooks:
- * - Revision is loaded (see @ref load above).
- * - Revision and field information is removed from the database.
- * - hook_ENTITY_TYPE_revision_delete()
- * - hook_entity_revision_delete()
- *
- * @section view View/render operations
- * To make a render array for a loaded entity:
- * @code
- * // You can omit the language ID if the default language is being used.
- * $build = $view_builder->view($entity, 'view_mode_name', $language->id);
- * @endcode
- * You can also use the viewMultiple() method to view multiple entities.
- *
- * Hooks invoked during the operation of building a render array:
- * - hook_entity_view_mode_alter()
- * - hook_ENTITY_TYPE_build_defaults_alter()
- * - hook_entity_build_defaults_alter()
- *
- * View builders for some types override these hooks, notably:
- * - The Tour view builder does not invoke any hooks.
- * - The Block view builder invokes hook_block_view_alter() and
- *   hook_block_view_BASE_BLOCK_ID_alter(). Note that in other view builders,
- *   the view alter hooks are run later in the process.
- *
- * During the rendering operation, the default entity viewer runs the following
- * hooks and operations in the pre-render step:
- * - hook_entity_view_display_alter()
- * - hook_entity_prepare_view()
- * - Entity fields are loaded, and render arrays are built for them using
- *   their formatters.
- * - hook_entity_display_build_alter()
- * - hook_ENTITY_TYPE_view()
- * - hook_entity_view()
- * - hook_ENTITY_TYPE_view_alter()
- * - hook_entity_view_alter()
- *
- * Some specific builders have specific hooks:
- * - The Node view builder invokes hook_node_links_alter().
- * - The Comment view builder invokes hook_comment_links_alter().
- *
- * After this point in rendering, the theme system takes over. See the
- * @link theme_render Theme system and render API topic @endlink for more
- * information.
- *
- * @section misc Other entity hooks
- * Some types of entities invoke hooks for specific operations:
- * - Searching nodes:
- *   - hook_ranking()
- *   - Query is executed to find matching nodes
- *   - Resulting node is loaded
- *   - Node render array is built
- *   - comment_node_update_index() is called (this adds "N comments" text)
- *   - hook_node_search_result()
- * - Search indexing nodes:
- *   - Node is loaded
- *   - Node render array is built
- *   - hook_node_update_index()
- * @}
- */
-
-/**
  * @addtogroup hooks
  * @{
  */
@@ -257,10 +32,6 @@
  *   deny access.
  *
  * @see \Drupal\Core\Entity\EntityAccessController
- * @see hook_entity_create_access()
- * @see hook_ENTITY_TYPE_access()
- *
- * @ingroup entity_api
  */
 function hook_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account, $langcode) {
   return NULL;
@@ -283,10 +54,6 @@ function hook_entity_access(\Drupal\Core\Entity\EntityInterface $entity, $operat
  *   deny access.
  *
  * @see \Drupal\Core\Entity\EntityAccessController
- * @see hook_ENTITY_TYPE_create_access()
- * @see hook_entity_access()
- *
- * @ingroup entity_api
  */
 function hook_ENTITY_TYPE_access(\Drupal\Core\Entity\EntityInterface $entity, $operation, \Drupal\Core\Session\AccountInterface $account, $langcode) {
   return NULL;
@@ -305,10 +72,6 @@ function hook_ENTITY_TYPE_access(\Drupal\Core\Entity\EntityInterface $entity, $o
  *   deny access.
  *
  * @see \Drupal\Core\Entity\EntityAccessController
- * @see hook_entity_access()
- * @see hook_ENTITY_TYPE_create_access()
- *
- * @ingroup entity_api
  */
 function hook_entity_create_access(\Drupal\Core\Session\AccountInterface $account, $langcode) {
   return NULL;
@@ -327,10 +90,6 @@ function hook_entity_create_access(\Drupal\Core\Session\AccountInterface $accoun
  *   deny access.
  *
  * @see \Drupal\Core\Entity\EntityAccessController
- * @see hook_ENTITY_TYPE_access()
- * @see hook_entity_create_access()
- *
- * @ingroup entity_api
  */
 function hook_ENTITY_TYPE_create_access(\Drupal\Core\Session\AccountInterface $account, $langcode) {
   return NULL;
@@ -439,8 +198,6 @@ function hook_entity_bundle_info_alter(&$bundles) {
  *   The type of $entity; e.g. 'node' or 'user'.
  * @param string $bundle
  *   The name of the bundle.
- *
- * @see entity_crud
  */
 function hook_entity_bundle_create($entity_type_id, $bundle) {
   // When a new bundle is created, the menu needs to be rebuilt to add the
@@ -459,8 +216,6 @@ function hook_entity_bundle_create($entity_type_id, $bundle) {
  *   The previous name of the bundle.
  * @param string $bundle_new
  *   The new name of the bundle.
- *
- * @see entity_crud
  */
 function hook_entity_bundle_rename($entity_type_id, $bundle_old, $bundle_new) {
   // Update the settings associated with the bundle in my_module.settings.
@@ -482,8 +237,6 @@ function hook_entity_bundle_rename($entity_type_id, $bundle_old, $bundle_new) {
  *   The type of entity; for example, 'node' or 'user'.
  * @param string $bundle
  *   The bundle that was just deleted.
- *
- * @ingroup entity_crud
  */
 function hook_entity_bundle_delete($entity_type_id, $bundle) {
   // Remove the settings associated with the bundle in my_module.settings.
@@ -503,9 +256,6 @@ function hook_entity_bundle_delete($entity_type_id, $bundle) {
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity object.
- *
- * @ingroup entity_crud
- * @see hook_ENTITY_TYPE_create()
  */
 function hook_entity_create(\Drupal\Core\Entity\EntityInterface $entity) {
   if ($entity instanceof ContentEntityInterface && !$entity->foo->value) {
@@ -514,24 +264,6 @@ function hook_entity_create(\Drupal\Core\Entity\EntityInterface $entity) {
 }
 
 /**
- * Act on a newly created entity of a specific type.
- *
- * This hook runs after a new entity object has just been instantiated. It can
- * be used to set initial values, e.g. to provide defaults.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity object.
- *
- * @ingroup entity_crud
- * @see hook_entity_create()
- */
-function hook_ENTITY_TYPE_create(\Drupal\Core\Entity\EntityInterface $entity) {
-  if (!$entity->foo->value) {
-    $entity->foo->value = 'some_initial_value';
-  }
-}
-
-/**
  * Act on entities when loaded.
  *
  * This is a generic load hook called for all entity types loaded via the
@@ -541,9 +273,6 @@ function hook_ENTITY_TYPE_create(\Drupal\Core\Entity\EntityInterface $entity) {
  *   The entities keyed by entity ID.
  * @param string $entity_type_id
  *   The type of entities being loaded (i.e. node, user, comment).
- *
- * @ingroup entity_crud
- * @see hook_ENTITY_TYPE_load()
  */
 function hook_entity_load($entities, $entity_type_id) {
   foreach ($entities as $entity) {
@@ -552,50 +281,13 @@ function hook_entity_load($entities, $entity_type_id) {
 }
 
 /**
- * Act on entities of a specific type when loaded.
- *
- * @param array $entities
- *   The entities keyed by entity ID.
- *
- * @ingroup entity_crud
- * @see hook_entity_load()
- */
-function hook_ENTITY_TYPE_load($entities) {
-  foreach ($entities as $entity) {
-    $entity->foo = mymodule_add_something($entity);
-  }
-}
-
-/**
- * Act on an entity before it is created or updated.
+ * Act on an entity before it is about to be created or updated.
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity object.
- *
- * @ingroup entity_crud
- * @see hook_ENTITY_TYPE_presave()
  */
 function hook_entity_presave(Drupal\Core\Entity\EntityInterface $entity) {
- if ($entity instanceof ContentEntityInterface && $entity->isTranslatable()) {
-   $attributes = \Drupal::request()->attributes;
-   \Drupal::service('content_translation.synchronizer')->synchronizeFields($entity, $entity->language()->id, $attributes->get('source_langcode'));
-  }
-}
-
-/**
- * Act on a specific type of entity before it is created or updated.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity object.
- *
- * @ingroup entity_crud
- * @see hook_entity_presave()
- */
-function hook_ENTITY_TYPE_presave(Drupal\Core\Entity\EntityInterface $entity) {
-  if ($entity->isTranslatable()) {
-    $attributes = \Drupal::request()->attributes;
-    \Drupal::service('content_translation.synchronizer')->synchronizeFields($entity, $entity->language()->id, $attributes->get('source_langcode'));
-  }
+  $entity->changed = REQUEST_TIME;
 }
 
 /**
@@ -606,9 +298,6 @@ function hook_ENTITY_TYPE_presave(Drupal\Core\Entity\EntityInterface $entity) {
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity object.
- *
- * @ingroup entity_crud
- * @see hook_ENTITY_TYPE_insert()
  */
 function hook_entity_insert(Drupal\Core\Entity\EntityInterface $entity) {
   // Insert the new entity into a fictional table of all entities.
@@ -623,29 +312,6 @@ function hook_entity_insert(Drupal\Core\Entity\EntityInterface $entity) {
 }
 
 /**
- * Respond to creation of a new entity of a particular type.
- *
- * This hook runs once the entity has been stored. Note that hook
- * implementations may not alter the stored entity data.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity object.
- *
- * @ingroup entity_crud
- * @see hook_entity_insert()
- */
-function hook_ENTITY_TYPE_insert(Drupal\Core\Entity\EntityInterface $entity) {
-  // Insert the new entity into a fictional table of this type of entity.
-  db_insert('example_entity')
-    ->fields(array(
-      'id' => $entity->id(),
-      'created' => REQUEST_TIME,
-      'updated' => REQUEST_TIME,
-    ))
-    ->execute();
-}
-
-/**
  * Respond to updates to an entity.
  *
  * This hook runs once the entity storage has been updated. Note that hook
@@ -653,9 +319,6 @@ function hook_ENTITY_TYPE_insert(Drupal\Core\Entity\EntityInterface $entity) {
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity object.
- *
- * @ingroup entity_crud
- * @see hook_ENTITY_TYPE_update()
  */
 function hook_entity_update(Drupal\Core\Entity\EntityInterface $entity) {
   // Update the entity's entry in a fictional table of all entities.
@@ -669,28 +332,6 @@ function hook_entity_update(Drupal\Core\Entity\EntityInterface $entity) {
 }
 
 /**
- * Respond to updates to an entity of a particular type.
- *
- * This hook runs once the entity storage has been updated. Note that hook
- * implementations may not alter the stored entity data.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity object.
- *
- * @ingroup entity_crud
- * @see hook_entity_update()
- */
-function hook_ENTITY_TYPE_update(Drupal\Core\Entity\EntityInterface $entity) {
-  // Update the entity's entry in a fictional table of this type of entity.
-  db_update('example_entity')
-    ->fields(array(
-      'updated' => REQUEST_TIME,
-    ))
-    ->condition('id', $entity->id())
-    ->execute();
-}
-
-/**
  * Respond to creation of a new entity translation.
  *
  * This hook runs once the entity translation has been stored. Note that hook
@@ -698,9 +339,6 @@ function hook_ENTITY_TYPE_update(Drupal\Core\Entity\EntityInterface $entity) {
  *
  * @param \Drupal\Core\Entity\EntityInterface $translation
  *   The entity object of the translation just stored.
- *
- * @ingroup entity_crud
- * @see hook_ENTITY_TYPE_translation_insert()
  */
 function hook_entity_translation_insert(\Drupal\Core\Entity\EntityInterface $translation) {
   $variables = array(
@@ -711,35 +349,12 @@ function hook_entity_translation_insert(\Drupal\Core\Entity\EntityInterface $tra
 }
 
 /**
- * Respond to creation of a new entity translation of a particular type.
- *
- * This hook runs once the entity translation has been stored. Note that hook
- * implementations may not alter the stored entity translation data.
- *
- * @param \Drupal\Core\Entity\EntityInterface $translation
- *   The entity object of the translation just stored.
- *
- * @ingroup entity_crud
- * @see hook_entity_translation_insert()
- */
-function hook_ENTITY_TYPE_translation_insert(\Drupal\Core\Entity\EntityInterface $translation) {
-  $variables = array(
-    '@language' => $translation->language()->name,
-    '@label' => $translation->getUntranslated()->label(),
-  );
-  \Drupal::logger('example')->notice('The @language translation of @label has just been stored.', $variables);
-}
-
-/**
  * Respond to entity translation deletion.
  *
  * This hook runs once the entity translation has been deleted from storage.
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The original entity object.
- *
- * @ingroup entity_crud
- * @see hook_ENTITY_TYPE_translation_delete()
  */
 function hook_entity_translation_delete(\Drupal\Core\Entity\EntityInterface $translation) {
   $languages = \Drupal::languageManager()->getLanguages();
@@ -751,33 +366,10 @@ function hook_entity_translation_delete(\Drupal\Core\Entity\EntityInterface $tra
 }
 
 /**
- * Respond to entity translation deletion of a particular type.
- *
- * This hook runs once the entity translation has been deleted from storage.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The original entity object.
- *
- * @ingroup entity_crud
- * @see hook_entity_translation_delete()
- */
-function hook_ENTITY_TYPE_translation_delete(\Drupal\Core\Entity\EntityInterface $translation) {
-  $languages = \Drupal::languageManager()->getLanguages();
-  $variables = array(
-    '@language' => $languages[$langcode]->name,
-    '@label' => $entity->label(),
-  );
-  \Drupal::logger('example')->notice('The @language translation of @label has just been deleted.', $variables);
-}
-
-/**
  * Act before entity deletion.
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity object for the entity that is about to be deleted.
- *
- * @ingroup entity_crud
- * @see hook_ENTITY_TYPE_predelete()
  */
 function hook_entity_predelete(Drupal\Core\Entity\EntityInterface $entity) {
   // Count references to this entity in a custom table before they are removed
@@ -799,43 +391,12 @@ function hook_entity_predelete(Drupal\Core\Entity\EntityInterface $entity) {
 }
 
 /**
- * Act before entity deletion of a particular entity type.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity object for the entity that is about to be deleted.
- *
- * @ingroup entity_crud
- * @see hook_entity_predelete()
- */
-function hook_ENTITY_TYPE_predelete(Drupal\Core\Entity\EntityInterface $entity) {
-  // Count references to this entity in a custom table before they are removed
-  // upon entity deletion.
-  $id = $entity->id();
-  $type = $entity->getEntityTypeId();
-  $count = db_select('example_entity_data')
-    ->condition('type', $type)
-    ->condition('id', $id)
-    ->countQuery()
-    ->execute()
-    ->fetchField();
-
-  // Log the count in a table that records this statistic for deleted entities.
-  db_merge('example_deleted_entity_statistics')
-    ->key(array('type' => $type, 'id' => $id))
-    ->fields(array('count' => $count))
-    ->execute();
-}
-
-/**
  * Respond to entity deletion.
  *
  * This hook runs once the entity has been deleted from the storage.
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity object for the entity that has been deleted.
- *
- * @ingroup entity_crud
- * @see hook_ENTITY_TYPE_delete()
  */
 function hook_entity_delete(Drupal\Core\Entity\EntityInterface $entity) {
   // Delete the entity's entry from a fictional table of all entities.
@@ -846,58 +407,15 @@ function hook_entity_delete(Drupal\Core\Entity\EntityInterface $entity) {
 }
 
 /**
- * Respond to entity deletion of a particular type.
- *
- * This hook runs once the entity has been deleted from the storage.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity object for the entity that has been deleted.
- *
- * @ingroup entity_crud
- * @see hook_entity_delete()
- */
-function hook_ENTITY_TYPE_delete(Drupal\Core\Entity\EntityInterface $entity) {
-  // Delete the entity's entry from a fictional table of all entities.
-  db_delete('example_entity')
-    ->condition('type', $entity->getEntityTypeId())
-    ->condition('id', $entity->id())
-    ->execute();
-}
-
-/**
  * Respond to entity revision deletion.
  *
  * This hook runs once the entity revision has been deleted from the storage.
  *
  * @param \Drupal\Core\Entity\EntityInterface $entity
  *   The entity object for the entity revision that has been deleted.
- *
- * @ingroup entity_crud
- * @see hook_ENTITY_TYPE_revision_delete()
  */
 function hook_entity_revision_delete(Drupal\Core\Entity\EntityInterface $entity) {
-  $referenced_files_by_field = _editor_get_file_uuids_by_field($entity);
-  foreach ($referenced_files_by_field as $field => $uuids) {
-    _editor_delete_file_usage($uuids, $entity, 1);
-  }
-}
-
-/**
- * Respond to entity revision deletion of a particular type.
- *
- * This hook runs once the entity revision has been deleted from the storage.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity object for the entity revision that has been deleted.
- *
- * @ingroup entity_crud
- * @see hook_entity_revision_delete()
- */
-function hook_ENTITY_TYPE_revision_delete(Drupal\Core\Entity\EntityInterface $entity) {
-  $referenced_files_by_field = _editor_get_file_uuids_by_field($entity);
-  foreach ($referenced_files_by_field as $field => $uuids) {
-    _editor_delete_file_usage($uuids, $entity, 1);
-  }
+  // @todo: code example
 }
 
 /**
@@ -935,9 +453,9 @@ function hook_entity_query_alter(\Drupal\Core\Entity\Query\QueryInterface $query
  * drupal_render().
  *
  * @see hook_entity_view_alter()
- * @see hook_ENTITY_TYPE_view()
- *
- * @ingroup entity_crud
+ * @see hook_comment_view()
+ * @see hook_node_view()
+ * @see hook_user_view()
  */
 function hook_entity_view(array &$build, \Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
   // Only do the extra work if the component is configured to be displayed.
@@ -952,43 +470,7 @@ function hook_entity_view(array &$build, \Drupal\Core\Entity\EntityInterface $en
 }
 
 /**
- * Act on entities of a particular type being assembled before rendering.
- *
- * @param &$build
- *   A renderable array representing the entity content.
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity object.
- * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
- *   The entity view display holding the display options configured for the
- *   entity components.
- * @param $view_mode
- *   The view mode the entity is rendered in.
- * @param $langcode
- *   The language code used for rendering.
- *
- * The module may add elements to $build prior to rendering. The
- * structure of $build is a renderable array as expected by
- * drupal_render().
- *
- * @see hook_ENTITY_TYPE_view_alter()
- * @see hook_entity_view()
- *
- * @ingroup entity_crud
- */
-function hook_ENTITY_TYPE_view(array &$build, \Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, $view_mode, $langcode) {
-  // Only do the extra work if the component is configured to be displayed.
-  // This assumes a 'mymodule_addition' extra field has been defined for the
-  // entity bundle in hook_entity_extra_field_info().
-  if ($display->getComponent('mymodule_addition')) {
-    $build['mymodule_addition'] = array(
-      '#markup' => mymodule_addition($entity),
-      '#theme' => 'mymodule_my_additional_field',
-    );
-  }
-}
-
-/**
- * Alter the results of the entity build array.
+ * Alter the results of ENTITY_view().
  *
  * This hook is called after the content has been assembled in a structured
  * array and may be used for doing processing which requires that the complete
@@ -1009,9 +491,10 @@ function hook_ENTITY_TYPE_view(array &$build, \Drupal\Core\Entity\EntityInterfac
  *   entity components.
  *
  * @see hook_entity_view()
- * @see hook_ENTITY_TYPE_view_alter()
- *
- * @ingroup entity_crud
+ * @see hook_comment_view_alter()
+ * @see hook_node_view_alter()
+ * @see hook_taxonomy_term_view_alter()
+ * @see hook_user_view_alter()
  */
 function hook_entity_view_alter(array &$build, Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display) {
   if ($build['#view_mode'] == 'full' && isset($build['an_additional_field'])) {
@@ -1024,42 +507,6 @@ function hook_entity_view_alter(array &$build, Drupal\Core\Entity\EntityInterfac
 }
 
 /**
- * Alter the results of the entity build array for a particular entity type.
- *
- * This hook is called after the content has been assembled in a structured
- * array and may be used for doing processing which requires that the complete
- * entity content structure has been built.
- *
- * If a module wishes to act on the rendered HTML of the entity rather than the
- * structured content array, it may use this hook to add a #post_render
- * callback. Alternatively, it could also implement hook_preprocess_HOOK() for
- * the particular entity type template, if there is one (e.g., node.html.twig).
- * See drupal_render() and _theme() for details.
- *
- * @param array &$build
- *   A renderable array representing the entity content.
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity object being rendered.
- * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
- *   The entity view display holding the display options configured for the
- *   entity components.
- *
- * @see hook_ENTITY_TYPE_view()
- * @see hook_entity_view_alter()
- *
- * @ingroup entity_crud
- */
-function hook_ENTITY_TYPE_view_alter(array &$build, Drupal\Core\Entity\EntityInterface $entity, \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display) {
-  if ($build['#view_mode'] == 'full' && isset($build['an_additional_field'])) {
-    // Change its weight.
-    $build['an_additional_field']['#weight'] = -10;
-
-    // Add a #post_render callback to act on the rendered HTML of the entity.
-    $build['#post_render'][] = 'my_module_node_post_render';
-  }
-}
-
-/**
  * Act on entities as they are being prepared for view.
  *
  * Allows you to operate on multiple entities as they are being prepared for
@@ -1075,8 +522,6 @@ function hook_ENTITY_TYPE_view_alter(array &$build, Drupal\Core\Entity\EntityInt
  *   for the entity components, keyed by bundle name.
  * @param string $view_mode
  *   The view mode.
- *
- * @ingroup entity_crud
  */
 function hook_entity_prepare_view($entity_type_id, array $entities, array $displays, $view_mode) {
   // Load a specific node into the user object for later theming.
@@ -1109,8 +554,6 @@ function hook_entity_prepare_view($entity_type_id, array $entities, array $displ
  * @param array $context
  *   Array with additional context information, currently only contains the
  *   langcode the entity is viewed in.
- *
- * @ingroup entity_crud
  */
 function hook_entity_view_mode_alter(&$view_mode, Drupal\Core\Entity\EntityInterface $entity, $context) {
   // For nodes, change the view mode when it is teaser.
@@ -1120,7 +563,7 @@ function hook_entity_view_mode_alter(&$view_mode, Drupal\Core\Entity\EntityInter
 }
 
 /**
- * Alter entity renderable values before cache checking in drupal_render().
+ * Alters entity renderable values before cache checking in drupal_render().
  *
  * Invoked for a specific entity type.
  *
@@ -1139,16 +582,13 @@ function hook_entity_view_mode_alter(&$view_mode, Drupal\Core\Entity\EntityInter
  *
  * @see drupal_render()
  * @see \Drupal\Core\Entity\EntityViewBuilder
- * @see hook_entity_build_defaults_alter()
- *
- * @ingroup entity_crud
  */
 function hook_ENTITY_TYPE_build_defaults_alter(array &$build, \Drupal\Core\Entity\EntityInterface $entity, $view_mode, $langcode) {
 
 }
 
 /**
- * Alter entity renderable values before cache checking in drupal_render().
+ * Alters entity renderable values before cache checking in drupal_render().
  *
  * The values in the #cache key of the renderable array are used to determine if
  * a cache entry exists for the entity's rendered output. Ideally only values
@@ -1165,16 +605,13 @@ function hook_ENTITY_TYPE_build_defaults_alter(array &$build, \Drupal\Core\Entit
  *
  * @see drupal_render()
  * @see \Drupal\Core\Entity\EntityViewBuilder
- * @see hook_ENTITY_TYPE_build_defaults_alter()
- *
- * @ingroup entity_crud
  */
 function hook_entity_build_defaults_alter(array &$build, \Drupal\Core\Entity\EntityInterface $entity, $view_mode, $langcode) {
 
 }
 
 /**
- * Alter the settings used for displaying an entity.
+ * Alters the settings used for displaying an entity.
  *
  * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
  *   The entity view display that will be used to display the entity
@@ -1184,8 +621,6 @@ function hook_entity_build_defaults_alter(array &$build, \Drupal\Core\Entity\Ent
  *   - entity_type: The entity type, e.g., 'node' or 'user'.
  *   - bundle: The bundle, e.g., 'page' or 'article'.
  *   - view_mode: The view mode, e.g. 'full', 'teaser'...
- *
- * @ingroup entity_crud
  */
 function hook_entity_view_display_alter(\Drupal\Core\Entity\Display\EntityViewDisplayInterface $display, array $context) {
   // Leave field labels out of the search index.
@@ -1209,8 +644,6 @@ function hook_entity_view_display_alter(\Drupal\Core\Entity\Display\EntityViewDi
  *   - entity: The entity being rendered.
  *   - view_mode: The view mode; for example, 'full' or 'teaser'.
  *   - display: The EntityDisplay holding the display options.
- *
- * @ingroup entity_crud
  */
 function hook_entity_display_build_alter(&$build, $context) {
   // Append RDF term mappings on displayed taxonomy links.
@@ -1245,9 +678,6 @@ function hook_entity_display_build_alter(&$build, $context) {
  *   An associative array containing the current state of the form.
  *
  * @see \Drupal\Core\Entity\EntityForm::prepareEntity()
- * @see hook_ENTITY_TYPE_prepare_form()
- *
- * @ingroup entity_crud
  */
 function hook_entity_prepare_form(\Drupal\Core\Entity\EntityInterface $entity, $operation, array &$form_state) {
   if ($operation == 'edit') {
@@ -1257,33 +687,7 @@ function hook_entity_prepare_form(\Drupal\Core\Entity\EntityInterface $entity, $
 }
 
 /**
- * Acts on a particular type of entity object about to be in an entity form.
- *
- * This can be typically used to pre-fill entity values or change the form state
- * before the entity form is built. It is invoked just once when first building
- * the entity form. Rebuilds will not trigger a new invocation.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity that is about to be shown on the form.
- * @param $operation
- *   The current operation.
- * @param array $form_state
- *   An associative array containing the current state of the form.
- *
- * @see \Drupal\Core\Entity\EntityForm::prepareEntity()
- * @see hook_entity_prepare_form()
- *
- * @ingroup entity_crud
- */
-function hook_ENTITY_TYPE_prepare_form(\Drupal\Core\Entity\EntityInterface $entity, $operation, array &$form_state) {
-  if ($operation == 'edit') {
-    $entity->label->value = 'Altered label';
-    $form_state['mymodule']['label_altered'] = TRUE;
-  }
-}
-
-/**
- * Alter the settings used for displaying an entity form.
+ * Alters the settings used for displaying an entity form.
  *
  * @param \Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display
  *   The entity_form_display object that will be used to display the entity form
@@ -1293,8 +697,6 @@ function hook_ENTITY_TYPE_prepare_form(\Drupal\Core\Entity\EntityInterface $enti
  *   - entity_type: The entity type, e.g., 'node' or 'user'.
  *   - bundle: The bundle, e.g., 'page' or 'article'.
  *   - form_mode: The form mode, e.g. 'default', 'profile', 'register'...
- *
- * @ingroup entity_crud
  */
 function hook_entity_form_display_alter(\Drupal\Core\Entity\Display\EntityFormDisplayInterface $form_display, array $context) {
   // Hide the 'user_picture' field from the register form.
@@ -1334,7 +736,7 @@ function hook_entity_base_field_info(\Drupal\Core\Entity\EntityTypeInterface $en
 }
 
 /**
- * Alter base field definitions for a content entity type.
+ * Alters base field definitions for a content entity type.
  *
  * @param \Drupal\Core\Field\FieldDefinitionInterface[] $fields
  *   The array of base field definitions for the entity type.
@@ -1390,7 +792,7 @@ function hook_entity_bundle_field_info(\Drupal\Core\Entity\EntityTypeInterface $
 }
 
 /**
- * Alter bundle field definitions.
+ * Alters bundle field definitions.
  *
  * @param \Drupal\Core\Field\FieldDefinitionInterface[] $fields
  *   The array of bundle field definitions.
@@ -1443,7 +845,7 @@ function hook_entity_field_storage_info(\Drupal\Core\Entity\EntityTypeInterface
 }
 
 /**
- * Alter field storage definitions for a content entity type.
+ * Alters field storage definitions for a content entity type.
  *
  * @param \Drupal\Core\Field\FieldStorageDefinitionInterface[] $fields
  *   The array of field storage definitions for the entity type.
@@ -1521,12 +923,12 @@ function hook_entity_operation_alter(array &$operations, \Drupal\Core\Entity\Ent
  */
 function hook_entity_field_access($operation, \Drupal\Core\Field\FieldDefinitionInterface $field_definition, \Drupal\Core\Session\AccountInterface $account, \Drupal\Core\Field\FieldItemListInterface $items = NULL) {
   if ($field_definition->getName() == 'field_of_interest' && $operation == 'edit') {
-    return $account->hasPermission('update field of interest');
+    return user_access('update field of interest', $account);
   }
 }
 
 /**
- * Alter the default access behavior for a given field.
+ * Alters the default access behavior for a given field.
  *
  * Use this hook to override access grants from another module. Note that the
  * original default access flag is masked under the ':default' key.
diff --git a/core/modules/system/src/Tests/Update/UpdateScriptTest.php b/core/modules/system/src/Tests/Update/UpdateScriptTest.php
index a040529..7c1fa5a 100644
--- a/core/modules/system/src/Tests/Update/UpdateScriptTest.php
+++ b/core/modules/system/src/Tests/Update/UpdateScriptTest.php
@@ -77,11 +77,7 @@ function testUpdateAccess() {
     $this->assertResponse(200);
 
     // Access the update page as user 1.
-    $user1 = user_load(1);
-    $user1->pass_raw = user_password();
-    $user1->pass = $this->container->get('password')->hash(trim($user1->pass_raw));
-    db_query("UPDATE {users} SET pass = :pass WHERE uid = :uid", array(':pass' => $user1->getPassword(), ':uid' => $user1->id()));
-    $this->drupalLogin($user1);
+    $this->drupalLogin($this->root_user);
     $this->drupalGet($this->update_url, array('external' => TRUE));
     $this->assertResponse(200);
   }
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
index 01e6872..7b7efaf 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTest.php
@@ -32,7 +32,7 @@
  *   },
  *   base_table = "entity_test",
  *   fieldable = TRUE,
- *   field_cache = FALSE,
+ *   persistent_cache = FALSE,
  *   entity_keys = {
  *     "id" = "id",
  *     "uuid" = "uuid",
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestCache.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestCache.php
index 07d0e75..ab5fdab 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestCache.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestCache.php
@@ -22,7 +22,6 @@
  *   },
  *   base_table = "entity_test",
  *   fieldable = TRUE,
- *   field_cache = TRUE,
  *   entity_keys = {
  *     "id" = "id",
  *     "uuid" = "uuid",
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestLabelCallback.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestLabelCallback.php
index e881b9a..e15d389 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestLabelCallback.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestLabelCallback.php
@@ -13,7 +13,7 @@
  * @ContentEntityType(
  *   id = "entity_test_label_callback",
  *   label = @Translation("Entity test label callback"),
- *   field_cache = FALSE,
+ *   persistent_cache = FALSE,
  *   base_table = "entity_test",
  *   label_callback = "entity_test_label_callback",
  *   fieldable = TRUE,
diff --git a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestNoLabel.php b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestNoLabel.php
index 6dc35d4..5ad3ba7 100644
--- a/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestNoLabel.php
+++ b/core/modules/system/tests/modules/entity_test/src/Entity/EntityTestNoLabel.php
@@ -13,7 +13,7 @@
  * @ContentEntityType(
  *   id = "entity_test_no_label",
  *   label = @Translation("Entity Test without label"),
- *   field_cache = FALSE,
+ *   persistent_cache = FALSE,
  *   base_table = "entity_test",
  *   entity_keys = {
  *     "id" = "id",
diff --git a/core/modules/tracker/src/Tests/TrackerNodeAccessTest.php b/core/modules/tracker/src/Tests/TrackerNodeAccessTest.php
index f5e00b1..486471e 100644
--- a/core/modules/tracker/src/Tests/TrackerNodeAccessTest.php
+++ b/core/modules/tracker/src/Tests/TrackerNodeAccessTest.php
@@ -28,6 +28,7 @@ public function setUp() {
     parent::setUp();
     node_access_rebuild();
     $this->drupalCreateContentType(array('type' => 'page'));
+    node_access_test_add_field(entity_load('node_type', 'page'));
     $this->container->get('comment.manager')->addDefaultField('node', 'page', 'comment', CommentItemInterface::OPEN);
     \Drupal::state()->set('node_access_test.private', TRUE);
   }
diff --git a/core/modules/user/src/UserStorage.php b/core/modules/user/src/UserStorage.php
index 44d323f..3a9d9f5 100644
--- a/core/modules/user/src/UserStorage.php
+++ b/core/modules/user/src/UserStorage.php
@@ -8,6 +8,7 @@
 namespace Drupal\user;
 
 use Drupal\Component\Uuid\UuidInterface;
+use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
@@ -32,13 +33,6 @@ class UserStorage extends ContentEntityDatabaseStorage implements UserStorageInt
   protected $password;
 
   /**
-   * Provides the user data service object.
-   *
-   * @var \Drupal\user\UserDataInterface
-   */
-  protected $userData;
-
-  /**
    * Constructs a new UserStorage object.
    *
    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
@@ -47,16 +41,15 @@ class UserStorage extends ContentEntityDatabaseStorage implements UserStorageInt
    *   The database connection to be used.
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
    * @param \Drupal\Core\Password\PasswordInterface $password
    *   The password hashing service.
-   * @param \Drupal\user\UserDataInterface $user_data
-   *   The user data service.
    */
-  public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager, PasswordInterface $password, UserDataInterface $user_data) {
-    parent::__construct($entity_type, $database, $entity_manager);
+  public function __construct(EntityTypeInterface $entity_type, Connection $database, EntityManagerInterface $entity_manager, CacheBackendInterface $cache, PasswordInterface $password) {
+    parent::__construct($entity_type, $database, $entity_manager, $cache);
 
     $this->password = $password;
-    $this->userData = $user_data;
   }
 
   /**
@@ -67,8 +60,8 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
       $entity_type,
       $container->get('database'),
       $container->get('entity.manager'),
-      $container->get('password'),
-      $container->get('user.data')
+      $container->get('cache.entity'),
+      $container->get('password')
     );
   }
 
@@ -149,6 +142,8 @@ public function updateLastLoginTimestamp(UserInterface $account) {
       ->fields(array('login' => $account->getLastLoginTime()))
       ->condition('uid', $account->id())
       ->execute();
+    // Ensure that the entity cache is cleared.
+    $this->resetCache(array($account->id()));
   }
 
   /**
diff --git a/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php b/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php
index 960f735..ae9fbcb 100644
--- a/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php
+++ b/core/tests/Drupal/Tests/Core/Entity/ContentEntityDatabaseStorageTest.php
@@ -47,6 +47,41 @@ class ContentEntityDatabaseStorageTest extends UnitTestCase {
   protected $entityManager;
 
   /**
+   * The entity type ID.
+   *
+   * @var string
+   */
+  protected $entityTypeId = 'entity_test';
+
+  /**
+   * The dependency injection container.
+   *
+   * @var \Symfony\Component\DependencyInjection\ContainerBuilder
+   */
+  protected $container;
+
+  /**
+   * The module handler.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $moduleHandler;
+
+  /**
+   * The cache backend to use.
+   *
+   * @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $cache;
+
+  /**
+   * The database connection to use.
+   *
+   * @var \Drupal\Core\Database\Connection|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $connection;
+
+  /**
    * {@inheritdoc}
    */
   public function setUp() {
