diff --git a/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php b/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php
index 6ee7cee..ff12175 100644
--- a/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php
+++ b/core/modules/edit/lib/Drupal/edit/Tests/EditLoadingTest.php
@@ -143,9 +143,10 @@ public function testUserWithPermission() {
     $this->assertRaw('data-edit-field-id="node/1/body/und/full"');
 
     // There should be only one revision so far.
-    $revisions = node_revision_list(node_load(1));
-    $this->assertIdentical(1, count($revisions), 'The node has only one revision.');
-    $original_log = $revisions[1]->log;
+    $node = node_load(1);
+    $vids = \Drupal::entityManager()->getStorage('node')->revisionIds($node);
+    $this->assertIdentical(1, count($vids), 'The node has only one revision.');
+    $original_log = $node->log->value;
 
     // Retrieving the metadata should result in a 200 JSON response.
     $htmlPageDrupalSettings = $this->drupalSettings;
@@ -237,9 +238,10 @@ public function testUserWithPermission() {
       $this->assertText('Fine thanks.');
 
       // Ensure no new revision was created and the log message is unchanged.
-      $revisions = node_revision_list(node_load(1));
-      $this->assertIdentical(1, count($revisions), 'The node has only one revision.');
-      $this->assertIdentical($original_log, $revisions[1]->log, 'The revision log message is unchanged.');
+      $node = node_load(1);
+      $vids = \Drupal::entityManager()->getStorage('node')->revisionIds($node);
+      $this->assertIdentical(1, count($vids), 'The node has only one revision.');
+      $this->assertIdentical($original_log, $node->log->value, 'The revision log message is unchanged.');
 
       // Now configure this node type to create new revisions automatically,
       // then again retrieve the field form, fill it, submit it (so it ends up
@@ -285,10 +287,12 @@ public function testUserWithPermission() {
       $this->assertResponse(200);
 
       // Test that a revision was created with the correct log message.
-      $revisions = node_revision_list(node_load(1));
-      $this->assertIdentical(2, count($revisions), 'The node has two revisions.');
-      $this->assertIdentical($original_log, $revisions[1]->log, 'The first revision log message is unchanged.');
-      $this->assertIdentical('Updated the <em class="placeholder">Body</em> field through in-place editing.', $revisions[2]->log, 'The second revision log message was correctly generated by Edit module.');
+      $vids = \Drupal::entityManager()->getStorage('node')->revisionIds(node_load(1));
+      $this->assertIdentical(2, count($vids), 'The node has two revisions.');
+      $revision = node_revision_load($vids[0]);
+      $this->assertIdentical($original_log, $revision->log->value, 'The first revision log message is unchanged.');
+      $revision = node_revision_load($vids[1]);
+      $this->assertIdentical('Updated the <em class="placeholder">Body</em> field through in-place editing.', $revision->log->value, 'The second revision log message was correctly generated by Edit module.');
     }
   }
 
diff --git a/core/modules/node/lib/Drupal/node/Entity/Node.php b/core/modules/node/lib/Drupal/node/Entity/Node.php
index 6dada8f..537c61f 100644
--- a/core/modules/node/lib/Drupal/node/Entity/Node.php
+++ b/core/modules/node/lib/Drupal/node/Entity/Node.php
@@ -24,6 +24,7 @@
  *   label = @Translation("Content"),
  *   bundle_label = @Translation("Content type"),
  *   controllers = {
+ *     "storage" = "Drupal\node\NodeStorage",
  *     "view_builder" = "Drupal\node\NodeViewBuilder",
  *     "access" = "Drupal\node\NodeAccessController",
  *     "form" = {
diff --git a/core/modules/node/lib/Drupal/node/NodeStorage.php b/core/modules/node/lib/Drupal/node/NodeStorage.php
new file mode 100644
index 0000000..5ac30cb
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/NodeStorage.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\node\NodeStorageController.
+ */
+
+namespace Drupal\node;
+
+use Drupal\Core\Entity\ContentEntityDatabaseStorage;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Language\Language;
+
+/**
+ * Defines the controller class for nodes.
+ *
+ * This extends the Drupal\Core\Entity\DatabaseStorageController class, adding
+ * required special handling for node entities.
+ */
+class NodeStorage extends ContentEntityDatabaseStorage implements NodeStorageInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function revisionIds(NodeInterface $node) {
+    return $this->database->select('node_revision', 'r')
+      ->fields('r', array('vid'))
+      ->condition('r.nid', $node->id())
+      ->orderBy('r.vid', 'ASC')
+      ->execute()
+      ->fetchCol();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function userRevisions(AccountInterface $account) {
+    return $this->database->select('node_field_revision', 'n')
+      ->fields('n', array('vid'))
+      ->condition('uid', $account->id())
+      ->execute()
+      ->fetchCol();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function updateType($old_id, $new_id) {
+    return $this->database->update('node')
+      ->fields(array('type' => $new_id))
+      ->condition('type', $old_id)
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteRevisionsLanguage($language) {
+    return $this->database->update('node_revision')
+      ->fields(array('langcode' => Language::LANGCODE_NOT_SPECIFIED))
+      ->condition('langcode', $language->id)
+      ->execute();
+  }
+}
diff --git a/core/modules/node/lib/Drupal/node/NodeStorageInterface.php b/core/modules/node/lib/Drupal/node/NodeStorageInterface.php
new file mode 100644
index 0000000..974924d
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/NodeStorageInterface.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\NodeStorageControllerInterface.
+ */
+
+namespace Drupal\node;
+
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Session\AccountInterface;
+
+/**
+ * Defines a common interface for node entity controller classes.
+ */
+interface NodeStorageInterface extends EntityStorageInterface {
+
+  /**
+   * Returns a list of data for all revisions of a specific node.
+   *
+   * @param \Drupal\node\NodeInterface
+   *   The node entity.
+   *
+   * @return int[]
+   *   Revision IDs (in ascending order).
+   */
+  public function revisionIds(NodeInterface $node);
+
+  /**
+   * Retrieve a list of revisions for a given user.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The user entity.
+   *
+   * @return array
+   *   Revision node ids.
+   */
+  public function userRevisions(AccountInterface $account);
+
+  /**
+   * Updates all nodes of one type to be of another type.
+   *
+   * @param string $old_id
+   *   The current node type of the nodes.
+   * @param string $new_id
+   *   The new node type of the nodes.
+   *
+   * @return int
+   *   The number of nodes whose node type field was modified.
+   */
+  public function updateType($old_id, $new_id);
+
+  /**
+   * On nodes with this language, unset the language.
+   *
+   * @param $language
+   *  The language object.
+   */
+  public function deleteRevisionsLanguage($language);
+}
diff --git a/core/modules/node/node.admin.inc b/core/modules/node/node.admin.inc
index a79328e..b0a806d 100644
--- a/core/modules/node/node.admin.inc
+++ b/core/modules/node/node.admin.inc
@@ -26,14 +26,17 @@
  * @param bool $load
  *   (optional) TRUE if $nodes contains an array of node IDs to be loaded, FALSE
  *   if it contains fully loaded nodes. Defaults to FALSE.
+ * @param bool $revisions
+ *   (optional) TRUE if $nodes contains an array of revision IDs instead of
+ *   node IDs. Defaults to FALSE; will be ignored if $load is FALSE.
  */
-function node_mass_update(array $nodes, array $updates, $langcode = NULL, $load = FALSE) {
+function node_mass_update(array $nodes, array $updates, $langcode = NULL, $load = FALSE, $revisions = FALSE) {
   // We use batch processing to prevent timeout when updating a large number
   // of nodes.
   if (count($nodes) > 10) {
     $batch = array(
       'operations' => array(
-        array('_node_mass_update_batch_process', array($nodes, $updates, $langcode, $load))
+        array('_node_mass_update_batch_process', array($nodes, $updates, $langcode, $load, $revisions))
       ),
       'finished' => '_node_mass_update_batch_finished',
       'title' => t('Processing'),
@@ -48,10 +51,13 @@ function node_mass_update(array $nodes, array $updates, $langcode = NULL, $load
     batch_set($batch);
   }
   else {
-    if ($load) {
+    if ($load && !$revisions) {
       $nodes = entity_load_multiple('node', $nodes);
     }
     foreach ($nodes as $node) {
+      if ($load && $revisions) {
+        $node = entity_revision_load('node', $node);
+      }
       _node_mass_update_helper($node, $updates, $langcode);
     }
     drupal_set_message(t('The update has been performed.'));
@@ -97,10 +103,13 @@ function _node_mass_update_helper(NodeInterface $node, array $updates, $langcode
  * @param bool $load
  *   TRUE if $nodes contains an array of node IDs to be loaded, FALSE if it
  *   contains fully loaded nodes.
+ * @param bool $revisions
+ *   (optional) TRUE if $nodes contains an array of revision IDs instead of
+ *   node IDs. Defaults to FALSE; will be ignored if $load is FALSE.
  * @param array $context
  *   An array of contextual key/values.
  */
-function _node_mass_update_batch_process(array $nodes, array $updates, $load, array &$context) {
+function _node_mass_update_batch_process(array $nodes, array $updates, $load, $revisions, array &$context) {
   if (!isset($context['sandbox']['progress'])) {
     $context['sandbox']['progress'] = 0;
     $context['sandbox']['max'] = count($nodes);
@@ -113,7 +122,8 @@ function _node_mass_update_batch_process(array $nodes, array $updates, $load, ar
     // For each nid, load the node, reset the values, and save it.
     $node = array_shift($context['sandbox']['nodes']);
     if ($load) {
-      $node = entity_load('node', $node);
+      $node = $revisions ?
+        entity_revision_load('node', $node) : entity_load('node', $node);
     }
     $node = _node_mass_update_helper($node, $updates);
 
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 66145b0..6caacc0 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -489,10 +489,7 @@ function node_entity_extra_field_info() {
  *   The number of nodes whose node type field was modified.
  */
 function node_type_update_nodes($old_id, $new_id) {
-  return db_update('node')
-    ->fields(array('type' => $new_id))
-    ->condition('type', $old_id)
-    ->execute();
+  return \Drupal::entityManager()->getStorage('node')->updateType($old_id, $new_id);
 }
 
 /**
@@ -813,31 +810,18 @@ function node_user_cancel($edit, $account, $method) {
   switch ($method) {
     case 'user_cancel_block_unpublish':
       // Unpublish nodes (current revisions).
-      module_load_include('inc', 'node', 'node.admin');
-      $nodes = db_select('node_field_data', 'n')
-        ->distinct()
-        ->fields('n', array('nid'))
+      $nids = \Drupal::entityQuery('node')
         ->condition('uid', $account->id())
-        ->execute()
-        ->fetchCol();
-      node_mass_update($nodes, array('status' => 0), NULL, TRUE);
+        ->execute();
+      module_load_include('inc', 'node', 'node.admin');
+      node_mass_update($nids, array('status' => 0), NULL, TRUE);
       break;
 
     case 'user_cancel_reassign':
-      // Anonymize nodes (current revisions).
+      // Anonymize all of the nodes for this old account.
       module_load_include('inc', 'node', 'node.admin');
-      $nodes = db_select('node_field_data', 'n')
-        ->distinct()
-        ->fields('n', array('nid'))
-        ->condition('uid', $account->id())
-        ->execute()
-        ->fetchCol();
-      node_mass_update($nodes, array('uid' => 0), NULL, TRUE);
-      // Anonymize old revisions.
-      db_update('node_field_revision')
-        ->fields(array('uid' => 0))
-        ->condition('uid', $account->id())
-        ->execute();
+      $vids = \Drupal::entityManager()->getStorage('node')->userRevisions($account);
+      node_mass_update($vids, array('uid' => 0), NULL, TRUE, TRUE);
       break;
   }
 }
@@ -848,15 +832,13 @@ function node_user_cancel($edit, $account, $method) {
 function node_user_predelete($account) {
   // Delete nodes (current revisions).
   // @todo Introduce node_mass_delete() or make node_mass_update() more flexible.
-  $nodes = db_select('node_field_data', 'n')
-    ->distinct()
-    ->fields('n', array('nid'))
+  $nids = \Drupal::entityQuery('node')
     ->condition('uid', $account->id())
-    ->execute()
-    ->fetchCol();
-  entity_delete_multiple('node', $nodes);
+    ->execute();
+  entity_delete_multiple('node', $nids);
   // Delete old revisions.
-  $revisions = db_query('SELECT DISTINCT vid FROM {node_field_revision} WHERE uid = :uid', array(':uid' => $account->id()))->fetchCol();
+  $storage_controller = \Drupal::entityManager()->getStorage('node');
+  $revisions = $storage_controller->userRevisions($account);
   foreach ($revisions as $revision) {
     node_revision_delete($revision);
   }
@@ -952,32 +934,24 @@ function node_page_title(NodeInterface $node) {
  *   A unix timestamp indicating the last time the node was changed.
  */
 function node_last_changed($nid, $langcode = NULL) {
+  $query = \Drupal::entityQuery('node')
+    ->condition('nid', $nid);
   if (isset($langcode)) {
-    $result = db_query('SELECT changed FROM {node_field_data} WHERE nid = :nid AND langcode = :langcode', array(':nid' => $nid, ':langcode' => $langcode))->fetch();
+    $query->condition('langcode', $langcode);
   }
   else {
-    $result = db_query('SELECT changed FROM {node_field_data} WHERE nid = :nid AND default_langcode = :default_langcode', array(':nid' => $nid, ':default_langcode' => 1))->fetch();
+    $query->condition('default_langcode', 1);
   }
-  return is_object($result) ? $result->changed : FALSE;
-}
+  $nids = $query
+    ->addTag('node_access')
+    ->execute();
 
-/**
- * Returns a list of all the existing revision numbers for the node passed in.
- *
- * @param \Drupal\node\NodeInterface $node
- *   The node entity.
- *
- * @return
- *   An associative array keyed by node revision number.
- */
-function node_revision_list(NodeInterface $node) {
-  $revisions = array();
-  $result = db_query('SELECT nr.vid, nfr.title, nr.log, nr.revision_uid AS uid, n.vid AS current_vid, nr.revision_timestamp, u.name FROM {node_field_revision} nfr JOIN {node_revision} nr ON nr.vid = nfr.vid LEFT JOIN {node} n ON n.vid = nfr.vid INNER JOIN {users} u ON u.uid = nr.revision_uid WHERE nfr.nid = :nid AND nfr.default_langcode = 1 ORDER BY nfr.vid DESC', array(':nid' => $node->id()));
-  foreach ($result as $revision) {
-    $revisions[$revision->vid] = $revision;
+  if ($nids) {
+    $nodes = node_load_multiple($nids);
+    return reset($nodes)->getChangedTime();
   }
 
-  return $revisions;
+  return FALSE;
 }
 
 /**
@@ -991,31 +965,31 @@ function node_revision_list(NodeInterface $node) {
  *   visible to the current user.
  */
 function node_get_recent($number = 10) {
-  $query = db_select('node_field_data', 'n');
+  $account = \Drupal::currentUser();
+  $query = \Drupal::entityQuery('node');
 
-  if (!user_access('bypass node access')) {
+  if (!$account->hasPermission('bypass node access')) {
     // If the user is able to view their own unpublished nodes, allow them
     // to see these in addition to published nodes. Check that they actually
     // have some unpublished nodes to view before adding the condition.
-    if (user_access('view own unpublished content') && $own_unpublished = db_query('SELECT DISTINCT nid FROM {node_field_data} WHERE uid = :uid AND status = :status', array(':uid' => \Drupal::currentUser()->id(), ':status' => NODE_NOT_PUBLISHED))->fetchCol()) {
-      $query->condition(db_or()
-        ->condition('n.status', NODE_PUBLISHED)
-        ->condition('n.nid', $own_unpublished, 'IN')
-      );
+    $access_query =  \Drupal::entityQuery('node')
+      ->condition('uid', $account->id())
+      ->condition('status', NODE_NOT_PUBLISHED);
+    if ($account->hasPermission('view own unpublished content') && ($own_unpublished = $access_query->execute())) {
+      $query->orConditionGroup()
+        ->condition('status', NODE_PUBLISHED)
+        ->condition('nid', $own_unpublished, 'IN');
     }
     else {
       // If not, restrict the query to published nodes.
-      $query->condition('n.status', NODE_PUBLISHED);
+      $query->condition('status', NODE_PUBLISHED);
     }
-  }
+   }
   $nids = $query
-    ->distinct()
-    ->fields('n', array('nid'))
-    ->orderBy('n.changed', 'DESC')
+    ->sort('changed', 'DESC')
     ->range(0, $number)
     ->addTag('node_access')
-    ->execute()
-    ->fetchCol();
+    ->execute();
 
   $nodes = node_load_multiple($nids);
 
@@ -1118,16 +1092,13 @@ function node_feed($nids = FALSE, $channel = array()) {
   $rss_config = \Drupal::config('system.rss');
 
   if ($nids === FALSE) {
-    $nids = db_select('node_field_data', 'n')
-      ->distinct()
-      ->fields('n', array('nid'))
-      ->condition('n.promote', 1)
-      ->condition('n.status', 1)
-      ->orderBy('n.created', 'DESC')
+    $nids = \Drupal::entityQuery('node')
+      ->condition('status', 1)
+      ->condition('promote', 1)
+      ->sort('created', 'DESC')
       ->range(0, $rss_config->get('items.limit'))
       ->addTag('node_access')
-      ->execute()
-      ->fetchCol();
+      ->execute();
   }
 
   $item_length = $rss_config->get('items.view_mode');
@@ -1312,9 +1283,9 @@ function node_form_system_themes_admin_form_submit($form, &$form_state) {
  * the process above is followed except that hook_node_access() is not called on
  * each node for performance reasons and for proper functioning of the pager
  * system. When adding a node listing to your module, be sure to use a dynamic
- * query created by db_select() and add a tag of "node_access". This will allow
- * modules dealing with node access to ensure only nodes to which the user has
- * access are retrieved, through the use of hook_query_TAG_alter().
+ * entity query and add a tag of "node_access". This will allow modules dealing
+ * with node access to ensure only nodes to which the user has access are
+ * retrieved, through the use of hook_query_TAG_alter().
  *
  * Note: Even a single module returning NODE_ACCESS_DENY from hook_node_access()
  * will block access to the node. Therefore, implementers should take care to
@@ -1795,10 +1766,7 @@ function node_file_download_access($field, EntityInterface $entity, File $file)
  */
 function node_language_entity_delete(LanguageEntity $language) {
   // On nodes with this language, unset the language.
-  db_update('node_revision')
-    ->fields(array('langcode' => ''))
-    ->condition('langcode', $language->id())
-    ->execute();
+  \Drupal::entityManager()->getStorage('node')->deleteRevisionsLanguage($language);
 }
 
 /**
diff --git a/core/modules/node/node.pages.inc b/core/modules/node/node.pages.inc
index fe5b0c1..12baf9f 100644
--- a/core/modules/node/node.pages.inc
+++ b/core/modules/node/node.pages.inc
@@ -120,8 +120,6 @@ function node_revision_overview($node) {
 
   $header = array(t('Revision'), t('Operations'));
 
-  $revisions = node_revision_list($node);
-
   $rows = array();
   $type = $node->getType();
 
@@ -133,45 +131,49 @@ function node_revision_overview($node) {
   if ((user_access("delete $type revisions") || user_access('delete all revisions') || user_access('administer nodes')) && $node->access('delete')) {
     $delete_permission = TRUE;
   }
-  foreach ($revisions as $revision) {
-    $row = array();
-    if ($revision->current_vid > 0) {
-      $username = array(
-        '#theme' => 'username',
-        '#account' => user_load($revision->uid),
-      );
-      $row[] = array('data' => t('!date by !username', array('!date' => l(format_date($revision->revision_timestamp, 'short'), 'node/' . $node->id()), '!username' => drupal_render($username)))
-                               . (($revision->log != '') ? '<p class="revision-log">' . filter_xss($revision->log) . '</p>' : ''),
-                     'class' => array('revision-current'));
-      $row[] = array('data' => drupal_placeholder(t('current revision')), 'class' => array('revision-current'));
-    }
-    else {
-      $username = array(
-        '#theme' => 'username',
-        '#account' => user_load($revision->uid),
-      );
-      $row[] = t('!date by !username', array('!date' => l(format_date($revision->revision_timestamp, 'short'), "node/" . $node->id() . "/revisions/" . $revision->vid . "/view"), '!username' => drupal_render($username)))
-               . (($revision->log != '') ? '<p class="revision-log">' . filter_xss($revision->log) . '</p>' : '');
-      if ($revert_permission) {
-        $links['revert'] = array(
-          'title' => t('Revert'),
-          'href' => "node/" . $node->id() . "/revisions/" . $revision->vid . "/revert",
+  $vids = \Drupal::entityManager()->getStorage('node')->revisionIds($node);
+  foreach (array_reverse($vids) as $vid) {
+    if ($revision = node_revision_load($vid)) {
+
+      $row = array();
+      if ($vid == $node->getRevisionId()) {
+        $username = array(
+          '#theme' => 'username',
+          '#account' => $revision->getOwner(),
         );
+        $row[] = array('data' => t('!date by !username', array('!date' => l(format_date($revision->getRevisionCreationTime(), 'short'), 'node/' . $node->id()), '!username' => drupal_render($username)))
+                                 . (($revision->log->value != '') ? '<p class="revision-log">' . filter_xss($revision->log->value) . '</p>' : ''),
+                       'class' => array('revision-current'));
+        $row[] = array('data' => drupal_placeholder(t('current revision')), 'class' => array('revision-current'));
       }
-      if ($delete_permission) {
-        $links['delete'] = array(
-          'title' => t('Delete'),
-          'href' => "node/" . $node->id() . "/revisions/" . $revision->vid . "/delete",
+      else {
+        $username = array(
+          '#theme' => 'username',
+          '#account' => $revision->getOwner(),
+        );
+        $row[] = t('!date by !username', array('!date' => l(format_date($revision->getRevisionCreationTime(), 'short'), "node/" . $node->id() . "/revisions/" . $vid . "/view"), '!username' => drupal_render($username)))
+                 . (($revision->log->value != '') ? '<p class="revision-log">' . filter_xss($revision->log->value) . '</p>' : '');
+        if ($revert_permission) {
+          $links['revert'] = array(
+            'title' => t('Revert'),
+            'href' => "node/" . $node->id() . "/revisions/" . $vid . "/revert",
+          );
+        }
+        if ($delete_permission) {
+          $links['delete'] = array(
+            'title' => t('Delete'),
+            'href' => "node/" . $node->id() . "/revisions/" . $vid . "/delete",
+          );
+        }
+        $row[] = array(
+          'data' => array(
+            '#type' => 'operations',
+            '#links' => $links,
+          ),
         );
       }
-      $row[] = array(
-        'data' => array(
-          '#type' => 'operations',
-          '#links' => $links,
-        ),
-      );
+      $rows[] = $row;
     }
-    $rows[] = $row;
   }
 
   $build['node_revisions_table'] = array(
