diff --git a/core/modules/book/book.admin.inc b/core/modules/book/book.admin.inc
index f74c196..ecaac0f 100644
--- a/core/modules/book/book.admin.inc
+++ b/core/modules/book/book.admin.inc
@@ -29,18 +29,18 @@ function theme_book_admin_table($variables) {
   $access = \Drupal::currentUser()->hasPermission('administer nodes');
   foreach (element_children($form) as $key) {
     $nid = $form[$key]['nid']['#value'];
-    $href = $form[$key]['href']['#value'];
+    $href = url('node/' . $nid);
 
     // Add special classes to be used with tabledrag.js.
-    $form[$key]['plid']['#attributes']['class'] = array('book-plid');
-    $form[$key]['mlid']['#attributes']['class'] = array('book-mlid');
+    $form[$key]['pid']['#attributes']['class'] = array('book-pid');
+    $form[$key]['nid']['#attributes']['class'] = array('book-nid');
     $form[$key]['weight']['#attributes']['class'] = array('book-weight');
 
     $indentation = array('#theme' => 'indentation', '#size' => $form[$key]['depth']['#value'] - 2);
     $data = array(
       drupal_render($indentation) . drupal_render($form[$key]['title']),
       drupal_render($form[$key]['weight']),
-      drupal_render($form[$key]['plid']) . drupal_render($form[$key]['mlid']),
+      drupal_render($form[$key]['pid']) . drupal_render($form[$key]['nid']),
     );
     $links = array();
     $links['view'] = array(
@@ -84,9 +84,9 @@ function theme_book_admin_table($variables) {
       array(
         'action' => 'match',
         'relationship' => 'parent',
-        'group' => 'book-plid',
-        'subgroup' => 'book-plid',
-        'source' => 'book-mlid',
+        'group' => 'book-pid',
+        'subgroup' => 'book-pid',
+        'source' => 'book-nid',
         'hidden' => TRUE,
         'limit' => MENU_MAX_DEPTH - 2,
       ),
diff --git a/core/modules/book/book.install b/core/modules/book/book.install
index fb8c225..efb2816 100644
--- a/core/modules/book/book.install
+++ b/core/modules/book/book.install
@@ -23,34 +23,114 @@ function book_schema() {
   $schema['book'] = array(
   'description' => 'Stores book outline information. Uniquely connects each node in the outline to a link in {menu_links}',
     'fields' => array(
-      'mlid' => array(
+      'nid' => array(
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => TRUE,
         'default' => 0,
-        'description' => "The book page's {menu_links}.mlid.",
+        'description' => "The book page's {node}.nid.",
       ),
-      'nid' => array(
+      'bid' => array(
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => TRUE,
         'default' => 0,
-        'description' => "The book page's {node}.nid.",
+        'description' => "The book ID is the {book}.nid of the top-level page.",
       ),
-      'bid' => array(
+      'pid' => array(
+        'description' => 'The parent ID (pid) is the id of the node above in the hierarchy, or zero if the node is at the top level in its menu.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'has_children' => array(
+        'description' => 'Flag indicating whether any nodes have this node as a parent (1 = children exist, 0 = no children).',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'small',
+      ),
+      'weight' => array(
+        'description' => 'Weight among book entries in the same book at the same depth.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'depth' => array(
+        'description' => 'The depth relative to the top level. A link with pid == 0 will have depth == 1.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'small',
+      ),
+      'p1' => array(
+        'description' => 'The first nid in the materialized path. If N = depth, then pN must equal the nid. If depth > 1 then p(N-1) must equal the pid. All pX where X > depth must equal zero. The columns p1 .. p9 are also called the parents.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p2' => array(
+        'description' => 'The second nid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p3' => array(
+        'description' => 'The third nid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p4' => array(
+        'description' => 'The fourth nid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p5' => array(
+        'description' => 'The fifth nid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p6' => array(
+        'description' => 'The sixth nid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p7' => array(
+        'description' => 'The seventh nid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p8' => array(
+        'description' => 'The eighth nid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p9' => array(
+        'description' => 'The ninth nid in the materialized path. See p1.',
         'type' => 'int',
         'unsigned' => TRUE,
         'not null' => TRUE,
         'default' => 0,
-        'description' => "The book ID is the {book}.nid of the top-level page.",
       ),
     ),
-    'primary key' => array('mlid'),
-    'unique keys' => array(
-      'nid' => array('nid'),
-    ),
+    'primary key' => array('nid'),
     'indexes' => array(
-      'bid' => array('bid'),
+      'book_parents' => array('bid', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'),
     ),
   );
 
diff --git a/core/modules/book/book.module b/core/modules/book/book.module
index 0457488..db211d3 100644
--- a/core/modules/book/book.module
+++ b/core/modules/book/book.module
@@ -40,29 +40,6 @@ function book_help($path, $arg) {
 }
 
 /**
- * Implements hook_entity_bundle_info().
- */
-function book_entity_bundle_info() {
-  $bundles['menu_link']['book-toc'] = array(
-    'label' => t('Book'),
-    'translatable' => FALSE,
-  );
-  return $bundles;
-}
-
-/**
- * Implements hook_TYPE_load().
- */
-function book_menu_link_load($entities) {
-  foreach ($entities as $entity) {
-    // Change the bundle of menu links related to a book.
-    if (strpos($entity->menu_name, 'book-toc-') === 0) {
-      $entity->bundle = 'book-toc';
-    }
-  }
-}
-
-/**
  * Implements hook_theme().
  */
 function book_theme() {
@@ -134,7 +111,7 @@ function book_node_links_alter(array &$node_links, NodeInterface $node, array &$
           $links['book_add_child'] = array(
             'title' => t('Add child page'),
             'href' => 'node/add/' . $child_type,
-            'query' => array('parent' => $node->book['mlid']),
+            'query' => array('parent' => $node->id()),
           );
         }
 
@@ -281,7 +258,7 @@ function book_pick_book_nojs_submit($form, &$form_state) {
  *   The rendered parent page select element.
  */
 function book_form_update($form, $form_state) {
-  return $form['book']['plid'];
+  return $form['book']['pid'];
 }
 
 /**
@@ -300,14 +277,14 @@ function book_form_update($form, $form_state) {
 function book_get_flat_menu($book_link) {
   $flat = &drupal_static(__FUNCTION__, array());
 
-  if (!isset($flat[$book_link['mlid']])) {
+  if (!isset($flat[$book_link['nid']])) {
     // Call bookTreeAllData() to take advantage of the menu system's caching.
-    $tree = \Drupal::service('book.manager')->bookTreeAllData($book_link['menu_name'], $book_link, $book_link['depth'] + 1);
-    $flat[$book_link['mlid']] = array();
-    _book_flatten_menu($tree, $flat[$book_link['mlid']]);
+    $tree = \Drupal::service('book.manager')->bookTreeAllData($book_link['bid'], $book_link, $book_link['depth'] + 1);
+    $flat[$book_link['nid']] = array();
+    _book_flatten_menu($tree, $flat[$book_link['nid']]);
   }
 
-  return $flat[$book_link['mlid']];
+  return $flat[$book_link['nid']];
 }
 
 /**
@@ -322,11 +299,9 @@ function book_get_flat_menu($book_link) {
  */
 function _book_flatten_menu($tree, &$flat) {
   foreach ($tree as $data) {
-    if (!$data['link']['hidden']) {
-      $flat[$data['link']['mlid']] = $data['link'];
-      if ($data['below']) {
-        _book_flatten_menu($data['below'], $flat);
-      }
+    $flat[$data['link']['nid']] = $data['link'];
+    if ($data['below']) {
+      _book_flatten_menu($data['below'], $flat);
     }
   }
 }
@@ -343,7 +318,7 @@ function _book_flatten_menu($tree, &$flat) {
  */
 function book_prev($book_link) {
   // If the parent is zero, we are at the start of a book.
-  if ($book_link['plid'] == 0) {
+  if ($book_link['pid'] == 0) {
     return NULL;
   }
   $flat = book_get_flat_menu($book_link);
@@ -352,22 +327,25 @@ function book_prev($book_link) {
   do {
     $prev = $curr;
     list($key, $curr) = each($flat);
-  } while ($key && $key != $book_link['mlid']);
+  } while ($key && $key != $book_link['nid']);
 
-  if ($key == $book_link['mlid']) {
+  if ($key == $book_link['nid']) {
+    /** @var \Drupal\book\BookManager $book_manager */
+    $book_manager = \Drupal::service('book.manager');
     // The previous page in the book may be a child of the previous visible link.
-    if ($prev['depth'] == $book_link['depth'] && $prev['has_children']) {
+    if ($prev['depth'] == $book_link['depth']) {
       // The subtree will have only one link at the top level - get its data.
-      $tree = \Drupal::service('book.manager')->bookMenuSubtreeData($prev);
+      $tree = $book_manager->bookMenuSubtreeData($prev);
       $data = array_shift($tree);
       // The link of interest is the last child - iterate to find the deepest one.
       while ($data['below']) {
         $data = end($data['below']);
       }
-
+      $book_manager->bookLinkTranslate($data['link']);
       return $data['link'];
     }
     else {
+      $book_manager->bookLinkTranslate($prev);
       return $prev;
     }
   }
@@ -389,10 +367,14 @@ function book_next($book_link) {
   do {
     list($key, ) = each($flat);
   }
-  while ($key && $key != $book_link['mlid']);
+  while ($key && $key != $book_link['nid']);
 
-  if ($key == $book_link['mlid']) {
-    return current($flat);
+  if ($key == $book_link['nid']) {
+    $next = current($flat);
+    if ($next) {
+      \Drupal::service('book.manager')->bookLinkTranslate($next);
+    }
+    return $next;
   }
 }
 
@@ -410,14 +392,14 @@ function book_children($book_link) {
 
   $children = array();
 
-  if ($book_link['has_children']) {
+  if (TRUE || $book_link['has_children']) {
     // Walk through the array until we find the current page.
     do {
       $link = array_shift($flat);
     }
-    while ($link && ($link['mlid'] != $book_link['mlid']));
+    while ($link && ($link['nid'] != $book_link['nid']));
     // Continue though the array and collect the links whose parent is this page.
-    while (($link = array_shift($flat)) && $link['plid'] == $book_link['mlid']) {
+    while (($link = array_shift($flat)) && $link['pid'] == $book_link['nid']) {
       $data['link'] = $link;
       $data['below'] = '';
       $children[] = $data;
@@ -435,12 +417,11 @@ function book_children($book_link) {
  * Implements hook_node_load().
  */
 function book_node_load($nodes) {
-  $result = db_query("SELECT * FROM {book} b INNER JOIN {menu_links} ml ON b.mlid = ml.mlid WHERE b.nid IN (:nids)", array(':nids' =>  array_keys($nodes)), array('fetch' => PDO::FETCH_ASSOC));
+  $result = db_query("SELECT * FROM {book} WHERE nid IN (:nids)", array(':nids' =>  array_keys($nodes)), array('fetch' => PDO::FETCH_ASSOC));
   foreach ($result as $record) {
     $nodes[$record['nid']]->book = $record;
-    $nodes[$record['nid']]->book['href'] = $record['link_path'];
-    $nodes[$record['nid']]->book['title'] = $record['link_title'];
-    $nodes[$record['nid']]->book['options'] = unserialize($record['options']);
+    $nodes[$record['nid']]->book['link_path'] = 'node/' . $record['nid'];
+    $nodes[$record['nid']]->book['link_title'] = $nodes[$record['nid']]->label();
   }
 }
 
@@ -474,7 +455,7 @@ function book_node_presave(EntityInterface $node) {
   }
   // Make sure a new node gets a new menu link.
   if ($node->isNew()) {
-    $node->book['mlid'] = NULL;
+    $node->book['nid'] = NULL;
   }
 }
 
@@ -488,8 +469,6 @@ function book_node_insert(EntityInterface $node) {
       // New nodes that are their own book.
       $node->book['bid'] = $node->id();
     }
-    $node->book['nid'] = $node->id();
-    $node->book['menu_name'] = $book_manager->createMenuName($node->book['bid']);
     $book_manager->updateOutline($node);
   }
 }
@@ -504,8 +483,6 @@ function book_node_update(EntityInterface $node) {
       // New nodes that are their own book.
       $node->book['bid'] = $node->id();
     }
-    $node->book['nid'] = $node->id();
-    $node->book['menu_name'] = $book_manager->createMenuName($node->book['bid']);
     $book_manager->updateOutline($node);
   }
 }
@@ -515,23 +492,9 @@ function book_node_update(EntityInterface $node) {
  */
 function book_node_predelete(EntityInterface $node) {
   if (!empty($node->book['bid'])) {
-    if ($node->id() == $node->book['bid']) {
-      // Handle deletion of a top-level post.
-      $result = db_query("SELECT b.nid FROM {menu_links} ml INNER JOIN {book} b on b.mlid = ml.mlid WHERE ml.plid = :plid", array(
-        ':plid' => $node->book['mlid']
-      ));
-      foreach ($result as $child) {
-        $child_node = node_load($child->id());
-        $child_node->book['bid'] = $child_node->id();
-        \Drupal::service('book.manager')->updateOutline($child_node);
-      }
-    }
-    // @todo - remove this call when we change the schema.
-    menu_link_delete($node->book['mlid']);
-    db_delete('book')
-      ->condition('mlid', $node->book['mlid'])
-      ->execute();
-    drupal_static_reset('book_get_books');
+    /** @var \Drupal\book\BookManager $book_manager */
+    $book_manager = \Drupal::service('book.manager');
+    $book_manager->deleteFromBook($node->book['nid']);
   }
 }
 
@@ -550,12 +513,11 @@ function book_node_prepare_form(NodeInterface $node, $form_display, $operation,
     $query = \Drupal::request()->query;
     if ($node->isNew() && !is_null($query->get('parent')) && is_numeric($query->get('parent'))) {
       // Handle "Add child page" links:
-      $parent = book_link_load($query->get('parent'));
+      $parent = $book_manager->loadBookLink($query->get('parent'), TRUE);
 
       if ($parent && $parent['access']) {
         $node->book['bid'] = $parent['bid'];
-        $node->book['plid'] = $parent['mlid'];
-        $node->book['menu_name'] = $parent['menu_name'];
+        $node->book['pid'] = $parent['nid'];
       }
     }
     // Set defaults.
@@ -631,7 +593,7 @@ function template_preprocess_book_all_books_block(&$variables) {
  * @param array $variables
  *   An associative array containing the following key:
  *   - book_link: An associative array of book link properties.
- *     Properties used: bid, link_title, depth, plid, mlid.
+ *     Properties used: bid, link_title, depth, pid, nid.
  */
 function template_preprocess_book_navigation(&$variables) {
   $book_link = $variables['book_link'];
@@ -639,17 +601,17 @@ function template_preprocess_book_navigation(&$variables) {
   // Provide extra variables for themers. Not needed by default.
   $variables['book_id'] = $book_link['bid'];
   $variables['book_title'] = check_plain($book_link['link_title']);
-  $variables['book_url'] = 'node/' . $book_link['bid'];
+  $variables['book_url'] = \Drupal::url('node.view', array('node' => $book_link['bid']));
   $variables['current_depth'] = $book_link['depth'];
   $variables['tree'] = '';
 
-  if ($book_link['mlid']) {
+  if ($book_link['nid']) {
     $variables['tree'] = book_children($book_link);
 
     $build = array();
 
     if ($prev = book_prev($book_link)) {
-      $prev_href = url($prev['href']);
+      $prev_href = url('node/' . $prev['nid']);
       $build['#attached']['drupal_add_html_head_link'][][] = array(
         'rel' => 'prev',
         'href' => $prev_href,
@@ -658,8 +620,10 @@ function template_preprocess_book_navigation(&$variables) {
       $variables['prev_title'] = check_plain($prev['title']);
     }
 
-    if ($book_link['plid'] && $parent = book_link_load($book_link['plid'])) {
-      $parent_href = url($parent['link_path']);
+    /** @var \Drupal\book\BookManager $book_manager */
+    $book_manager = \Drupal::service('book.manager');
+    if ($book_link['pid'] && $parent = $book_manager->loadBookLink($book_link['pid'])) {
+      $parent_href = url('node/' . $book_link['pid']);
       $build['#attached']['drupal_add_html_head_link'][][] = array(
         'rel' => 'up',
         'href' => $parent_href,
@@ -669,7 +633,7 @@ function template_preprocess_book_navigation(&$variables) {
     }
 
     if ($next = book_next($book_link)) {
-      $next_href = url($next['href']);
+      $next_href = url('node/' . $next['nid']);
       $build['#attached']['drupal_add_html_head_link'][][] = array(
         'rel' => 'next',
         'href' => $next_href,
@@ -786,24 +750,6 @@ function book_node_type_update(NodeTypeInterface $type) {
 }
 
 /**
- * Gets a book menu link by its menu link ID.
- *
- * Like menu_link_load(), but adds additional data from the {book} table.
- *
- * Do not call when loading a node, since this function may call node_load().
- *
- * @param $mlid
- *   The menu link ID of the menu item.
- *
- * @return
- *   A menu link, with the link translated for rendering and data added from the
- *   {book} table. FALSE if there is an error.
- */
-function book_link_load($mlid) {
-  return entity_load('menu_link', $mlid);
-}
-
-/**
  * Implements hook_library_info().
  */
 function book_library_info() {
diff --git a/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php b/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php
index 6dd54ff..0f16d1f 100644
--- a/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php
+++ b/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php
@@ -19,11 +19,11 @@
 class BookBreadcrumbBuilder extends BreadcrumbBuilderBase {
 
   /**
-   * The menu link storage controller.
+   * The node storage controller.
    *
-   * @var \Drupal\menu_link\MenuLinkStorageControllerInterface
+   * @var \Drupal\Core\Entity\EntityStorageControllerInterface
    */
-  protected $menuLinkStorage;
+  protected $nodeStorage;
 
   /**
    * The access manager.
@@ -50,7 +50,7 @@ class BookBreadcrumbBuilder extends BreadcrumbBuilderBase {
    *   The current user account.
    */
   public function __construct(EntityManagerInterface $entity_manager, AccessManager $access_manager, AccountInterface $account) {
-    $this->menuLinkStorage = $entity_manager->getStorageController('menu_link');
+    $this->nodeStorage = $entity_manager->getStorageController('node');
     $this->accessManager = $access_manager;
     $this->account = $account;
   }
@@ -68,22 +68,22 @@ public function applies(array $attributes) {
    * {@inheritdoc}
    */
   public function build(array $attributes) {
-    $mlids = array();
+    $book_nids = array();
     $links = array($this->l($this->t('Home'), '<front>'));
     $book = $attributes['node']->book;
     $depth = 1;
     // We skip the current node.
     while (!empty($book['p' . ($depth + 1)])) {
-      $mlids[] = $book['p' . $depth];
+      $book_nids[] = $book['p' . $depth];
       $depth++;
     }
-    $menu_links = $this->menuLinkStorage->loadMultiple($mlids);
-    if (count($menu_links) > 0) {
+    $parent_books = $this->nodeStorage->loadMultiple($book_nids);
+    if (count($parent_books) > 0) {
       $depth = 1;
       while (!empty($book['p' . ($depth + 1)])) {
-        if (!empty($menu_links[$book['p' . $depth]]) && ($menu_link = $menu_links[$book['p' . $depth]])) {
-          if ($this->accessManager->checkNamedRoute($menu_link->route_name, $menu_link->route_parameters, $this->account)) {
-            $links[] = $this->l($menu_link->label(), $menu_link->route_name, $menu_link->route_parameters, $menu_link->options);
+        if (!empty($parent_books[$book['p' . $depth]]) && ($parent_book = $parent_books[$book['p' . $depth]])) {
+          if ($parent_book->access('view', $this->account)) {
+            $links[] = $this->l($parent_book->label(), 'node.view', array('node' => $parent_book->id()));
           }
         }
         $depth++;
diff --git a/core/modules/book/lib/Drupal/book/BookManager.php b/core/modules/book/lib/Drupal/book/BookManager.php
index 9b415a8..bf978f4 100644
--- a/core/modules/book/lib/Drupal/book/BookManager.php
+++ b/core/modules/book/lib/Drupal/book/BookManager.php
@@ -6,9 +6,9 @@
 
 namespace Drupal\book;
 
+use Drupal\Component\Utility\Unicode;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Database\Connection;
-use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Session\AccountInterface;
@@ -21,6 +21,8 @@
  */
 class BookManager {
 
+  const MENU_MAX_DEPTH = 9;
+
   /**
    * Database Service Object.
    *
@@ -91,23 +93,20 @@ protected function loadBooks() {
 
     if ($nids) {
       $query = $this->connection->select('book', 'b', array('fetch' => \PDO::FETCH_ASSOC));
-      $query->join('menu_links', 'ml', 'b.mlid = ml.mlid');
       $query->fields('b');
-      $query->fields('ml');
       $query->condition('b.nid', $nids);
-      $query->orderBy('ml.weight');
-      $query->orderBy('ml.link_title');
       $query->addTag('node_access');
       $query->addMetaData('base_table', 'book');
       $book_links = $query->execute();
 
       $nodes = $this->entityManager->getStorageController('node')->loadMultiple($nids);
+      // @todo: Sort by weight and translated title.
 
+      // @todo: use route name for links, not system path.
       foreach ($book_links as $link) {
         $nid = $link['nid'];
         if (isset($nodes[$nid]) && $nodes[$nid]->status) {
-          $link['href'] = $link['link_path'];
-          $link['options'] = unserialize($link['options']);
+          $link['link_path'] = 'node/' . $nid;
           $link['title'] = $nodes[$nid]->label();
           $link['type'] = $nodes[$nid]->bundle();
           $this->books[$link['bid']] = $link;
@@ -128,15 +127,11 @@ protected function loadBooks() {
   public function getLinkDefaults($nid) {
     return array(
       'original_bid' => 0,
-      'menu_name' => '',
       'nid' => $nid,
       'bid' => 0,
-      'router_path' => 'node/%',
-      'plid' => 0,
-      'mlid' => 0,
+      'pid' => 0,
       'has_children' => 0,
       'weight' => 0,
-      'module' => 'book',
       'options' => array(),
     );
   }
@@ -151,16 +146,16 @@ public function getLinkDefaults($nid) {
    *   The depth limit for items in the parent select.
    */
   public function getParentDepthLimit(array $book_link) {
-    return MENU_MAX_DEPTH - 1 - (($book_link['mlid'] && $book_link['has_children']) ? $this->findChildrenRelativeDepth($book_link) : 0);
+    return MENU_MAX_DEPTH - 1 - (($book_link['bid'] && $book_link['has_children']) ? $this->findChildrenRelativeDepth($book_link) : 0);
   }
 
   /**
    * {@inheritdoc}
    */
   protected function findChildrenRelativeDepth(array $entity) {
-    $query = db_select('menu_links');
-    $query->addField('menu_links', 'depth');
-    $query->condition('menu_name', $entity['menu_name']);
+    $query = db_select('book');
+    $query->addField('book', 'depth');
+    $query->condition('bid', $entity['bid']);
     $query->orderBy('depth', 'DESC');
     $query->range(0, 1);
 
@@ -211,14 +206,14 @@ public function addFormElements(array $form, array &$form_state, NodeInterface $
       ),
       '#tree' => TRUE,
     );
-    foreach (array('menu_name', 'mlid', 'nid', 'router_path', 'has_children', 'options', 'module', 'original_bid', 'parent_depth_limit') as $key) {
+    foreach (array('nid', 'has_children', 'original_bid', 'parent_depth_limit') as $key) {
       $form['book'][$key] = array(
         '#type' => 'value',
         '#value' => $node->book[$key],
       );
     }
 
-    $form['book']['plid'] = $this->addParentSelectFormElements($node->book);
+    $form['book']['pid'] = $this->addParentSelectFormElements($node->book);
 
     // @see \Drupal\book\Form\BookAdminEditForm::bookAdminTableTree(). The
     // weight may be larger than 15.
@@ -246,7 +241,7 @@ public function addFormElements(array $form, array &$form_state, NodeInterface $
       // The node can become a new book, if it is not one already.
       $options = array($nid => $this->t('- Create a new book -')) + $options;
     }
-    if (!$node->book['mlid']) {
+    if (!$node->book['bid']) {
       // The node is not currently in the hierarchy.
       $options = array(0 => $this->t('- None -')) + $options;
     }
@@ -304,58 +299,47 @@ public function updateOutline(NodeInterface $node) {
     if (empty($node->book['bid'])) {
       return FALSE;
     }
-    $new = empty($node->book['mlid']);
+    // Ensure to create a new book if either the node itself is new, or the
+    // bid was selected the first time, so that the original_bid is still empty.
+    $new = empty($node->book['nid']) || empty($node->book['original_bid']);
 
-    $node->book['link_path'] = 'node/' . $node->id();
-    $node->book['link_title'] = $node->label();
-    $node->book['parent_mismatch'] = FALSE; // The normal case.
+    $node->book['nid'] = $node->id();
 
     if ($node->book['bid'] == $node->id()) {
-      $node->book['plid'] = 0;
-      $node->book['menu_name'] = $this->createMenuName($node->id());
+      $node->book['pid'] = 0;
     }
-    else {
-      // Check in case the parent is not is this book; the book takes precedence.
-      if (!empty($node->book['plid'])) {
-        $parent = $this->connection->query("SELECT * FROM {book} WHERE mlid = :mlid", array(
-          ':mlid' => $node->book['plid'],
-        ))->fetchAssoc();
-      }
-      if (empty($node->book['plid']) || !$parent || $parent['bid'] != $node->book['bid']) {
-        $node->book['plid'] = $this->connection->query("SELECT mlid FROM {book} WHERE nid = :nid", array(
-          ':nid' => $node->book['bid'],
-        ))->fetchField();
-        $node->book['parent_mismatch'] = TRUE; // Likely when JS is disabled.
-      }
+    elseif ($node->book['pid'] == -1) {
+      $node->book['pid'] = $node->book['bid'];
     }
+    return $this->saveBookLink($node->book, $new);
+  }
 
-    $node->book = $this->entityManager
-      ->getStorageController('menu_link')->create($node->book);
-    if ($node->book->save()) {
-      if ($new) {
-        // Insert new.
-        $this->connection->insert('book')
-          ->fields(array(
-            'nid' => $node->id(),
-            'mlid' => $node->book['mlid'],
-            'bid' => $node->book['bid'],
-          ))
-          ->execute();
+  public function getBookParents(array $item, array $parent = array()) {
+    $book = array();
+    if ($item['pid'] == 0) {
+      $book['p1'] = $item['nid'];
+      for ($i = 2; $i <= static::MENU_MAX_DEPTH; $i++) {
+        $parent_property = "p$i";
+        $book[$parent_property] = 0;
       }
-      else {
-        if ($node->book['bid'] != $this->connection->query("SELECT bid FROM {book} WHERE nid = :nid", array(
-          ':nid' => $node->id(),
-        ))->fetchField()) {
-          // Update the bid for this page and all children.
-          $this->updateId($node->book);
-        }
+      $book['depth'] = 1;
+    }
+    else {
+      $i = 1;
+      $book['depth'] = $parent['depth'] + 1;
+      while ($i < $book['depth']) {
+        $p = 'p' . $i++;
+        $book[$p] = $parent[$p];
+      }
+      $p = 'p' . $i++;
+      // The parent (p1 - p9) corresponding to the depth always equals the nid.
+      $book[$p] = $item['nid'];
+      while ($i <= static::MENU_MAX_DEPTH) {
+        $p = 'p' . $i++;
+        $book[$p] = 0;
       }
-
-      return TRUE;
     }
-
-    // Failed to save the menu link.
-    return FALSE;
+    return $book;
   }
 
 /**
@@ -368,36 +352,24 @@ protected function t($string, array $args = array(), array $options = array()) {
   }
 
   /**
-   * Generates the corresponding menu name from a book ID.
-   *
-   * @param $id
-   *   The book ID for which to make a menu name.
-   *
-   * @return
-   *   The menu name.
-   */
-  public function createMenuName($id) {
-    return 'book-toc-' . $id;
-  }
-
-  /**
    * Updates the book ID of a page and its children when it moves to a new book.
    *
    * @param array $book_link
    *   A fully loaded menu link that is part of the book hierarchy.
    */
   public function updateId($book_link) {
-    $query = $this->connection->select('menu_links');
-    $query->addField('menu_links', 'mlid');
+    $query = $this->connection->select('book');
+    $query->addField('book', 'nid');
+
     for ($i = 1; $i <= MENU_MAX_DEPTH && $book_link["p$i"]; $i++) {
       $query->condition("p$i", $book_link["p$i"]);
     }
-    $mlids = $query->execute()->fetchCol();
+    $nids = $query->execute()->fetchCol();
 
-    if ($mlids) {
+    if ($nids) {
       $this->connection->update('book')
         ->fields(array('bid' => $book_link['bid']))
-        ->condition('mlid', $mlids, 'IN')
+        ->condition('nid', $nids, 'IN')
         ->execute();
     }
   }
@@ -443,9 +415,9 @@ protected function addParentSelectFormElements(array $book_link) {
       $form = array(
         '#type' => 'select',
         '#title' => $this->t('Parent item'),
-        '#default_value' => $book_link['plid'],
+        '#default_value' => $book_link['pid'],
         '#description' => $this->t('The parent page in the book. The maximum depth for a book and all child pages is !maxdepth. Some pages in the selected book may not be available as parents if selecting them would exceed this limit.', array('!maxdepth' => MENU_MAX_DEPTH)),
-        '#options' => $this->getTableOfContents($book_link['bid'], $book_link['parent_depth_limit'], array($book_link['mlid'])),
+        '#options' => $this->getTableOfContents($book_link['bid'], $book_link['parent_depth_limit'], array($book_link['nid'])),
         '#attributes' => array('class' => array('book-title-select')),
         '#prefix' => '<div id="edit-book-plid-wrapper">',
         '#suffix' => '</div>',
@@ -478,17 +450,27 @@ protected function addParentSelectFormElements(array $book_link) {
    *   Any link deeper than this value will be excluded (along with its children).
    */
   protected function recurseTableOfContents(array $tree, $indent, array &$toc, array $exclude, $depth_limit) {
+    $nids = array();
     foreach ($tree as $data) {
       if ($data['link']['depth'] > $depth_limit) {
         // Don't iterate through any links on this level.
         break;
       }
+      if (!in_array($data['link']['nid'], $exclude)) {
+        $nids[] = $data['link']['nid'];
+      }
+    }
 
-      if (!in_array($data['link']['mlid'], $exclude)) {
-        $toc[$data['link']['mlid']] = $indent . ' ' . truncate_utf8($data['link']['title'], 30, TRUE, TRUE);
-        if ($data['below']) {
-          $this->recurseTableOfContents($data['below'], $indent . '--', $toc, $exclude, $depth_limit);
-        }
+    $nodes = $this->entityManager->getStorageController('node')->loadMultiple($nids);
+
+    foreach ($tree as $data) {
+      $nid = $data['link']['nid'];
+      if (in_array($nid, $exclude)) {
+        continue;
+      }
+      $toc[$nid] = $indent . ' ' . Unicode::truncate($nodes[$nid]->label(), 30, TRUE, TRUE);
+      if ($data['below']) {
+        $this->recurseTableOfContents($data['below'], $indent . '--', $toc, $exclude, $depth_limit);
       }
     }
   }
@@ -510,7 +492,7 @@ protected function recurseTableOfContents(array $tree, $indent, array &$toc, arr
    *   book page.
    */
   public function getTableOfContents($bid, $depth_limit, array $exclude = array()) {
-    $tree = $this->bookTreeAllData($this->createMenuName($bid));
+    $tree = $this->bookTreeAllData($bid);
     $toc = array();
     $this->recurseTableOfContents($tree, '', $toc, $exclude, $depth_limit);
 
@@ -518,15 +500,28 @@ public function getTableOfContents($bid, $depth_limit, array $exclude = array())
   }
 
   /**
-   * Deletes node's entry form book table.
+   * Deletes node's entry from book table.
    *
    * @param int $nid
    *   The nid to delete.
    */
-  public function deleteBook($nid) {
+  public function deleteFromBook($nid) {
+    $original = $this->loadBookLink($nid, FALSE);
     $this->connection->delete('book')
       ->condition('nid', $nid)
       ->execute();
+    if ($nid == $original['bid']) {
+      // Handle deletion of a top-level post.
+      $result = $this->connection->query("SELECT * FROM {book} WHERE pid = :nid", array(
+        ':nid' => $nid
+      ))->fetchAllAssoc('nid', \PDO::FETCH_ASSOC);
+      foreach ($result as $child) {
+        $child['bid'] = $child['nid'];
+        $this->updateOutline($child);
+      }
+    }
+    $this->updateParentalStatus($original);
+    $this->books = NULL;
   }
 
   /**
@@ -535,8 +530,8 @@ public function deleteBook($nid) {
    * Since this can be the full tree including hidden items, the data returned
    * may be used for generating an an admin interface or a select.
    *
-   * @param string $menu_name
-   *   The named menu links to return
+   * @param int $bid
+   *   The Book ID to find links for.
    * @param $link
    *   A fully loaded menu link, or NULL. If a link is supplied, only the
    *   path to root will be included in the returned tree - as if this link
@@ -551,14 +546,14 @@ public function deleteBook($nid) {
    *
    * Note: copied from menu_tree_all_data().
    */
-  public function bookTreeAllData($menu_name, $link = NULL, $max_depth = NULL) {
-    $tree = &drupal_static('menu_tree_all_data', array());
+  public function bookTreeAllData($bid, $link = NULL, $max_depth = NULL) {
+    $tree = &drupal_static(__FUNCTION__, array());
     $language_interface = language(Language::TYPE_INTERFACE);
 
-    // Use $mlid as a flag for whether the data being loaded is for the whole tree.
-    $mlid = isset($link['mlid']) ? $link['mlid'] : 0;
-    // Generate a cache ID (cid) specific for this $menu_name, $link, $language, and depth.
-    $cid = 'links:' . $menu_name . ':all:' . $mlid . ':' . $language_interface->id . ':' . (int) $max_depth;
+    // Use $nid as a flag for whether the data being loaded is for the whole tree.
+    $nid = isset($link['nid']) ? $link['nid'] : 0;
+    // Generate a cache ID (cid) specific for this $bid, $link, $language, and depth.
+    $cid = 'book-links:' . $bid . ':all:' . $nid . ':' . $language_interface->id . ':' . (int) $max_depth;
 
     if (!isset($tree[$cid])) {
       // If the static variable doesn't have the data, check {cache_menu}.
@@ -574,7 +569,7 @@ public function bookTreeAllData($menu_name, $link = NULL, $max_depth = NULL) {
           'min_depth' => 1,
           'max_depth' => $max_depth,
         );
-        if ($mlid) {
+        if ($nid) {
           // The tree is for a single item, so we need to match the values in its
           // p columns and 0 (the top level) with the plid values of other links.
           $parents = array(0);
@@ -585,16 +580,16 @@ public function bookTreeAllData($menu_name, $link = NULL, $max_depth = NULL) {
           }
           $tree_parameters['expanded'] = $parents;
           $tree_parameters['active_trail'] = $parents;
-          $tree_parameters['active_trail'][] = $mlid;
+          $tree_parameters['active_trail'][] = $nid;
         }
 
         // Cache the tree building parameters using the page-specific cid.
-        cache('menu')->set($cid, $tree_parameters, CacheBackendInterface::CACHE_PERMANENT, array('menu' => $menu_name));
+        cache('menu')->set($cid, $tree_parameters, CacheBackendInterface::CACHE_PERMANENT, array('bid' => $bid));
       }
 
       // Build the tree using the parameters; the resulting tree will be cached
       // by _menu_build_tree()).
-      $tree[$cid] = $this->menu_build_tree($menu_name, $tree_parameters);
+      $tree[$cid] = $this->menu_build_tree($bid, $tree_parameters);
     }
 
     return $tree[$cid];
@@ -624,7 +619,7 @@ public function bookTreeOutput(array $tree) {
     // Pull out just the menu links we are going to render so that we
     // get an accurate count for the first/last classes.
     foreach ($tree as $data) {
-      if ($data['link']['access'] && !$data['link']['hidden']) {
+      if ($data['link']['access']) {
         $items[] = $data;
       }
     }
@@ -657,22 +652,24 @@ public function bookTreeOutput(array $tree) {
       }
 
       // Allow menu-specific theme overrides.
-      $element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_');
+      $element['#theme'] = 'menu_link__book_toc_' . $data['link']['nid'];
       $element['#attributes']['class'] = $class;
       $element['#title'] = $data['link']['title'];
-      $element['#href'] = $data['link']['href'];
+      $node = \Drupal::entityManager()->getStorageController('node')->load($data['link']['nid']);
+      $uri = $node->uri();
+      $element['#href'] = $uri['path'];
       $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array();
       $element['#below'] = $data['below'] ? $this->bookTreeOutput($data['below']) : $data['below'];
       $element['#original_link'] = $data['link'];
-      // Index using the link's unique mlid.
-      $build[$data['link']['mlid']] = $element;
+      // Index using the link's unique nid.
+      $build[$data['link']['nid']] = $element;
     }
     if ($build) {
       // Make sure drupal_render() does not re-order the links.
       $build['#sorted'] = TRUE;
       // Add the theme wrapper for outer markup.
       // Allow menu-specific theme overrides.
-      $build['#theme_wrappers'][] = 'menu_tree__' . strtr($data['link']['menu_name'], '-', '_');
+      $build['#theme_wrappers'][] = 'menu_tree__book_toc_' . $data['link']['nid'];
     }
 
     return $build;
@@ -681,8 +678,8 @@ public function bookTreeOutput(array $tree) {
   /**
    * Builds a menu tree, translates links, and checks access.
    *
-   * @param string $menu_name
-   *   The name of the menu.
+   * @param int $bid
+   *   The Book ID to find links for.
    * @param array $parameters
    *   (optional) An associative array of build parameters. Possible keys:
    *   - expanded: An array of parent link ids to return only menu links that are
@@ -702,11 +699,11 @@ public function bookTreeOutput(array $tree) {
    * @return array
    *   A fully built menu tree.
    */
-  protected function menu_build_tree($menu_name, array $parameters = array()) {
+  protected function menu_build_tree($bid, array $parameters = array()) {
     // Build the menu tree.
-    $data = $this->_menu_build_tree($menu_name, $parameters);
+    $data = $this->_menu_build_tree($bid, $parameters);
     // Check access for the current user to each item in the tree.
-    menu_tree_check_access($data['tree'], $data['node_links']);
+    $this->bookTreeCheckAccess($data['tree'], $data['node_links']);
     return $data['tree'];
   }
 
@@ -719,7 +716,7 @@ protected function menu_build_tree($menu_name, array $parameters = array()) {
    *
    * @see menu_build_tree()
    */
-  protected function _menu_build_tree($menu_name, array $parameters = array()) {
+  protected function _menu_build_tree($bid, array $parameters = array()) {
     // Static cache of already built menu trees.
     $trees = &drupal_static('menu_build_tree', array());
     $language_interface = language(Language::TYPE_INTERFACE);
@@ -729,27 +726,25 @@ protected function _menu_build_tree($menu_name, array $parameters = array()) {
     if (isset($parameters['expanded'])) {
       sort($parameters['expanded']);
     }
-    $tree_cid = 'links:' . $menu_name . ':tree-data:' . $language_interface->id . ':' . hash('sha256', serialize($parameters));
+    $tree_cid = 'book-links:' . $bid . ':tree-data:' . $language_interface->id . ':' . hash('sha256', serialize($parameters));
 
     // If we do not have this tree in the static cache, check {cache_menu}.
     if (!isset($trees[$tree_cid])) {
       $cache = cache('menu')->get($tree_cid);
-      if ($cache && isset($cache->data)) {
+      if (FALSE && $cache && isset($cache->data)) {
         $trees[$tree_cid] = $cache->data;
       }
     }
 
     if (!isset($trees[$tree_cid])) {
-      $query = \Drupal::entityQuery('menu_link');
+      $query = $this->connection->select('book');
+      $query->fields('book');
       for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) {
-        $query->sort('p' . $i, 'ASC');
+        $query->orderBy('p' . $i, 'ASC');
       }
-      $query->condition('menu_name', $menu_name);
+      $query->condition('bid', $bid);
       if (!empty($parameters['expanded'])) {
-        $query->condition('plid', $parameters['expanded'], 'IN');
-      }
-      elseif (!empty($parameters['only_active_trail'])) {
-        $query->condition('mlid', $parameters['active_trail'], 'IN');
+        $query->condition('pid', $parameters['expanded'], 'IN');
       }
       $min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1);
       if ($min_depth != 1) {
@@ -767,8 +762,10 @@ protected function _menu_build_tree($menu_name, array $parameters = array()) {
 
       // Build an ordered array of links using the query result object.
       $links = array();
-      if ($result = $query->execute()) {
-        $links = menu_link_load_multiple($result);
+      $result = $query->execute();
+      foreach ($result as $link) {
+        $link = (array) $link;
+        $links[$link['nid']] = $link;
       }
       $active_trail = (isset($parameters['active_trail']) ? $parameters['active_trail'] : array());
       $data['tree'] = $this->menu_tree_data($links, $active_trail, $min_depth);
@@ -776,7 +773,7 @@ protected function _menu_build_tree($menu_name, array $parameters = array()) {
       $this->bookTreeCollectNodeLinks($data['tree'], $data['node_links']);
 
       // Cache the data, if it is not already in the cache.
-      cache('menu')->set($tree_cid, $data, CacheBackendInterface::CACHE_PERMANENT, array('menu' => $menu_name));
+      cache('menu')->set($tree_cid, $data, CacheBackendInterface::CACHE_PERMANENT, array('bid' => $bid));
       $trees[$tree_cid] = $data;
     }
 
@@ -795,12 +792,9 @@ public function bookTreeCollectNodeLinks(&$tree, &$node_links) {
     // All book links are nodes.
     // @todo clean this up.
     foreach ($tree as $key => $v) {
-      if (!is_array($v['link']['route_parameters'])) {
-        $v['link']['route_parameters'] = unserialize($v['link']['route_parameters']);
-      }
-      if ($v['link']['route_name'] == 'node.view' && isset($v['link']['route_parameters']['node'])) {
-        $nid = $v['link']['route_parameters']['node'];
-        $node_links[$nid][$tree[$key]['link']['mlid']] = &$tree[$key]['link'];
+      if ($v['link']['nid']) {
+        $nid = $v['link']['nid'];
+        $node_links[$nid][$tree[$key]['link']['nid']] = &$tree[$key]['link'];
         $tree[$key]['link']['access'] = FALSE;
       }
       if ($tree[$key]['below']) {
@@ -810,6 +804,163 @@ public function bookTreeCollectNodeLinks(&$tree, &$node_links) {
   }
 
   /**
+   * Load a single book entry.
+   *
+   * @param int $nid
+   *   The node ID of the book.
+   * @param book $translate
+   *   If TRUE, set access, title, and other elements.
+   *
+   * @return array
+   *   The book data of that node.
+   */
+  public function loadBookLink($nid, $translate = TRUE) {
+    $link =  $this->connection->query("SELECT * FROM {book} WHERE nid = :nid", array(':nid' => $nid))->fetchAssoc();
+    if ($link && $translate) {
+      $this->bookLinkTranslate($link);
+    }
+    return $link;
+  }
+
+  /**
+   * Save a single book entry.
+   *
+   * @param array $link
+   *   The link data to save.
+   *
+   * @return array
+   *   The book data of that node.
+   */
+  public function saveBookLink(array $link, $new) {
+    if ($new) {
+      // Insert new.
+      $this->connection->insert('book')
+        ->fields(array(
+            'nid' => $link['nid'],
+            'bid' => $link['bid'],
+            'pid' => $link['pid'],
+          ) + $this->getBookParents($link, (array) $this->loadBookLink($link['pid'], FALSE)))
+        ->execute();
+      // Check the has_children status of the parent, while excluding this item.
+      $this->updateParentalStatus($link);
+    }
+    else {
+      $original = $this->loadBookLink($link['nid'], FALSE);
+      if ($link['bid'] != $original['bid'] || $link['pid'] != $original['pid']) {
+        // Update the bid for this page and all children.
+        if ($link['pid'] == 0) {
+          $link['depth'] = 1;
+          $parent = NULL;
+        }
+        // In case the form did not specified a proper PID we use the BID as new
+        // parent.
+        elseif (($parent_link = $this->loadBookLink($link['pid'], FALSE)) && $parent_link['bid'] != $link['bid']) {
+          $link['pid'] = $link['bid'];
+          $parent = $this->loadBookLink($link['pid'], FALSE);
+          $link['depth'] = $parent['depth'] + 1;
+        }
+        else {
+          $parent = $this->loadBookLink($link['pid'], FALSE);
+          $link['depth'] = $parent['depth'] + 1;
+        }
+        $this->setParents($link, $parent);
+        $this->moveChildren($link, $original);
+      }
+      // Update the weight and pid.
+      $query = $this->connection->update('book');
+      $query->fields(array('weight' => $link['weight'], 'pid' => $link['pid'], 'bid' => $link['bid']));
+      $query->condition('nid', $link['nid']);
+      $query->execute();
+    }
+  }
+
+  protected function moveChildren(array $link, array $original) {
+    $query = $this->connection->update('book');
+
+    $query->fields(array('bid' => $link['bid']));
+
+    $p = 'p1';
+    $expressions = array();
+    for ($i = 1; $i <= $link['depth']; $p = 'p' . ++$i) {
+      $expressions[] = array($p, ":p_$i", array(":p_$i" => $link[$p]));
+    }
+    $j = $original['depth'] + 1;
+    while ($i <= static::MENU_MAX_DEPTH && $j <= static::MENU_MAX_DEPTH) {
+      $expressions[] = array('p' . $i++, 'p' . $j++, array());
+    }
+    while ($i <= static::MENU_MAX_DEPTH) {
+      $expressions[] = array('p' . $i++, 0, array());
+    }
+
+    $shift = $link['depth'] - $original['depth'];
+    if ($shift > 0) {
+      // The order of expressions must be reversed so the new values don't
+      // overwrite the old ones before they can be used because "Single-table
+      // UPDATE assignments are generally evaluated from left to right"
+      // @see http://dev.mysql.com/doc/refman/5.0/en/update.html
+      $expressions = array_reverse($expressions);
+    }
+    foreach ($expressions as $expression) {
+      $query->expression($expression[0], $expression[1], $expression[2]);
+    }
+
+    $query->expression('depth', 'depth + :depth', array(':depth' => $shift));
+    $query->condition('bid', $original['bid']);
+    $p = 'p1';
+    for ($i = 1; !empty($original[$p]); $p = 'p' . ++$i) {
+      $query->condition($p, $original[$p]);
+    }
+
+    $query->execute();
+
+    // Check the has_children status of the parent, while excluding this item.
+    $this->updateParentalStatus($original);
+  }
+
+  protected function updateParentalStatus(array $original) {
+    // If pid == 0, there is nothing to update.
+    if ($original['pid']) {
+      // Check if at least one visible child exists in the table.
+      $parent_has_children = $this->connection->select('book', 'b')
+        ->condition('bid', $original['bid'])
+        ->condition('pid', $original['pid'])
+        ->condition('nid', $original['nid'], '<>')
+        ->countQuery()
+        ->execute()
+        ->fetchField();
+
+      $parent_has_children = ((bool) $parent_has_children) ? 1 : 0;
+      $this->connection->update('book')
+        ->fields(array('has_children' => $parent_has_children))
+        ->condition('nid', $original['pid'])
+        ->execute();
+    }
+  }
+
+  /**
+   * Sets the p1 through p9 properties for a book link being saved.
+   *
+   * @param array $link
+   *   A book link.
+   * @param array $parent
+   *   A book link.
+   */
+  protected function setParents(array &$link, array $parent) {
+    $i = 1;
+    while ($i < $link['depth']) {
+      $p = 'p' . $i++;
+      $link[$p] = $parent[$p];
+    }
+    $p = 'p' . $i++;
+    // The parent (p1 - p9) corresponding to the depth always equals the nid.
+    $link[$p] = $link['nid'];
+    while ($i <= static::MENU_MAX_DEPTH) {
+      $p = 'p' . $i++;
+      $link[$p] = 0;
+    }
+  }
+
+  /**
    * Checks access and performs dynamic operations for each link in the tree.
    *
    * @param array $tree
@@ -846,7 +997,7 @@ protected function _menu_tree_check_access(&$tree) {
     $new_tree = array();
     foreach ($tree as $key => $v) {
       $item = &$tree[$key]['link'];
-      $this->_menu_link_translate($item);
+      $this->bookLinkTranslate($item);
       if ($item['access']) {
         if ($tree[$key]['below']) {
           $this->_menu_tree_check_access($tree[$key]['below']);
@@ -854,7 +1005,7 @@ protected function _menu_tree_check_access(&$tree) {
         // The weights are made a uniform 5 digits by adding 50000 as an offset.
         // After _menu_link_translate(), $item['title'] has the localized link title.
         // Adding the mlid to the end of the index insures that it is unique.
-        $new_tree[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $tree[$key];
+        $new_tree[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['nid']] = $tree[$key];
       }
     }
     // Sort siblings in the tree based on the weights and localized titles.
@@ -868,41 +1019,27 @@ protected function _menu_tree_check_access(&$tree) {
    * This function is similar to _menu_translate(), but it also does
    * link-specific preparation (such as always calling to_arg() functions).
    *
-   * @param $item
-   *   A menu link.
-   * @param bool $translate
-   *   (optional) Whether to try to translate a link containing dynamic path
-   *   argument placeholders (%) based on the menu router item of the current
-   *   path. Defaults to FALSE. Internally used for breadcrumbs.
+   * @param array $link
+   *   A book link.
    *
    * Note: copied from _menu_link_translate() in menu.inc, but reduced to the
    * minimal code that's used.
    */
-  protected function _menu_link_translate(&$item, $translate = FALSE) {
-    if (!is_array($item['options'])) {
-      $item['options'] = unserialize($item['options']);
-    }
-    if (!is_array($item['route_parameters'])) {
-      $item['route_parameters'] = unserialize($item['route_parameters']);
-    }
-    $item['href'] = $item['link_path'];
-    // menu_tree_check_access() may set this ahead of time for links to nodes.
-    if (!isset($item['access'])) {
-      $item['access'] = \Drupal::service('access_manager')->checkNamedRoute('node.view', array('node' => $item['route_parameters']['node']), \Drupal::currentUser());
+  public function bookLinkTranslate(&$link) {
+    $node = NULL;
+    // Access will already be set in the tree functions.
+    if (!isset($link['access'])) {
+      $node = $this->entityManager->getStorageController('node')->load($link['nid']);
+      $link['access'] = $node && $node->access('view');
     }
     // For performance, don't localize a link the user can't access.
-    if ($item['access']) {
-      // Inlined the code we use from _menu_item_localize().
-      $item['localized_options'] = $item['options'];
-      // All 'class' attributes are assumed to be an array during rendering, but
-      // links stored in the database may use an old string value.
-      // @todo In order to remove this code we need to implement a database update
-      //   including unserializing all existing link options and running this code
-      //   on them, as well as adding validation to menu_link_save().
-      if (isset($item['options']['attributes']['class']) && is_string($item['options']['attributes']['class'])) {
-        $item['localized_options']['attributes']['class'] = explode(' ', $item['options']['attributes']['class']);
+    if ($link['access']) {
+      // @todo - load the nodes en-mass rather than individually.
+      if (!$node) {
+        $node = $this->entityManager->getStorageController('node')->load($link['nid']);
       }
-      $item['title'] = $item['link_title'];
+      $link['title'] = $node->label();
+      $link['options'] = array();
     }
   }
 
@@ -949,9 +1086,9 @@ protected function _menu_tree_data(&$links, $parents, $depth) {
     while ($item = array_pop($links)) {
       // We need to determine if we're on the path to root so we can later build
       // the correct active trail.
-      $item['in_active_trail'] = in_array($item['mlid'], $parents);
+      $item['in_active_trail'] = in_array($item['nid'], $parents);
       // Add the current link to the tree.
-      $tree[$item['mlid']] = array(
+      $tree[$item['nid']] = array(
         'link' => $item,
         'below' => array(),
       );
@@ -961,7 +1098,7 @@ protected function _menu_tree_data(&$links, $parents, $depth) {
       // Check whether the next link is the first in a new sub-tree.
       if ($next && $next['depth'] > $depth) {
         // Recursively call _menu_tree_data to build the sub-tree.
-        $tree[$item['mlid']]['below'] = $this->_menu_tree_data($links, $parents, $next['depth']);
+        $tree[$item['nid']]['below'] = $this->_menu_tree_data($links, $parents, $next['depth']);
         // Fetch next link after filling the sub-tree.
         $next = end($links);
       }
@@ -989,8 +1126,8 @@ protected function _menu_tree_data(&$links, $parents, $depth) {
   public function bookMenuSubtreeData($link) {
     $tree = &drupal_static(__FUNCTION__, array());
 
-    // Generate a cache ID (cid) specific for this $menu_name and $link.
-    $cid = 'links:' . $link['menu_name'] . ':subtree-cid:' . $link['mlid'];
+    // Generate a cache ID (cid) specific for this $link.
+    $cid = 'book-links:subtree-cid:' . $link['nid'];
 
     if (!isset($tree[$cid])) {
       $cache = cache('menu')->get($cid);
@@ -1007,11 +1144,9 @@ public function bookMenuSubtreeData($link) {
 
       // If the subtree data was not in the cache, $data will be NULL.
       if (!isset($data)) {
-        $query = db_select('menu_links', 'ml', array('fetch' => \PDO::FETCH_ASSOC));
-        $query->join('book', 'b', 'ml.mlid = b.mlid');
+        $query = db_select('book', 'b', array('fetch' => \PDO::FETCH_ASSOC));
         $query->fields('b');
-        $query->fields('ml');
-        $query->condition('menu_name', $link['menu_name']);
+        $query->condition('b.bid', $link['bid']);
         for ($i = 1; $i <= MENU_MAX_DEPTH && $link["p$i"]; ++$i) {
           $query->condition("p$i", $link["p$i"]);
         }
@@ -1026,7 +1161,7 @@ public function bookMenuSubtreeData($link) {
         $data['node_links'] = array();
         $this->bookTreeCollectNodeLinks($data['tree'], $data['node_links']);
         // Compute the real cid for book subtree data.
-        $tree_cid = 'links:' . $item['menu_name'] . ':subtree-data:' . hash('sha256', serialize($data));
+        $tree_cid = 'book-links:subtree-data:' . hash('sha256', serialize($data));
         // Cache the data, if it is not already in the cache.
 
         if (!cache('menu')->get($tree_cid)) {
diff --git a/core/modules/book/lib/Drupal/book/Controller/BookController.php b/core/modules/book/lib/Drupal/book/Controller/BookController.php
index b9920d2..bf6277e 100644
--- a/core/modules/book/lib/Drupal/book/Controller/BookController.php
+++ b/core/modules/book/lib/Drupal/book/Controller/BookController.php
@@ -72,7 +72,7 @@ public function adminOverview() {
     // Add any recognized books to the table list.
     foreach ($this->bookManager->getAllBooks() as $book) {
       $row = array(
-        l($book['title'], $book['href'], $book['options']),
+        l($book['title'], $book['link_path'], isset($book['options']) ? $book['options'] : array()),
       );
       $links = array();
       $links['edit'] = array(
diff --git a/core/modules/book/lib/Drupal/book/Form/BookAdminEditForm.php b/core/modules/book/lib/Drupal/book/Form/BookAdminEditForm.php
index 07beef0..de57b07 100644
--- a/core/modules/book/lib/Drupal/book/Form/BookAdminEditForm.php
+++ b/core/modules/book/lib/Drupal/book/Form/BookAdminEditForm.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\book\Form;
 
+use \Drupal\book\BookManager;
 use Drupal\Component\Utility\Crypt;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
@@ -35,11 +36,11 @@ class BookAdminEditForm extends FormBase {
   protected $nodeStorage;
 
   /**
-   * The menu link storage controller.
+   * The book manager.
    *
-   * @var \Drupal\menu_link\MenuLinkStorageControllerInterface
+   * @var \Drupal\book\BookManager
    */
-  protected $menuLinkStorage;
+  protected $bookManager;
 
   /**
    * Constructs a new BookAdminEditForm.
@@ -48,13 +49,13 @@ class BookAdminEditForm extends FormBase {
    *   The menu cache object to be used by this controller.
    * @param \Drupal\Core\Entity\EntityStorageControllerInterface $node_storage
    *   The custom block storage controller.
-   * @param \Drupal\menu_link\MenuLinkStorageControllerInterface $menu_link_storage
-   *   The custom block type storage controller.
+   * @param \Drupal\book\BookManager $book_manager
+   *   The book manager.
    */
-  public function __construct(CacheBackendInterface $cache, EntityStorageControllerInterface $node_storage, MenuLinkStorageControllerInterface $menu_link_storage) {
+  public function __construct(CacheBackendInterface $cache, EntityStorageControllerInterface $node_storage, BookManager $book_manager) {
     $this->cache = $cache;
     $this->nodeStorage = $node_storage;
-    $this->menuLinkStorage = $menu_link_storage;
+    $this->bookManager = $book_manager;
   }
 
   /**
@@ -65,7 +66,7 @@ public static function create(ContainerInterface $container) {
     return new static(
       $container->get('cache.menu'),
       $entity_manager->getStorageController('node'),
-      $entity_manager->getStorageController('menu_link')
+      $container->get('book.manager')
     );
   }
 
@@ -117,11 +118,11 @@ public function submitForm(array &$form, array &$form_state) {
         $values = $form_state['values']['table'][$key];
 
         // Update menu item if moved.
-        if ($row['plid']['#default_value'] != $values['plid'] || $row['weight']['#default_value'] != $values['weight']) {
-          $menu_link = $this->menuLinkStorage->load($values['mlid']);
-          $menu_link->weight = $values['weight'];
-          $menu_link->plid = $values['plid'];
-          $menu_link->save();
+        if ($row['pid']['#default_value'] != $values['pid'] || $row['weight']['#default_value'] != $values['weight']) {
+          $link = $this->bookManager->loadBookLink($values['nid'], FALSE);
+          $link['weight'] = $values['weight'];
+          $link['pid'] = $values['pid'];
+          $this->bookManager->saveBookLink($link, FALSE);
           $updated = TRUE;
         }
 
@@ -140,7 +141,7 @@ public function submitForm(array &$form, array &$form_state) {
     if ($updated) {
       // Flush static and cache.
       drupal_static_reset('book_menu_subtree_data');
-      $cid = 'links:' . $form['#node']->book['menu_name'] . ':subtree-cid:' . $form['#node']->book['mlid'];
+      $cid = 'book-links:subtree-cid:' . $form['#node']->book['nid'];
       $this->cache->delete($cid);
     }
 
@@ -202,10 +203,9 @@ protected function bookAdminTableTree(array $tree, array &$form) {
         '#item' => $data['link'],
         'nid' => array('#type' => 'value', '#value' => $data['link']['nid']),
         'depth' => array('#type' => 'value', '#value' => $data['link']['depth']),
-        'href' => array('#type' => 'value', '#value' => $data['link']['href']),
         'title' => array(
           '#type' => 'textfield',
-          '#default_value' => $data['link']['link_title'],
+          '#default_value' => $data['link']['title'],
           '#maxlength' => 255,
           '#size' => 40,
         ),
@@ -216,13 +216,13 @@ protected function bookAdminTableTree(array $tree, array &$form) {
           '#title' => $this->t('Weight for @title', array('@title' => $data['link']['title'])),
           '#title_display' => 'invisible',
         ),
-        'plid' => array(
+        'pid' => array(
           '#type' => 'hidden',
-          '#default_value' => $data['link']['plid'],
+          '#default_value' => $data['link']['pid'],
         ),
-        'mlid' => array(
+        'nid' => array(
           '#type' => 'hidden',
-          '#default_value' => $data['link']['mlid'],
+          '#default_value' => $data['link']['nid'],
         ),
       );
       if ($data['below']) {
diff --git a/core/modules/book/lib/Drupal/book/Form/BookRemoveForm.php b/core/modules/book/lib/Drupal/book/Form/BookRemoveForm.php
index ce84ea0..2038d83 100644
--- a/core/modules/book/lib/Drupal/book/Form/BookRemoveForm.php
+++ b/core/modules/book/lib/Drupal/book/Form/BookRemoveForm.php
@@ -109,8 +109,7 @@ public function getCancelRoute() {
    */
   public function submitForm(array &$form, array &$form_state) {
     if ($this->bookManager->checkNodeIsRemovable($this->node)) {
-      menu_link_delete($this->node->book['mlid']);
-      $this->bookManager->deleteBook($this->node->id());
+      $this->bookManager->deleteFromBook($this->node->id());
       drupal_set_message($this->t('The post has been removed from the book.'));
     }
     $form_state['redirect_route'] = array(
diff --git a/core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php b/core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php
index 3fd3f64..f7cab5e 100644
--- a/core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php
+++ b/core/modules/book/lib/Drupal/book/Plugin/Block/BookNavigationBlock.php
@@ -71,7 +71,7 @@ public function build() {
         if ($book['bid'] == $current_bid) {
           // If the current page is a node associated with a book, the menu
           // needs to be retrieved.
-          $data = \Drupal::service('book.manager')->bookTreeAllData($node->book['menu_name'], $node->book);
+          $data = \Drupal::service('book.manager')->bookTreeAllData($node->book['bid'], $node->book);
           $book_menus[$book_id] = \Drupal::service('book.manager')->bookTreeOutput($data);
         }
         else {
@@ -100,7 +100,7 @@ public function build() {
       $nid = $select->execute()->fetchField();
       // Only show the block if the user has view access for the top-level node.
       if ($nid) {
-        $tree = \Drupal::service('book.manager')->bookTreeAllData($node->book['menu_name'], $node->book);
+        $tree = \Drupal::service('book.manager')->bookTreeAllData($node->book['bid'], $node->book);
         // There should only be one element at the top level.
         $data = array_shift($tree);
         $below = \Drupal::service('book.manager')->bookTreeOutput($data['below']);
diff --git a/core/modules/book/lib/Drupal/book/Tests/BookTest.php b/core/modules/book/lib/Drupal/book/Tests/BookTest.php
index 21fc988..a002f80 100644
--- a/core/modules/book/lib/Drupal/book/Tests/BookTest.php
+++ b/core/modules/book/lib/Drupal/book/Tests/BookTest.php
@@ -92,8 +92,8 @@ function createBook() {
      */
     $nodes = array();
     $nodes[] = $this->createBookNode($book->id()); // Node 0.
-    $nodes[] = $this->createBookNode($book->id(), $nodes[0]->book['mlid']); // Node 1.
-    $nodes[] = $this->createBookNode($book->id(), $nodes[0]->book['mlid']); // Node 2.
+    $nodes[] = $this->createBookNode($book->id(), $nodes[0]->book['nid']); // Node 1.
+    $nodes[] = $this->createBookNode($book->id(), $nodes[0]->book['nid']); // Node 2.
     $nodes[] = $this->createBookNode($book->id()); // Node 3.
     $nodes[] = $this->createBookNode($book->id()); // Node 4.
 
@@ -250,7 +250,7 @@ function createBookNode($book_nid, $parent = NULL) {
     if ($parent !== NULL) {
       $this->drupalPostForm('node/add/book', $edit, t('Change book (update list of parents)'));
 
-      $edit['book[plid]'] = $parent;
+      $edit['book[pid]'] = $parent;
       $this->drupalPostForm(NULL, $edit, t('Save'));
     }
     else {
@@ -493,7 +493,7 @@ public function testBookOrdering() {
     $this->drupalLogin($this->admin_user);
     $node1 = $this->createBookNode($book->id());
     $node2 = $this->createBookNode($book->id());
-    $plid = $node1->book['mlid'];
+    $pid = $node1->book['nid'];
 
     // Head to admin screen and attempt to re-order.
     $this->drupalGet('admin/structure/book/' . $book->id());
@@ -501,13 +501,13 @@ public function testBookOrdering() {
       "table[book-admin-{$node1->id()}][weight]" => 1,
       "table[book-admin-{$node2->id()}][weight]" => 2,
       // Put node 2 under node 1.
-      "table[book-admin-{$node2->id()}][plid]" => $plid,
+      "table[book-admin-{$node2->id()}][pid]" => $pid,
     );
     $this->drupalPostForm(NULL, $edit, t('Save book pages'));
     // Verify weight was updated.
     $this->assertFieldByName("table[book-admin-{$node1->id()}][weight]", 1);
     $this->assertFieldByName("table[book-admin-{$node2->id()}][weight]", 2);
-    $this->assertFieldByName("table[book-admin-{$node2->id()}][plid]", $plid);
+    $this->assertFieldByName("table[book-admin-{$node2->id()}][pid]", $pid);
   }
 
   /**
@@ -521,5 +521,24 @@ public function testBookOutline() {
     $this->drupalLogin($this->admin_user);
     $this->drupalGet('node/' . $book->id() . '/outline');
     $this->assertRaw(t('Book outline'));
+
+    // Create a new node and set the book after the node was created.
+    $node = $this->drupalCreateNode(array('type' => 'book'));
+    $edit = array();
+    $edit['book[bid]'] = $node->id();
+    $this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
+    $node = \Drupal::entityManager()->getStorageController('node')->load($node->id());
+
+    // Test the book array.
+    $this->assertEqual($node->book['nid'], $node->id());
+    $this->assertEqual($node->book['bid'], $node->id());
+    $this->assertEqual($node->book['depth'], 1);
+    $this->assertEqual($node->book['p1'], $node->id());
+    $this->assertEqual($node->book['pid'], '0');
+
+    // Test the form itself.
+    $this->drupalGet('node/' . $node->id() . '/edit');
+    $this->assertOptionSelected('edit-book-bid', $node->id());
   }
+
 }
diff --git a/core/modules/book/tests/Drupal/book/Tests/BookManagerTest.php b/core/modules/book/tests/Drupal/book/Tests/BookManagerTest.php
new file mode 100644
index 0000000..a41837f
--- /dev/null
+++ b/core/modules/book/tests/Drupal/book/Tests/BookManagerTest.php
@@ -0,0 +1,127 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\book\Tests\BookManagerTest.
+ */
+
+namespace Drupal\book\Tests;
+
+use Drupal\book\BookManager;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the book manager.
+ *
+ * @see \Drupal\book\BookManager
+ */
+class BookManagerTest extends UnitTestCase {
+
+  /**
+   * The mocked database connection.
+   *
+   * @var \Drupal\Core\Database\Connection|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $connection;
+
+  /**
+   * The mocked entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManager|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $entityManager;
+
+  /**
+   * The mocked config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactory|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $configFactory;
+
+  /**
+   * The mocked translation manager.
+   *
+   * @var \Drupal\Core\StringTranslation\TranslationInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $translation;
+
+  /**
+   * The tested book manager.
+   *
+   * @var \Drupal\book\BookManager
+   */
+  protected $bookManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Book manager',
+      'description' => 'Test the book manager.',
+      'group' => 'Book',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    $this->connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
+      ->disableOriginalConstructor()
+      ->getMock();
+    $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
+    $this->translation = $this->getStringTranslationStub();
+    $this->configFactory = $this->getConfigFactoryStub(array());
+    $this->bookManager = new BookManager($this->connection, $this->entityManager, $this->translation, $this->configFactory);
+  }
+
+  /**
+   * Tests the getBookParents() method.
+   *
+   * @dataProvider providerTestGetBookParents
+   */
+  public function testGetBookParents($book, $parent, $expected) {
+    $this->assertEquals($expected, $this->bookManager->getBookParents($book, $parent));
+  }
+
+  /**
+   * Provides test data for testGetBookParents.
+   *
+   * @return array
+   */
+  public function providerTestGetBookParents() {
+    $empty = array(
+      'p1' => 0,
+      'p2' => 0,
+      'p3' => 0,
+      'p4' => 0,
+      'p5' => 0,
+      'p6' => 0,
+      'p7' => 0,
+      'p8' => 0,
+      'p9' => 0,
+    );
+    return array(
+      // Provides a book without an existing parent.
+      array(
+        array('pid' => 0, 'nid' => 12),
+        array(),
+        array('depth' => 1, 'p1' => 12) + $empty,
+      ),
+      // Provides a book with an existing parent.
+      array(
+        array('pid' => 11, 'nid' => 12),
+        array('nid' => 11, 'depth' => 1, 'p1' => 11,),
+        array('depth' => 2, 'p1' => 11, 'p2' => 12) + $empty,
+      ),
+      // Provides a book with two existing parents.
+      array(
+        array('pid' => 11, 'nid' => 12),
+        array('nid' => 11, 'depth' => 2, 'p1' => 10, 'p2' => 11),
+        array('depth' => 3, 'p1' => 10, 'p2' => 11, 'p3' => 12) + $empty,
+      ),
+    );
+  }
+
+}
