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..38cb869
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/NodeStorage.php
@@ -0,0 +1,81 @@
+<?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 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 anonymizeUserRevisions(AccountInterface $account) {
+    $this->database->update('node_field_revision')
+      ->fields(array('uid' => 0))
+      ->condition('uid', $account->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();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function revisionsList(NodeInterface $node) {
+    $query = $this->database->select('node_field_revision', 'nfr');
+    $query->innerJoin('node_revision', 'nr', 'nr.vid = nfr.vid');
+    $query->leftJoin('node', 'n', 'n.vid = nfr.vid');
+    $query->innerJoin('users', 'u', 'u.uid = nr.revision_uid');
+    $query->condition('nfr.nid', $node->id())
+      ->condition('nfr.default_langcode', 1)
+      ->orderBy('nfr.vid', 'DESC')
+      ->fields('nr', array('vid', 'log', 'revision_timestamp'));
+    $query->addField('nr', 'revision_uid', 'uid');
+    $query->addField('nfr', 'title');
+    $query->addField('n', 'vid', 'current_vid');
+    $query->addField('u', 'name');
+    return $query->execute()->fetchAllAssoc('vid');
+  }
+}
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..8dbdfb7
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/NodeStorageInterface.php
@@ -0,0 +1,77 @@
+<?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 {
+
+  /**
+   * 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);
+
+  /**
+   * Anonymize the node revisions of the given account.
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The user entity.
+   */
+  public function anonymizeUserRevisions(AccountInterface $account);
+
+  /**
+   * On nodes with this language, unset the language.
+   *
+   * @param $language
+   *  The language object.
+   */
+  public function deleteRevisionsLanguage($language);
+
+  /**
+   * Returns a list of data for all revisions of a specific node.
+   *
+   * @param \Drupal\node\NodeInterface
+   *   The node entity.
+   *
+   * @return mixed
+   *   An associative array of objects containing specific properties (empty
+   *   array if no result set), keyed by revision id. The keys of object
+   *   properties are:
+   *   - vid
+   *   - current_vid (vid of the _current_ node, not this revision)
+   *   - title
+   *   - revision_timestamp
+   *   - log
+   *   - uid
+   *   - name (user name linked to uid)
+   */
+  public function revisionsList(NodeInterface $node);
+}
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 7c429ae..c75de6c 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -491,10 +491,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);
   }
@@ -977,13 +959,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();
+
+  if ($nids) {
+    $nodes = node_load_multiple($nids);
+    return reset($nodes)->getChangedTime();
+  }
+
+  return FALSE;
 }
 
 /**
@@ -996,13 +989,7 @@ function node_last_changed($nid, $langcode = NULL) {
  *   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;
-  }
-
-  return $revisions;
+  return \Drupal::entityManager()->getStorage('node')->revisionsList($node);
 }
 
 /**
@@ -1016,31 +1003,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);
 
@@ -1143,16 +1130,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');
@@ -1337,9 +1321,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
@@ -1820,10 +1804,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);
 }
 
 /**
