diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module
index 76da041..72f1d0b 100644
--- a/core/modules/menu/menu.module
+++ b/core/modules/menu/menu.module
@@ -440,7 +440,9 @@ function menu_node_prepare_form(NodeInterface $node, $operation, array &$form_st
  * Find the depth limit for items in the parent select.
  */
 function _menu_parent_depth_limit($item) {
-  return MENU_MAX_DEPTH - 1 - (($item['mlid'] && $item['has_children']) ? entity_get_controller('menu_link')->findChildrenRelativeDepth($item) : 0);
+  /** @var \Drupal\menu_link\MenuTreeStorageInterface $menu_tree_storage */
+  $menu_tree_storage = \Drupal::service('menu_link.tree_storage');
+  return MENU_MAX_DEPTH - 1 - (($item['mlid'] && $item['has_children']) ? $menu_tree_storage->findChildrenRelativeDepth($item) : 0);
 }
 
 /**
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php b/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php
index ebb28bf..e851067 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php
@@ -427,7 +427,9 @@ public static function preDelete(EntityStorageInterface $storage, array $entitie
     parent::preDelete($storage, $entities);
 
     // Nothing to do if we don't want to reparent children.
-    if ($storage->getPreventReparenting()) {
+    /** @var \Drupal\menu_link\MenuTreeStorageInterface $menu_tree_storage */
+    $menu_tree_storage = \Drupal::service('menu_link.tree_storage');
+    if ($menu_tree_storage->getPreventReparenting()) {
       return;
     }
 
@@ -451,9 +453,11 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti
 
     $affected_menus = array();
     // Update the has_children status of the parent.
+    /** @var \Drupal\menu_link\MenuTreeStorageInterface $menu_tree_storage */
+    $menu_tree_storage = \Drupal::service('menu_link.tree_storage');
     foreach ($entities as $entity) {
-      if (!$storage->getPreventReparenting()) {
-        $storage->updateParentalStatus($entity);
+      if (!$menu_tree_storage->getPreventReparenting()) {
+        $menu_tree_storage->updateParentalStatus($entity);
       }
 
       // Store all menu names for which we need to clear the cache.
@@ -478,46 +482,9 @@ public function preSave(EntityStorageInterface $storage) {
     // since a path marked as external does not need to match a route.
     $this->external = (url_is_external($this->link_path) || $this->link_path == '<front>') ? 1 : 0;
 
-    // Try to find a parent link. If found, assign it and derive its menu.
-    $parent = $this->findParent($storage);
-    if ($parent) {
-      $this->plid = $parent->id();
-      $this->menu_name = $parent->menu_name;
-    }
-    // If no corresponding parent link was found, move the link to the top-level.
-    else {
-      $this->plid = 0;
-    }
-
-    // Directly fill parents for top-level links.
-    if ($this->plid == 0) {
-      $this->p1 = $this->id();
-      for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) {
-        $parent_property = "p$i";
-        $this->{$parent_property} = 0;
-      }
-      $this->depth = 1;
-    }
-    // Otherwise, ensure that this link's depth is not beyond the maximum depth
-    // and fill parents based on the parent link.
-    else {
-      if ($this->has_children && $this->original) {
-        $limit = MENU_MAX_DEPTH - $storage->findChildrenRelativeDepth($this->original) - 1;
-      }
-      else {
-        $limit = MENU_MAX_DEPTH - 1;
-      }
-      if ($parent->depth > $limit) {
-        return FALSE;
-      }
-      $this->depth = $parent->depth + 1;
-      $this->setParents($parent);
-    }
-
-    // Need to check both plid and menu_name, since plid can be 0 in any menu.
-    if (isset($this->original) && ($this->plid != $this->original->plid || $this->menu_name != $this->original->menu_name)) {
-      $storage->moveChildren($this);
-    }
+    /** @var \Drupal\menu_link\MenuTreeStorageInterface $menu_tree_storage */
+    $menu_tree_storage = \Drupal::service('menu_link.tree_storage');
+    $menu_tree_storage->preSaveMenuLink($this);
 
     // Find the route_name.
     if (!isset($this->route_name)) {
@@ -537,7 +504,9 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) {
     parent::postSave($storage, $update);
 
     // Check the has_children status of the parent.
-    $storage->updateParentalStatus($this);
+    /** @var \Drupal\menu_link\MenuTreeStorageInterface $menu_tree_storage */
+    $menu_tree_storage = \Drupal::service('menu_link.tree_storage');
+    $menu_tree_storage->updateParentalStatus($this);
 
     Cache::invalidateTags(array('menu' => $this->menu_name));
     if (isset($this->original) && $this->menu_name != $this->original->menu_name) {
@@ -584,58 +553,6 @@ public static function postLoad(EntityStorageInterface $storage, array &$entitie
   }
 
   /**
-   * {@inheritdoc}
-   */
-  protected function setParents(MenuLinkInterface $parent) {
-    $i = 1;
-    while ($i < $this->depth) {
-      $p = 'p' . $i++;
-      $this->{$p} = $parent->{$p};
-    }
-    $p = 'p' . $i++;
-    // The parent (p1 - p9) corresponding to the depth always equals the mlid.
-    $this->{$p} = $this->id();
-    while ($i <= MENU_MAX_DEPTH) {
-      $p = 'p' . $i++;
-      $this->{$p} = 0;
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function findParent(EntityStorageInterface $storage) {
-    $parent = FALSE;
-
-    // This item is explicitly top-level, skip the rest of the parenting.
-    if (isset($this->plid) && empty($this->plid)) {
-      return $parent;
-    }
-
-    // If we have a parent link ID, try to use that.
-    $candidates = array();
-    if (isset($this->plid)) {
-      $candidates[] = $this->plid;
-    }
-
-    // Else, if we have a link hierarchy try to find a valid parent in there.
-    if (!empty($this->depth) && $this->depth > 1) {
-      for ($depth = $this->depth - 1; $depth >= 1; $depth--) {
-        $parent_property = "p$depth";
-        $candidates[] = $this->$parent_property;
-      }
-    }
-
-    foreach ($candidates as $mlid) {
-      $parent = $storage->load($mlid);
-      if ($parent) {
-        break;
-      }
-    }
-    return $parent;
-  }
-
-  /**
    * Builds and returns the renderable array for this menu link.
    *
    * @return array
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorage.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorage.php
index ea05968..831959f 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorage.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorage.php
@@ -19,12 +19,6 @@
  */
 class MenuLinkStorage extends EntityDatabaseStorage implements MenuLinkStorageInterface {
 
-  /**
-   * Indicates whether the delete operation should re-parent children items.
-   *
-   * @var bool
-   */
-  protected $preventReparenting = FALSE;
 
   /**
    * {@inheritdoc}
@@ -108,19 +102,6 @@ public function save(EntityInterface $entity) {
     }
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function setPreventReparenting($value = FALSE) {
-    $this->preventReparenting = $value;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getPreventReparenting() {
-    return $this->preventReparenting;
-  }
 
   /**
    * {@inheritdoc}
@@ -197,75 +178,6 @@ public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE)
     }
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  public function findChildrenRelativeDepth(EntityInterface $entity) {
-    // @todo Since all we need is a specific field from the base table, does it
-    // make sense to convert to EFQ?
-    $query = $this->database->select('menu_links');
-    $query->addField('menu_links', 'depth');
-    $query->condition('menu_name', $entity->menu_name);
-    $query->orderBy('depth', 'DESC');
-    $query->range(0, 1);
-
-    $i = 1;
-    $p = 'p1';
-    while ($i <= MENU_MAX_DEPTH && $entity->{$p}) {
-      $query->condition($p, $entity->{$p});
-      $p = 'p' . ++$i;
-    }
-
-    $max_depth = $query->execute()->fetchField();
-
-    return ($max_depth > $entity->depth) ? $max_depth - $entity->depth : 0;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function moveChildren(EntityInterface $entity) {
-    $query = $this->database->update($this->entityType->getBaseTable());
-
-    $query->fields(array('menu_name' => $entity->menu_name));
-
-    $p = 'p1';
-    $expressions = array();
-    for ($i = 1; $i <= $entity->depth; $p = 'p' . ++$i) {
-      $expressions[] = array($p, ":p_$i", array(":p_$i" => $entity->{$p}));
-    }
-    $j = $entity->original->depth + 1;
-    while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) {
-      $expressions[] = array('p' . $i++, 'p' . $j++, array());
-    }
-    while ($i <= MENU_MAX_DEPTH) {
-      $expressions[] = array('p' . $i++, 0, array());
-    }
-
-    $shift = $entity->depth - $entity->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('menu_name', $entity->original->menu_name);
-    $p = 'p1';
-    for ($i = 1; $i <= MENU_MAX_DEPTH && $entity->original->{$p}; $p = 'p' . ++$i) {
-      $query->condition($p, $entity->original->{$p});
-    }
-
-    $query->execute();
-
-    // Check the has_children status of the parent, while excluding this item.
-    $this->updateParentalStatus($entity->original, TRUE);
-  }
 
   /**
    * {@inheritdoc}
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageInterface.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageInterface.php
index 3d3fd5d..c7c7097 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageInterface.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageInterface.php
@@ -16,24 +16,6 @@
 interface MenuLinkStorageInterface extends EntityStorageInterface {
 
   /**
-   * Sets an internal flag that allows us to prevent the reparenting operations
-   * executed during deletion.
-   *
-   * @param bool $value
-   *   TRUE if reparenting should be allowed, FALSE if it should be prevented.
-   */
-  public function setPreventReparenting($value = FALSE);
-
-  /**
-   * Gets value of internal flag that allows/prevents reparenting operations
-   * executed during deletion.
-   *
-   * @return bool
-   *   TRUE if reparenting is allowed, FALSE if it is prevented.
-   */
-  public function getPreventReparenting();
-
-  /**
    * Loads system menu link as needed by system_get_module_admin_tasks().
    *
    * @return array
@@ -42,39 +24,6 @@ public function getPreventReparenting();
   public function loadModuleAdminTasks();
 
   /**
-   * Checks and updates the 'has_children' property for the parent of a link.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   A menu link entity.
-   */
-  public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE);
-
-  /**
-   * Finds the depth of an item's children relative to its depth.
-   *
-   * For example, if the item has a depth of 2 and the maximum of any child in
-   * the menu link tree is 5, the relative depth is 3.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   A menu link entity.
-   *
-   * @return int
-   *   The relative depth, or zero.
-   */
-  public function findChildrenRelativeDepth(EntityInterface $entity);
-
-  /**
-   * Updates the children of a menu link that is being moved.
-   *
-   * The menu name, parents (p1 - p6), and depth are updated for all children of
-   * the link, and the has_children status of the previous parent is updated.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   A menu link entity.
-   */
-  public function moveChildren(EntityInterface $entity);
-
-  /**
    * Returns the number of menu links from a menu.
    *
    * @param string $menu_name
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuTree.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuTree.php
index f12cae8..23f3098 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuTree.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuTree.php
@@ -23,14 +23,6 @@
 class MenuTree implements MenuTreeInterface {
 
   /**
-   * The database connection.
-   *
-   * @var \Drupal\Core\Database\Connection
-   *   The database connection.
-   */
-  protected $database;
-
-  /**
    * The cache backend.
    *
    * @var \Drupal\Core\Cache\CacheBackendInterface
@@ -59,13 +51,6 @@ class MenuTree implements MenuTreeInterface {
   protected $menuLinkStorage;
 
   /**
-   * The entity query factory.
-   *
-   * @var \Drupal\Core\Entity\Query\QueryFactory
-   */
-  protected $queryFactory;
-
-  /**
    * The state.
    *
    * @var \Drupal\Core\KeyValueStore\StateInterface
@@ -117,10 +102,15 @@ class MenuTree implements MenuTreeInterface {
   protected $menuPageTrees;
 
   /**
+   * The menu tree storage.
+   *
+   * @var \Drupal\menu_link\MenuTreeStorageInterface
+   */
+  protected $menuTreeStorage;
+
+  /**
    * Constructs a new MenuTree.
    *
-   * @param \Drupal\Core\Database\Connection $database
-   *   The database connection.
    * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
    *   The cache backend.
    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
@@ -129,19 +119,18 @@ class MenuTree implements MenuTreeInterface {
    *   The request stack.
    * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
    *   The entity manager.
-   * @param \Drupal\Core\Entity\Query\QueryFactory $entity_query_factory
-   *   The entity query factory.
    * @param \Drupal\Core\KeyValueStore\StateInterface $state
    *   The state.
+   * @param \Drupal\menu_link\MenuTreeStorageInterface $menu_tree_storage
+   *   The menu tree storage.
    */
-  public function __construct(Connection $database, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, RequestStack $request_stack, EntityManagerInterface $entity_manager, QueryFactory $entity_query_factory, StateInterface $state) {
-    $this->database = $database;
+  public function __construct(CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, RequestStack $request_stack, EntityManagerInterface $entity_manager, StateInterface $state, MenuTreeStorageInterface $menu_tree_storage) {
     $this->cache = $cache_backend;
     $this->languageManager = $language_manager;
     $this->requestStack = $request_stack;
     $this->menuLinkStorage = $entity_manager->getStorage('menu_link');
-    $this->queryFactory = $entity_query_factory;
     $this->state = $state;
+    $this->menuTreeStorage = $menu_tree_storage;
   }
 
   /**
@@ -285,13 +274,7 @@ public function buildPageData($menu_name, $max_depth = NULL, $only_active_trail
               // Collect all the links set to be expanded, and then add all of
               // their children to the list as well.
               do {
-                $query = $this->queryFactory->get('menu_link')
-                  ->condition('menu_name', $menu_name)
-                  ->condition('expanded', 1)
-                  ->condition('has_children', 1)
-                  ->condition('plid', $parents, 'IN')
-                  ->condition('mlid', $parents, 'NOT IN');
-                $result = $query->execute();
+                $result = $this->menuTreeStorage->getExpandedParents($parents, $menu_name);
                 $parents += $result;
               } while (!empty($result));
             }
@@ -452,37 +435,13 @@ protected function doBuildTree($menu_name, array $parameters = array()) {
     }
 
     if (!isset($this->menuTree[$tree_cid])) {
-      $query = $this->queryFactory->get('menu_link');
-      for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) {
-        $query->sort('p' . $i, 'ASC');
-      }
-      $query->condition('menu_name', $menu_name);
-      if (!empty($parameters['expanded'])) {
-        $query->condition('plid', $parameters['expanded'], 'IN');
-      }
-      elseif (!empty($parameters['only_active_trail'])) {
-        $query->condition('mlid', $parameters['active_trail'], 'IN');
-      }
-      $min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1);
-      if ($min_depth != 1) {
-        $query->condition('depth', $min_depth, '>=');
-      }
-      if (isset($parameters['max_depth'])) {
-        $query->condition('depth', $parameters['max_depth'], '<=');
-      }
-      // Add custom query conditions, if any were passed.
-      if (isset($parameters['conditions'])) {
-        foreach ($parameters['conditions'] as $column => $value) {
-          $query->condition($column, $value);
-        }
-      }
-
       // Build an ordered array of links using the query result object.
       $links = array();
-      if ($result = $query->execute()) {
+      if ($result = $this->menuTreeStorage->findNodes($parameters, $menu_name)) {
         $links = $this->menuLinkStorage->loadMultiple($result);
       }
       $active_trail = (isset($parameters['active_trail']) ? $parameters['active_trail'] : array());
+      $min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1);
       $tree = $this->doBuildTreeData($links, $active_trail, $min_depth);
 
       // Cache the data, if it is not already in the cache.
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuTreeStorage.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuTreeStorage.php
new file mode 100644
index 0000000..ab70524
--- /dev/null
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuTreeStorage.php
@@ -0,0 +1,282 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link\MenuTreeStorage.
+ */
+
+namespace Drupal\menu_link;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityStorageInterface;
+
+class MenuTreeStorage implements MenuTreeStorageInterface {
+
+  /**
+   * Indicates whether the delete operation should re-parent children items.
+   *
+   * @var bool
+   */
+  protected $preventReparenting = FALSE;
+
+  public function __construct(Connection $database) {
+    $this->database = $database;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE) {
+    // If plid == 0, there is nothing to update.
+    if ($entity->plid) {
+      // Check if at least one visible child exists in the table.
+      $query = $this->database->select('menu_tree');
+      $query
+        ->fields('menu_tree', array('mlid'))
+        ->condition('menu_name', $entity->menu_name)
+//        ->condition('hidden', 0)
+        ->condition('plid', $entity->plid)
+        ->countQuery();
+
+      if ($exclude) {
+        $query->condition('mlid', $entity->id(), '<>');
+      }
+
+      $parent_has_children = ((bool) $query->execute()) ? 1 : 0;
+      $this->database->update('menu_tree')
+        ->fields(array('has_children' => $parent_has_children))
+        ->condition('mlid', $entity->plid)
+        ->execute();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setParents(MenuLinkInterface $menu_link, MenuLinkInterface $parent) {
+    $i = 1;
+    while ($i < $menu_link->depth) {
+      $p = 'p' . $i++;
+      $menu_link->{$p} = $parent->{$p};
+    }
+    $p = 'p' . $i++;
+    // The parent (p1 - p9) corresponding to the depth always equals the mlid.
+    $menu_link->{$p} = $menu_link->id();
+    while ($i <= MENU_MAX_DEPTH) {
+      $p = 'p' . $i++;
+      $menu_link->{$p} = 0;
+    }
+  }
+
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function findParent(MenuLinkInterface $menu_link, EntityStorageInterface $storage) {
+    $parent = FALSE;
+
+    // This item is explicitly top-level, skip the rest of the parenting.
+    if (isset($menu_link->plid) && empty($menu_link->plid)) {
+      return $parent;
+    }
+
+    // If we have a parent link ID, try to use that.
+    $candidates = array();
+    if (isset($menu_link->plid)) {
+      $candidates[] = $menu_link->plid;
+    }
+
+    // Else, if we have a link hierarchy try to find a valid parent in there.
+    if (!empty($menu_link->depth) && $menu_link->depth > 1) {
+      for ($depth = $menu_link->depth - 1; $depth >= 1; $depth--) {
+        $parent_property = "p$depth";
+        $candidates[] = $menu_link->$parent_property;
+      }
+    }
+
+    foreach ($candidates as $mlid) {
+      $parent = $storage->load($mlid);
+      if ($parent) {
+        break;
+      }
+    }
+    return $parent;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function findChildrenRelativeDepth(EntityInterface $entity) {
+    // @todo Since all we need is a specific field from the base table, does it
+    // make sense to convert to EFQ?
+    $query = $this->database->select('menu_links');
+    $query->addField('menu_links', 'depth');
+    $query->condition('menu_name', $entity->menu_name);
+    $query->orderBy('depth', 'DESC');
+    $query->range(0, 1);
+
+    $i = 1;
+    $p = 'p1';
+    while ($i <= MENU_MAX_DEPTH && $entity->{$p}) {
+      $query->condition($p, $entity->{$p});
+      $p = 'p' . ++$i;
+    }
+
+    $max_depth = $query->execute()->fetchField();
+
+    return ($max_depth > $entity->depth) ? $max_depth - $entity->depth : 0;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function moveChildren(EntityInterface $entity) {
+    $query = $this->database->update('menu_tree');
+
+    $query->fields(array('menu_name' => $entity->menu_name));
+
+    $p = 'p1';
+    $expressions = array();
+    for ($i = 1; $i <= $entity->depth; $p = 'p' . ++$i) {
+      $expressions[] = array($p, ":p_$i", array(":p_$i" => $entity->{$p}));
+    }
+    $j = $entity->original->depth + 1;
+    while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) {
+      $expressions[] = array('p' . $i++, 'p' . $j++, array());
+    }
+    while ($i <= MENU_MAX_DEPTH) {
+      $expressions[] = array('p' . $i++, 0, array());
+    }
+
+    $shift = $entity->depth - $entity->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('menu_name', $entity->original->menu_name);
+    $p = 'p1';
+    for ($i = 1; $i <= MENU_MAX_DEPTH && $entity->original->{$p}; $p = 'p' . ++$i) {
+      $query->condition($p, $entity->original->{$p});
+    }
+
+    $query->execute();
+
+    // Check the has_children status of the parent, while excluding this item.
+    $this->updateParentalStatus($entity->original, TRUE);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function preSaveMenuLink(MenuLinkInterface $menu_link) {
+    $storage = \Drupal::entityManager()->getStorage('menu_link');
+    // Try to find a parent link. If found, assign it and derive its menu.
+    $parent = $this->findParent($menu_link, $storage);
+    if ($parent) {
+      $menu_link->plid = $parent->id();
+      $menu_link->menu_name = $parent->menu_name;
+    }
+    // If no corresponding parent link was found, move the link to the top-level.
+    else {
+      $menu_link->plid = 0;
+    }
+
+    // Directly fill parents for top-level links.
+    if ($menu_link->plid == 0) {
+      $menu_link->p1 = $menu_link->id();
+      for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) {
+        $parent_property = "p$i";
+        $menu_link->{$parent_property} = 0;
+      }
+      $menu_link->depth = 1;
+    }
+    // Otherwise, ensure that this link's depth is not beyond the maximum depth
+    // and fill parents based on the parent link.
+    else {
+      if ($menu_link->has_children && $menu_link->original) {
+        $limit = MENU_MAX_DEPTH - $this->findChildrenRelativeDepth($menu_link->original) - 1;
+      }
+      else {
+        $limit = MENU_MAX_DEPTH - 1;
+      }
+      if ($parent->depth > $limit) {
+        return FALSE;
+      }
+      $menu_link->depth = $parent->depth + 1;
+      $this->setParents($menu_link, $parent);
+    }
+
+    // Need to check both plid and menu_name, since plid can be 0 in any menu.
+    if (isset($menu_link->original) && ($menu_link->plid != $menu_link->original->plid || $menu_link->menu_name != $menu_link->original->menu_name)) {
+      $this->moveChildren($menu_link);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setPreventReparenting($value = FALSE) {
+    $this->preventReparenting = $value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPreventReparenting() {
+    return $this->preventReparenting;
+  }
+
+  public function getExpandedParents(array $parents, $menu_name) {
+    $result = $this->database->select('menu_tree')
+      ->fields('menu_tree', array('mlid'))
+      ->condition('menu_name', $menu_name)
+      ->condition('expanded', 1)
+      ->condition('has_children', 1)
+      ->condition('plid', $parents, 'IN')
+      ->condition('mlid', $parents, 'NOT IN')
+      ->execute()
+      ->fetchAllKeyed();
+
+    return $result;
+  }
+
+  public function findNodes($parameters, $menu_name) {
+    $query = $this->database->select('menu_link');
+    $query->addField('menu_link', 'mlid');
+    for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) {
+      $query->orderBy('p' . $i, 'ASC');
+    }
+    $query->condition('menu_name', $menu_name);
+    if (!empty($parameters['expanded'])) {
+      $query->condition('plid', $parameters['expanded'], 'IN');
+    }
+    elseif (!empty($parameters['only_active_trail'])) {
+      $query->condition('mlid', $parameters['active_trail'], 'IN');
+    }
+    $min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1);
+    if ($min_depth != 1) {
+      $query->condition('depth', $min_depth, '>=');
+    }
+    if (isset($parameters['max_depth'])) {
+      $query->condition('depth', $parameters['max_depth'], '<=');
+    }
+    // Add custom query conditions, if any were passed.
+    if (isset($parameters['conditions'])) {
+      foreach ($parameters['conditions'] as $column => $value) {
+        $query->condition($column, $value);
+      }
+    }
+    return $query->execute()->fetchAllKeyed();
+  }
+
+}
+
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageInterface.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuTreeStorageInterface.php
similarity index 55%
copy from core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageInterface.php
copy to core/modules/menu_link/lib/Drupal/menu_link/MenuTreeStorageInterface.php
index 3d3fd5d..10af93a 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageInterface.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuTreeStorageInterface.php
@@ -2,50 +2,22 @@
 
 /**
  * @file
- * Contains \Drupal\menu_link\MenuLinkStorageInterface.
-*/
+ * Contains \Drupal\menu_link\MenuTreeStorageInterface.
+ */
 
 namespace Drupal\menu_link;
 
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityStorageInterface;
-
-/**
- * Defines a common interface for menu link entity controller classes.
- */
-interface MenuLinkStorageInterface extends EntityStorageInterface {
 
-  /**
-   * Sets an internal flag that allows us to prevent the reparenting operations
-   * executed during deletion.
-   *
-   * @param bool $value
-   *   TRUE if reparenting should be allowed, FALSE if it should be prevented.
-   */
-  public function setPreventReparenting($value = FALSE);
-
-  /**
-   * Gets value of internal flag that allows/prevents reparenting operations
-   * executed during deletion.
-   *
-   * @return bool
-   *   TRUE if reparenting is allowed, FALSE if it is prevented.
-   */
-  public function getPreventReparenting();
-
-  /**
-   * Loads system menu link as needed by system_get_module_admin_tasks().
-   *
-   * @return array
-   *   An array of menu link entities indexed by their IDs.
-   */
-  public function loadModuleAdminTasks();
+interface MenuTreeStorageInterface {
 
   /**
    * Checks and updates the 'has_children' property for the parent of a link.
    *
    * @param \Drupal\Core\Entity\EntityInterface $entity
    *   A menu link entity.
+   * @param bool $exclude
+   * @return
    */
   public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE);
 
@@ -75,36 +47,33 @@ public function findChildrenRelativeDepth(EntityInterface $entity);
   public function moveChildren(EntityInterface $entity);
 
   /**
-   * Returns the number of menu links from a menu.
-   *
-   * @param string $menu_name
-   *   The unique name of a menu.
+   * React an saving a menu link.
+   * @param MenuLinkInterface $menu_link
+   * @return
    */
-  public function countMenuLinks($menu_name);
+  public function preSaveMenuLink(MenuLinkInterface $menu_link);
 
   /**
-   * Tries to derive menu link's parent from the path hierarchy.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   A menu link entity.
+   * Sets an internal flag that allows us to prevent the reparenting operations
+   * executed during deletion.
    *
-   * @return \Drupal\Core\Entity\EntityInterface|false
-   *   A menu link entity or FALSE if not valid parent was found.
+   * @param bool $value
+   *   TRUE if reparenting should be allowed, FALSE if it should be prevented.
    */
-  public function getParentFromHierarchy(EntityInterface $entity);
+  public function setPreventReparenting($value = FALSE);
 
   /**
-   * Builds a menu link entity from a default item.
-   *
-   * This function should only be called for link data from
-   * the menu_link.static service.
-   *
-   * @param array $item
-   *   An item returned from the menu_link.static service.
+   * Gets value of internal flag that allows/prevents reparenting operations
+   * executed during deletion.
    *
-   * @return \Drupal\menu_link\MenuLinkInterface
-   *   A menu link entity.
+   * @return bool
+   *   TRUE if reparenting is allowed, FALSE if it is prevented.
    */
-  public function createFromDefaultLink(array $item);
+  public function getPreventReparenting();
+
+  public function getExpandedParents(array $parents, $menu_name);
+
+  public function findNodes($parameters, $menu_name);
 
 }
+
diff --git a/core/modules/menu_link/menu_link.install b/core/modules/menu_link/menu_link.install
index c789d34..b62bc67 100644
--- a/core/modules/menu_link/menu_link.install
+++ b/core/modules/menu_link/menu_link.install
@@ -217,5 +217,118 @@ function menu_link_schema() {
     'primary key' => array('mlid'),
   );
 
+  $schema['menu_tree'] = array(
+    'description' => 'Contains the menu tree hierarchy.',
+    'fields' => array(
+      'menu_name' => array(
+        'description' => "The menu name. All links with the same menu name (such as 'tools') are part of the same menu.",
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'mlid' => array(
+        'description' => 'The menu link ID (mlid) is the integer primary key.',
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'plid' => array(
+        'description' => 'The parent link ID (plid) is the mlid of the link above in the hierarchy, or zero if the link 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 links have this link as a parent (1 = children exist, 0 = no children).',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'small',
+      ),
+      'weight' => array(
+        'description' => 'Link weight among links in the same menu at the same depth.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'depth' => array(
+        'description' => 'The depth relative to the top level. A link with plid == 0 will have depth == 1.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+        'size' => 'small',
+      ),
+      'p1' => array(
+        'description' => 'The first mlid in the materialized path. If N = depth, then pN must equal the mlid. If depth > 1 then p(N-1) must equal the plid. 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 mlid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p3' => array(
+        'description' => 'The third mlid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p4' => array(
+        'description' => 'The fourth mlid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p5' => array(
+        'description' => 'The fifth mlid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p6' => array(
+        'description' => 'The sixth mlid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p7' => array(
+        'description' => 'The seventh mlid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p8' => array(
+        'description' => 'The eighth mlid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'p9' => array(
+        'description' => 'The ninth mlid in the materialized path. See p1.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'indexes' => array(
+      'menu_parents' => array('menu_name', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'),
+    ),
+    'primary key' => array('mlid'),
+  );
+
   return $schema;
 }
diff --git a/core/modules/menu_link/menu_link.module b/core/modules/menu_link/menu_link.module
index ea3bb1f..7cd6156 100644
--- a/core/modules/menu_link/menu_link.module
+++ b/core/modules/menu_link/menu_link.module
@@ -114,7 +114,10 @@ function menu_link_delete_multiple(array $mlids, $force = FALSE, $prevent_repare
   else {
     $entities = $controller->loadMultiple($mlids);
   }
-  $controller->setPreventReparenting($prevent_reparenting);
+  /** @var \Drupal\menu_link\MenuTreeStorageInterface $menu_tree_storage */
+  $menu_tree_storage = \Drupal::service('menu_link.tree_storage');
+  $menu_tree_storage->setPreventReparenting($prevent_reparenting);
+
   $controller->delete($entities);
 }
 
diff --git a/core/modules/menu_link/menu_link.services.yml b/core/modules/menu_link/menu_link.services.yml
index 88f5037..00009fe 100644
--- a/core/modules/menu_link/menu_link.services.yml
+++ b/core/modules/menu_link/menu_link.services.yml
@@ -1,7 +1,10 @@
 services:
   menu_link.tree:
     class: Drupal\menu_link\MenuTree
-    arguments: ['@database', '@cache.data', '@language_manager', '@request_stack', '@entity.manager', '@entity.query', '@state']
+    arguments: ['@cache.data', '@language_manager', '@request_stack', '@entity.manager', '@state', '@menu_link.tree_storage']
   menu_link.static:
     class: Drupal\menu_link\StaticMenuLinks
     arguments: ['@module_handler']
+  menu_link.tree_storage:
+    class: Drupal\menu_link\MenuTreeStorage
+    arguments: ['@database']
diff --git a/core/modules/menu_link/tests/Drupal/menu_link/Tests/MenuTreeTest.php b/core/modules/menu_link/tests/Drupal/menu_link/Tests/MenuTreeTest.php
index 3b44b08..f96d00a 100644
--- a/core/modules/menu_link/tests/Drupal/menu_link/Tests/MenuTreeTest.php
+++ b/core/modules/menu_link/tests/Drupal/menu_link/Tests/MenuTreeTest.php
@@ -97,6 +97,13 @@ class MenuTreeTest extends UnitTestCase {
   );
 
   /**
+   * The mocked menu tree storage.
+   *
+   * @var \Drupal\menu_link\MenuTreeStorageInterface|\PHPUnit_Framework_MockObject_MockObject
+   */
+  protected $menuTreeStorage;
+
+  /**
    * {@inheritdoc}
    */
   public static function getInfo() {
@@ -111,19 +118,14 @@ public static function getInfo() {
    * {@inheritdoc}
    */
   protected function setUp() {
-    $this->connection = $this->getMockBuilder('Drupal\Core\Database\Connection')
-      ->disableOriginalConstructor()
-      ->getMock();
     $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface');
     $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface');
     $this->requestStack = new RequestStack();
     $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface');
-    $this->entityQueryFactory = $this->getMockBuilder('Drupal\Core\Entity\Query\QueryFactory')
-      ->disableOriginalConstructor()
-      ->getMock();
     $this->state = $this->getMock('Drupal\Core\KeyValueStore\StateInterface');
+    $this->menuTreeStorage = $this->getMock('Drupal\menu_link\MenuTreeStorageInterface');
 
-    $this->menuTree = new TestMenuTree($this->connection, $this->cacheBackend, $this->languageManager, $this->requestStack, $this->entityManager, $this->entityQueryFactory, $this->state);
+    $this->menuTree = new TestMenuTree($this->cacheBackend, $this->languageManager, $this->requestStack, $this->entityManager, $this->state, $this->menuTreeStorage);
   }
 
   /**
