diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 3ba3b5d..731d185 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -643,7 +643,12 @@ function _menu_item_localize(&$item, $map, $link_translate = FALSE) {
   //   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 ($item instanceof MenuLink) {
+      $item->localized_options->attributes['class'] = explode(' ', $item->options->attributes['class']);
+    }
+    else {
+      $item['localized_options']['attributes']['class'] = explode(' ', $item['options']['attributes']['class']);
+    }
   }
   // If we are translating the title of a menu link, and its title is the same
   // as the corresponding router item, then we can use the title information
@@ -671,7 +676,12 @@ function _menu_item_localize(&$item, $map, $link_translate = FALSE) {
       }
       // Avoid calling check_plain again on l() function.
       if ($title_callback == 'check_plain') {
-        $item['localized_options']['html'] = TRUE;
+        if ($item instanceof MenuLink) {
+          $item->localized_options->html = TRUE;
+        }
+        else {
+          $item['localized_options']['html'] = TRUE;
+        }
       }
     }
   }
@@ -707,8 +717,8 @@ function _menu_item_localize(&$item, $map, $link_translate = FALSE) {
   }
   // If the title and description are the same, use the translated description
   // as a localized title.
-  if ($link_translate && isset($original_description) && isset($item['options']['attributes']['title']) && $item['options']['attributes']['title'] == $original_description) {
-    $item['localized_options']['attributes']['title'] = $item['description'];
+  if ($link_translate && isset($original_description) && isset($item['options']['attributes']['title']) && $item->options->attributes['title'] == $original_description) {
+    $item->localized_options->setValue(array('title' => $item['description']) + $item['localized_options']['attributes']);
   }
 }
 
@@ -872,21 +882,18 @@ function menu_tail_load($arg, &$map, $index) {
  *   to $item['localized_options'] by _menu_item_localize().
  */
 function _menu_link_translate(&$item, $translate = FALSE) {
-  if (!is_array($item['options'])) {
-    $item['options'] = unserialize($item['options']);
-  }
-  if ($item['external']) {
-    $item['access'] = 1;
+  if ($item->isExternal()) {
+    $item->setAccess(1);
     $map = array();
-    $item['href'] = $item['link_path'];
-    $item['title'] = $item['link_title'];
-    $item['localized_options'] = $item['options'];
+    $item['href'] = $item->getLinkPath();
+    $item['title'] = $item->getLinkTitle();
+    $item->localized_options->setValue($item->options->getValue());
   }
   else {
     // Complete the path of the menu link with elements from the current path,
     // if it contains dynamic placeholders (%).
-    $map = explode('/', $item['link_path']);
-    if (strpos($item['link_path'], '%') !== FALSE) {
+    $map = explode('/', $item->getLinkPath());
+    if (strpos($item->getLinkPath(), '%') !== FALSE) {
       // Invoke registered to_arg callbacks.
       if (!empty($item['to_arg_functions'])) {
         _menu_link_map_translate($map, $item['to_arg_functions']);
@@ -901,7 +908,7 @@ function _menu_link_translate(&$item, $translate = FALSE) {
       elseif ($translate && ($current_router_item = menu_get_item())) {
         // If $translate is TRUE, then this link is in the active trail.
         // Only translate paths within the current path.
-        if (strpos($current_router_item['path'], $item['link_path']) === 0) {
+        if (strpos($current_router_item['path'], $item->getLinkPath()) === 0) {
           $count = count($map);
           $map = array_slice($current_router_item['original_map'], 0, $count);
           $item['original_map'] = $map;
@@ -909,7 +916,7 @@ function _menu_link_translate(&$item, $translate = FALSE) {
             $item['map'] = array_slice($current_router_item['map'], 0, $count);
           }
           // Reset access to check it (for the first time).
-          unset($item['access']);
+          $item->setAccess(NULL);
         }
       }
     }
@@ -917,17 +924,17 @@ function _menu_link_translate(&$item, $translate = FALSE) {
 
     // Skip links containing untranslated arguments.
     if (strpos($item['href'], '%') !== FALSE) {
-      $item['access'] = FALSE;
+      $item->setAccess(FALSE);
       return FALSE;
     }
     // menu_tree_check_access() may set this ahead of time for links to nodes.
-    if (!isset($item['access'])) {
+    if ($item->getAccess() === NULL) {
       if ($route = $item->getRoute()) {
-        $item['access'] = menu_item_route_access($route, $item['href'], $map);
+        $item->setAccess(menu_item_route_access($route, $item['href'], $map));
       }
       elseif (!empty($item['load_functions']) && !_menu_load_objects($item, $map)) {
         // An error occurred loading an object.
-        $item['access'] = FALSE;
+        $item->setAccess(FALSE);
         return FALSE;
       }
       // Apply the access check defined in hook_menu() if there is not route
@@ -937,7 +944,7 @@ function _menu_link_translate(&$item, $translate = FALSE) {
       }
     }
     // For performance, don't localize a link the user can't access.
-    if ($item['access']) {
+    if ($item->getAccess()) {
       _menu_item_localize($item, $map, TRUE);
     }
   }
@@ -945,7 +952,7 @@ function _menu_link_translate(&$item, $translate = FALSE) {
   // Allow other customizations - e.g. adding a page-specific query string to the
   // options array. For performance reasons we only invoke this hook if the link
   // has the 'alter' flag set in the options array.
-  if (!empty($item['options']['alter'])) {
+  if (!empty($item->options->alter)) {
     drupal_alter('translated_menu_link', $item, $map);
   }
 
@@ -1097,7 +1104,7 @@ function menu_tree_output($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']->getAccess() && !$data['link']->isHidden()) {
       $items[] = $data;
     }
   }
@@ -1115,45 +1122,48 @@ function menu_tree_output($tree) {
     // Set a class for the <li>-tag. Since $data['below'] may contain local
     // tasks, only set 'expanded' class if the link also has children within
     // the current menu.
-    if ($data['link']['has_children'] && $data['below']) {
+    if ($data['link']->hasChildren() && $data['below']) {
       $class[] = 'expanded';
     }
-    elseif ($data['link']['has_children']) {
+    elseif ($data['link']->hasChildren()) {
       $class[] = 'collapsed';
     }
     else {
       $class[] = 'leaf';
     }
     // Set a class if the link is in the active trail.
-    if ($data['link']['in_active_trail']) {
+    $localized_attributes = $data['link']->localized_options->attributes;
+    if ($data['link']->in_active_trail->value) {
       $class[] = 'active-trail';
-      $data['link']['localized_options']['attributes']['class'][] = 'active-trail';
+      $localized_attributes['class'][] = 'active-trail';
     }
     // Normally, l() compares the href of every link with the current path and
     // sets the active class accordingly. But local tasks do not appear in menu
     // trees, so if the current path is a local task, and this link is its
     // tab root, then we have to set the class manually.
     if ($data['link']['href'] == $router_item['tab_root_href'] && $data['link']['href'] != current_path()) {
-      $data['link']['localized_options']['attributes']['class'][] = 'active';
+      $localized_attributes['class'][] = 'active';
     }
+    $data['link']->localized_options->attributes = $localized_attributes;
 
     // Allow menu-specific theme overrides.
-    $element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_');
+    $element['#theme'] = 'menu_link__' . strtr($data['link']->getMenuName(), '-', '_');
     $element['#attributes']['class'] = $class;
     $element['#title'] = $data['link']['title'];
     $element['#href'] = $data['link']['href'];
-    $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array();
+    $localized_options = $data['link']['localized_options'];
+    $element['#localized_options'] = !empty($localized_options) ? $localized_options : array();
     $element['#below'] = $data['below'] ? menu_tree_output($data['below']) : $data['below'];
     $element['#original_link'] = $data['link'];
     // Index using the link's unique mlid.
-    $build[$data['link']['mlid']] = $element;
+    $build[$data['link']->id()] = $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__' . strtr($data['link']->getMenuName(), '-', '_');
   }
 
   return $build;
@@ -1361,12 +1371,13 @@ function menu_tree_page_data($menu_name, $max_depth = NULL, $only_active_trail =
             // active trail, if it resides in the requested menu. Otherwise,
             // we'd needlessly re-run _menu_build_tree() queries for every menu
             // on every page.
-            if ($active_link['menu_name'] == $menu_name) {
+            if ($active_link->getMenuName() == $menu_name) {
               // Use all the coordinates, except the last one because there
               // can be no child beyond the last column.
               for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
-                if ($active_link['p' . $i]) {
-                  $active_trail[$active_link['p' . $i]] = $active_link['p' . $i];
+                $p_i = 'p' . $i;
+                if ($active_link->{$p_i}->value) {
+                  $active_trail[$active_link->{$p_i}->value] = $active_link->{$p_i}->value;
                 }
               }
               // If we are asked to build links for the active trail only, skip
@@ -1531,11 +1542,11 @@ function _menu_build_tree($menu_name, array $parameters = array()) {
  */
 function menu_tree_collect_node_links(&$tree, &$node_links) {
   foreach ($tree as $key => $v) {
-    if ($tree[$key]['link']['router_path'] == 'node/%') {
-      $nid = substr($tree[$key]['link']['link_path'], 5);
+    if ($tree[$key]['link']->getRouterPath() == 'node/%') {
+      $nid = substr($tree[$key]['link']->getLinkPath(), 5);
       if (is_numeric($nid)) {
-        $node_links[$nid][$tree[$key]['link']['mlid']] = &$tree[$key]['link'];
-        $tree[$key]['link']['access'] = FALSE;
+        $node_links[$nid][$tree[$key]['link']->id()] = &$tree[$key]['link'];
+        $tree[$key]['link']->set('access', FALSE);
       }
     }
     if ($tree[$key]['below']) {
@@ -1582,14 +1593,14 @@ function _menu_tree_check_access(&$tree) {
   foreach ($tree as $key => $v) {
     $item = &$tree[$key]['link'];
     _menu_link_translate($item);
-    if ($item['access'] || ($item['in_active_trail'] && strpos($item['href'], '%') !== FALSE)) {
+    if ($item->getAccess() || ($item->in_active_trail->value && strpos($item['href'], '%') !== FALSE)) {
       if ($tree[$key]['below']) {
         _menu_tree_check_access($tree[$key]['below']);
       }
       // 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->getWeight()) . ' ' . $item['title'] . ' ' . $item->id()] = $tree[$key];
     }
   }
   // Sort siblings in the tree based on the weights and localized titles.
@@ -1640,9 +1651,9 @@ 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->value = in_array($item->id(), $parents);
     // Add the current link to the tree.
-    $tree[$item['mlid']] = array(
+    $tree[$item->id()] = array(
       'link' => $item,
       'below' => array(),
     );
@@ -1650,14 +1661,14 @@ function _menu_tree_data(&$links, $parents, $depth) {
     // to other recursive function calls if we return or build a sub-tree.
     $next = end($links);
     // Check whether the next link is the first in a new sub-tree.
-    if ($next && $next['depth'] > $depth) {
+    if ($next && $next->getDepth() > $depth) {
       // Recursively call _menu_tree_data to build the sub-tree.
-      $tree[$item['mlid']]['below'] = _menu_tree_data($links, $parents, $next['depth']);
+      $tree[$item->id()]['below'] = _menu_tree_data($links, $parents, $next->getDepth());
       // Fetch next link after filling the sub-tree.
       $next = end($links);
     }
     // Determine if we should exit the loop and return.
-    if (!$next || $next['depth'] < $depth) {
+    if (!$next || $next->getDepth() < $depth) {
       break;
     }
   }
@@ -1907,12 +1918,12 @@ function menu_navigation_links($menu_name, $level = 0) {
   $router_item = menu_get_item();
   $links = array();
   foreach ($tree as $item) {
-    if (!$item['link']['hidden']) {
+    if (!$item['link']->isHidden()) {
       $class = '';
-      $l = $item['link']['localized_options'];
+      $l = $item['link']->localized_options->getValue();
       $l['href'] = $item['link']['href'];
       $l['title'] = $item['link']['title'];
-      if ($item['link']['in_active_trail']) {
+      if ($item['link']->in_active_trail->value) {
         $class = ' active-trail';
         $l['attributes']['class'][] = 'active-trail';
       }
@@ -1924,7 +1935,7 @@ function menu_navigation_links($menu_name, $level = 0) {
         $l['attributes']['class'][] = 'active';
       }
       // Keyed with the unique mlid to generate classes in theme_links().
-      $links['menu-' . $item['link']['mlid'] . $class] = $l;
+      $links['menu-' . $item['link']->id() . $class] = $l;
     }
   }
   return $links;
@@ -2478,7 +2489,7 @@ function menu_set_active_trail($new_trail = NULL) {
       // Pass TRUE for $only_active_trail to make menu_tree_page_data() build
       // a stripped down menu tree containing the active trail only, in case
       // the given menu has not been built in this request yet.
-      $tree = menu_tree_page_data($preferred_link['menu_name'], NULL, TRUE);
+      $tree = menu_tree_page_data($preferred_link->getMenuName(), NULL, TRUE);
       list($key, $curr) = each($tree);
     }
     // There is no link for the current path.
@@ -2489,7 +2500,7 @@ function menu_set_active_trail($new_trail = NULL) {
 
     while ($curr) {
       $link = $curr['link'];
-      if ($link['in_active_trail']) {
+      if ($link->in_active_trail->value) {
         // Add the link to the trail, unless it links to its parent.
         if (!($link['type'] & MENU_LINKS_TO_PARENT)) {
           // The menu tree for the active trail may contain additional links
@@ -2503,7 +2514,7 @@ function menu_set_active_trail($new_trail = NULL) {
           if (strpos($link['href'], '%') !== FALSE) {
             _menu_link_translate($link, TRUE);
           }
-          if ($link['access']) {
+          if ($link->getAccess()) {
             $trail[] = $link;
           }
         }
@@ -2581,10 +2592,10 @@ function menu_link_get_preferred($path = NULL, $selected_menu = NULL) {
     // Sort candidates by link path and menu name.
     $candidates = array();
     foreach ($menu_links as $candidate) {
-      $candidates[$candidate['link_path']][$candidate['menu_name']] = $candidate;
+      $candidates[$candidate->getLinkPath()][$candidate->getMenuName()] = $candidate;
       // Add any menus not already in the menu name search list.
-      if (!in_array($candidate['menu_name'], $menu_names)) {
-        $menu_names[] = $candidate['menu_name'];
+      if (!in_array($candidate->getMenuName(), $menu_names)) {
+        $menu_names[] = $candidate->getMenuName();
       }
     }
 
@@ -2597,7 +2608,7 @@ function menu_link_get_preferred($path = NULL, $selected_menu = NULL) {
             $candidate_item = $candidates[$link_path][$menu_name];
             $map = explode('/', $path);
             _menu_translate($candidate_item, $map);
-            if ($candidate_item['access']) {
+            if ($candidate_item->getAccess()) {
               $preferred_links[$path][$menu_name] = $candidate_item;
               if (empty($preferred_links[$path][MENU_PREFERRED_LINK])) {
                 // Store the most specific link.
@@ -2840,6 +2851,8 @@ function _menu_navigation_links_rebuild($menu) {
     array_multisort($sort, SORT_NUMERIC, $router_items);
 
     foreach ($router_items as $key => $router_item) {
+      // Add the path to the item.
+      $router_item['path'] = $key;
       // For performance reasons, do a straight query now and convert to a menu
       // link entity later.
       // @todo revisit before release.
@@ -2847,9 +2860,8 @@ function _menu_navigation_links_rebuild($menu) {
         ->fields('menu_links')
         ->condition('link_path', $router_item['path'])
         ->condition('module', 'system')
-        ->execute()->fetchAll();
+        ->execute()->fetch();
       if ($existing_item) {
-        $existing_item = reset($existing_item);
         $existing_item->options = unserialize($existing_item->options);
 
         $router_item['mlid'] = $existing_item->mlid;
@@ -2875,8 +2887,8 @@ function _menu_navigation_links_rebuild($menu) {
         $existing_item = NULL;
       }
 
-      if ($existing_item && $existing_item->customized) {
-        $parent_candidates[$existing_item->mlid] = $existing_item;
+      if ($existing_item && $existing_item->customized->value) {
+        $parent_candidates[$existing_item->id()] = $existing_item;
       }
       else {
         $menu_link = MenuLink::buildFromRouterItem($router_item);
@@ -2893,11 +2905,11 @@ function _menu_navigation_links_rebuild($menu) {
   // Updated and customized items whose router paths are gone need new ones.
   $menu_links = $menu_link_controller->loadUpdatedCustomized($paths);
   foreach ($menu_links as $menu_link) {
-    $router_path = _menu_find_router_path($menu_link->link_path);
-    if (!empty($router_path) && ($router_path != $menu_link->router_path || $menu_link->updated)) {
+    $router_path = _menu_find_router_path($menu_link->link_path->value);
+    if (!empty($router_path) && ($router_path != $menu_link->router_path->value || $menu_link->updated->value)) {
       // If the router path and the link path matches, it's surely a working
       // item, so we clear the updated flag.
-      $updated = $menu_link->updated && $router_path != $menu_link->link_path;
+      $updated = $menu_link->updated->value && $router_path != $menu_link->link_path->value;
 
       $menu_link->router_path = $router_path;
       $menu_link->updated = (int) $updated;
diff --git a/core/includes/path.inc b/core/includes/path.inc
index 2187dd3..52ab706 100644
--- a/core/includes/path.inc
+++ b/core/includes/path.inc
@@ -204,7 +204,7 @@ function drupal_valid_path($path, $dynamic_allowed = FALSE) {
       $item['link_path']  = $form_item['link_path'];
       $item['link_title'] = $form_item['link_title'];
       $item['external']   = FALSE;
-      $item['options'] = '';
+      $item['options'] = array();
       _menu_link_translate($item);
     }
   }
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index b65b71e..0888ed4 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -1703,9 +1703,9 @@ function theme_links($variables) {
       // Handle title-only text items.
       else {
         // Merge in default array properties into $link.
-        $link += array(
-          'html' => FALSE,
-        );
+        if (!isset($link['html'])) {
+          $link['html'] = FALSE;
+        }
         $item = ($link['html'] ? $link['title'] : check_plain($link['title']));
         if (isset($link['attributes'])) {
           $item = '<span' . new Attribute($link['attributes']) . '>' . $item . '</span>';
diff --git a/core/lib/Drupal/Core/Entity/EntityNG.php b/core/lib/Drupal/Core/Entity/EntityNG.php
index 49fa3a0..10e8237 100644
--- a/core/lib/Drupal/Core/Entity/EntityNG.php
+++ b/core/lib/Drupal/Core/Entity/EntityNG.php
@@ -617,7 +617,7 @@ public function updateOriginalValues() {
   }
 
   /**
-   * Implements the magic method for setting object properties.
+   * Implements the magic method for getting object properties.
    *
    * @todo: A lot of code still uses non-fields (e.g. $entity->content in render
    *   controllers) by reference. Clean that up.
diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/MapItem.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/MapItem.php
new file mode 100644
index 0000000..b2e55ce
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/MapItem.php
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Entity\Plugin\DataType\MapItem.
+ */
+
+namespace Drupal\Core\Entity\Plugin\DataType;
+
+use Drupal\Core\TypedData\Annotation\DataType;
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Entity\Field\FieldItemBase;
+use Drupal\Core\TypedData\TypedData;
+
+/**
+ * Defines the 'map_field' entity field item.
+ *
+ * @DataType(
+ *   id = "map_field",
+ *   label = @Translation("Map field item"),
+ *   description = @Translation("An entity field containing a map value."),
+ *   list_class = "\Drupal\Core\Entity\Field\Field"
+ * )
+ */
+class MapItem extends FieldItemBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPropertyDefinitions() {
+    $definitions = array();
+    foreach ($this->properties as $name => $property) {
+      $definitions[$name] = $property->getDefinition();
+    }
+
+    return $definitions;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\Field\FieldItemBase::setValue().
+   *
+   * @param array|null $values
+   *   An array of property values.
+   */
+  public function setValue($values, $notify = TRUE) {
+    $this->properties = array();
+    if (!isset($values)) {
+      return;
+    }
+
+    if (!is_array($values)) {
+      if ($values instanceof MapItem) {
+        $values = $values->getValue();
+      }
+      else {
+        $values = unserialize($values);
+      }
+    }
+
+    foreach($values as $name => $value) {
+      $this->properties[$name] = $this->valueToProperty($value, $name);
+    }
+  }
+
+  public function __get($name) {
+    if (!isset($this->properties[$name])) {
+      $this->properties[$name] = $this->valueToProperty(array(), $name);
+    }
+
+    return $this->properties[$name]->getValue();
+  }
+
+  public function __set($name, $value) {
+    if (isset($value)) {
+      if (isset($this->properties[$name])) {
+        $this->properties[$name]->setValue($value);
+      }
+      else {
+        $this->properties[$name] = $this->valueToProperty($value, $name);
+      }
+    }
+    else {
+      unset($this->properties[$name]);
+    }
+  }
+
+  protected function valueToProperty($value, $name) {
+    $def = array('type' => 'any');
+    if ($value instanceof TypedData) {
+      $def = $value->getDefinition();
+      $value = $value->getValue();
+    }
+
+    return \Drupal::typedData()->create($def, $value, $name, $this);
+  }
+}
\ No newline at end of file
diff --git a/core/modules/book/book.module b/core/modules/book/book.module
index 282aa92..fd86043 100644
--- a/core/modules/book/book.module
+++ b/core/modules/book/book.module
@@ -58,7 +58,7 @@ function book_entity_bundle_info() {
 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) {
+    if (strpos($entity->menu_name->value, 'book-toc-') === 0) {
       $entity->bundle = 'book-toc';
     }
   }
@@ -904,7 +904,7 @@ function book_menu_subtree_data($link) {
       }
       $links = array();
       foreach ($query->execute() as $item) {
-        $links[] = $item;
+        $links[] = entity_create('menu_link', $item);
       }
       $data['tree'] = menu_tree_data($links, array(), $link['depth']);
       $data['node_links'] = array();
diff --git a/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php b/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php
index 2fef927..93d9e8e 100644
--- a/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php
+++ b/core/modules/book/lib/Drupal/book/BookBreadcrumbBuilder.php
@@ -85,8 +85,8 @@ public function build(array $attributes) {
         $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)) {
-              $links[] = $this->linkGenerator->generate($menu_link->label(), $menu_link->route_name, $menu_link->route_parameters, $menu_link->options);
+            if ($this->accessManager->checkNamedRoute($menu_link->route_name->value, $menu_link->route_parameters[0]->getValue())) {
+              $links[] = $this->linkGenerator->generate($menu_link->label(), $menu_link->route_name->value, $menu_link->route_parameters[0]->getValue(), $menu_link->options[0]->getValue());
             }
           }
           $depth++;
diff --git a/core/modules/book/lib/Drupal/book/BookManager.php b/core/modules/book/lib/Drupal/book/BookManager.php
index d154c46..bbd59c5 100644
--- a/core/modules/book/lib/Drupal/book/BookManager.php
+++ b/core/modules/book/lib/Drupal/book/BookManager.php
@@ -108,7 +108,7 @@ protected function loadBooks() {
           $link['options'] = unserialize($link['options']);
           $link['title'] = $nodes[$nid]->label();
           $link['type'] = $nodes[$nid]->bundle();
-          $this->books[$link['bid']] = $link;
+          $this->books[$link['bid']] = entity_create('menu_link', $link);
         }
       }
     }
diff --git a/core/modules/menu/lib/Drupal/menu/Form/MenuLinkDeleteForm.php b/core/modules/menu/lib/Drupal/menu/Form/MenuLinkDeleteForm.php
index 2815a91..43db3a2 100644
--- a/core/modules/menu/lib/Drupal/menu/Form/MenuLinkDeleteForm.php
+++ b/core/modules/menu/lib/Drupal/menu/Form/MenuLinkDeleteForm.php
@@ -18,7 +18,7 @@ class MenuLinkDeleteForm extends EntityConfirmFormBase {
    * {@inheritdoc}
    */
   public function getQuestion() {
-    return t('Are you sure you want to delete the custom menu link %item?', array('%item' => $this->entity->link_title));
+    return t('Are you sure you want to delete the custom menu link %item?', array('%item' => $this->entity->link_title->value));
   }
 
   /**
@@ -28,7 +28,7 @@ public function getCancelRoute() {
     return array(
       'route_name' => 'menu.menu_edit',
       'route_parameters' => array(
-        'menu' => $this->entity->menu_name,
+        'menu' => $this->entity->menu_name->value,
       ),
     );
   }
@@ -38,10 +38,10 @@ public function getCancelRoute() {
    */
   public function submit(array $form, array &$form_state) {
     menu_link_delete($this->entity->id());
-    $t_args = array('%title' => $this->entity->link_title);
+    $t_args = array('%title' => $this->entity->getLinkTitle());
     drupal_set_message(t('The menu link %title has been deleted.', $t_args));
     watchdog('menu', 'Deleted menu link %title.', $t_args, WATCHDOG_NOTICE);
-    $form_state['redirect'] = 'admin/structure/menu/manage/' . $this->entity->menu_name;
+    $form_state['redirect'] = 'admin/structure/menu/manage/' . $this->entity->getMenuName();
   }
 
 }
diff --git a/core/modules/menu/lib/Drupal/menu/Form/MenuLinkResetForm.php b/core/modules/menu/lib/Drupal/menu/Form/MenuLinkResetForm.php
index 5b79ef4..6f41606 100644
--- a/core/modules/menu/lib/Drupal/menu/Form/MenuLinkResetForm.php
+++ b/core/modules/menu/lib/Drupal/menu/Form/MenuLinkResetForm.php
@@ -18,7 +18,7 @@ class MenuLinkResetForm extends EntityConfirmFormBase {
    * {@inheritdoc}
    */
   public function getQuestion() {
-    return t('Are you sure you want to reset the link %item to its default values?', array('%item' => $this->entity->link_title));
+    return t('Are you sure you want to reset the link %item to its default values?', array('%item' => $this->entity->link_title->value));
   }
 
   /**
@@ -28,7 +28,7 @@ public function getCancelRoute() {
     return array(
       'route_name' => 'menu.menu_edit',
       'route_parameters' => array(
-        'menu' => $this->entity->menu_name,
+        'menu' => $this->entity->menu_name->value,
       ),
     );
   }
@@ -53,7 +53,7 @@ public function getConfirmText() {
   public function submit(array $form, array &$form_state) {
     $new_menu_link = $this->entity->reset();
     drupal_set_message(t('The menu link was reset to its default settings.'));
-    $form_state['redirect'] = 'admin/structure/menu/manage/' . $new_menu_link->menu_name;
+    $form_state['redirect'] = 'admin/structure/menu/manage/' . $new_menu_link->getMenuName();
   }
 
 }
diff --git a/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php b/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php
index f7e0690..e0ac0d7 100644
--- a/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php
+++ b/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php
@@ -82,13 +82,13 @@ function testMenu() {
     $item = entity_load('menu_link', $item['mlid']);
     // Verify that a change to the description is saved.
     $description = $this->randomName(16);
-    $item['options']['attributes']['title']  = $description;
+    $item->options->attributes = array('title' => $description);
     $return_value = menu_link_save($item);
     // Save the menu link again to test the return value of the procedural save
     // helper.
     $this->assertIdentical($return_value, $item->save(), 'Return value of menu_link_save() is identical to the return value of $menu_link->save().');
     $saved_item = entity_load('menu_link', $item['mlid']);
-    $this->assertEqual($description, $saved_item['options']['attributes']['title'], 'Saving an existing link updates the description (title attribute)');
+    $this->assertEqual($description, $saved_item->options->attributes['title'], 'Saving an existing link updates the description (title attribute)');
     $this->resetMenuLink($item, $old_title);
   }
 
@@ -448,7 +448,7 @@ public function testMenuBundles() {
 
     $unsaved_item = entity_create('menu_link', array('menu_name' => $menu->id(), 'link_title' => $this->randomName(16), 'link_path' => '<front>'));
     $this->assertEqual($unsaved_item->bundle(), $menu->id(), 'Unsaved menu link bundle matches the menu');
-    $this->assertEqual($unsaved_item->menu_name, $menu->id(), 'Unsaved menu link menu name matches the menu');
+    $this->assertEqual($unsaved_item->menu_name->value, $menu->id(), 'Unsaved menu link menu name matches the menu');
 
   }
 
diff --git a/core/modules/menu/menu.install b/core/modules/menu/menu.install
index 61f73b2..04c0dbb 100644
--- a/core/modules/menu/menu.install
+++ b/core/modules/menu/menu.install
@@ -18,7 +18,7 @@ function menu_install() {
   $system_link = reset($system_link);
 
   $base_link = entity_create('menu_link', array(
-    'menu_name' => $system_link->menu_name,
+    'menu_name' => $system_link->getMenuName(),
     'router_path' => 'admin/structure/menu/manage/%',
     'module' => 'menu',
   ));
@@ -31,8 +31,8 @@ function menu_install() {
     $link->link_path = 'admin/structure/menu/manage/' . $menu->id();
 
     $query = \Drupal::entityQuery('menu_link')
-      ->condition('link_path', $link->link_path)
-      ->condition('plid', $link->plid);
+      ->condition('link_path', $link->getLinkPath())
+      ->condition('plid', $link->getParentLinkId());
     $result = $query->execute();
 
     if (empty($result)) {
diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module
index 1689d35..b0b1d27 100644
--- a/core/modules/menu/menu.module
+++ b/core/modules/menu/menu.module
@@ -351,7 +351,7 @@ function menu_block_view_system_menu_block_alter(array &$build, BlockPluginInter
   list(, $menu_name) = explode(':', $block->getPluginId());
   if (isset($menus[$menu_name]) && isset($build['content'])) {
     foreach (element_children($build['content']) as $key) {
-      $build['content']['#contextual_links']['menu'] = array('admin/structure/menu/manage', array($build['content'][$key]['#original_link']['menu_name']));
+      $build['content']['#contextual_links']['menu'] = array('admin/structure/menu/manage', array($build['content'][$key]['#original_link']->getMenuName()));
     }
   }
 }
@@ -561,7 +561,7 @@ function menu_form_node_form_alter(&$form, $form_state) {
   );
 
   // Get number of items in menu so the weight selector is sized appropriately.
-  $delta = entity_get_controller('menu_link')->countMenuLinks($link->menu_name);
+  $delta = entity_get_controller('menu_link')->countMenuLinks($link->getMenuName());
   if ($delta < 50) {
     // Old hardcoded value
     $delta = 50;
@@ -585,7 +585,8 @@ function menu_node_submit(EntityInterface $node, $form, $form_state) {
   // Decompose the selected menu parent option into 'menu_name' and 'plid', if
   // the form used the default parent selection widget.
   if (!empty($form_state['values']['menu']['parent'])) {
-    list($node->menu['menu_name'], $node->menu['plid']) = explode(':', $form_state['values']['menu']['parent']);
+    $parts = explode(':', $form_state['values']['menu']['parent']);
+    $node->menu->setMenuName($parts[0])->setParentLinkId($parts[1]);
   }
 }
 
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 0fc739a..878ace8 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
@@ -7,16 +7,18 @@
 
 namespace Drupal\menu_link\Entity;
 
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Entity\Annotation\EntityType;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityNG;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\TypedData\TypedDataInterface;
+use Drupal\Component\Uuid\Uuid;
 use Drupal\menu_link\MenuLinkInterface;
+use Drupal\menu_link\MenuLinkStorageControllerInterface;
 use Symfony\Component\Routing\Route;
 use Symfony\Component\HttpFoundation\Request;
 
-use Drupal\Core\Entity\EntityStorageException;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityStorageControllerInterface;
-use Drupal\Core\Entity\ContentEntityInterface;
-use Drupal\Core\Entity\Entity;
-
 /**
  * Defines the menu link entity class.
  *
@@ -47,14 +49,7 @@
  *   }
  * )
  */
-class MenuLink extends Entity implements \ArrayAccess, MenuLinkInterface {
-
-  /**
-   * The link's menu name.
-   *
-   * @var string
-   */
-  public $menu_name = 'tools';
+class MenuLink extends EntityNG implements \ArrayAccess, MenuLinkInterface {
 
   /**
    * The link's bundle.
@@ -64,215 +59,40 @@ class MenuLink extends Entity implements \ArrayAccess, MenuLinkInterface {
   public $bundle = 'tools';
 
   /**
-   * The menu link ID.
-   *
-   * @var int
-   */
-  public $mlid;
-
-  /**
-   * The menu link UUID.
-   *
-   * @var string
-   */
-  public $uuid;
-
-  /**
-   * The parent link ID.
-   *
-   * @var int
-   */
-  public $plid;
-
-  /**
-   * The Drupal path or external path this link points to.
-   *
-   * @var string
-   */
-  public $link_path;
-
-  /**
-   * For links corresponding to a Drupal path (external = 0), this connects the
-   * link to a {menu_router}.path for joins.
-   *
-   * @var string
-   */
-  public $router_path;
-
-  /**
-   * The entity label.
-   *
-   * @var string
-   */
-  public $link_title = '';
-
-  /**
-   * A serialized array of options to be passed to the url() or l() function,
-   * such as a query string or HTML attributes.
-   *
-   * @var array
-   */
-  public $options = array();
-
-  /**
-   * The name of the module that generated this link.
-   *
-   * @var string
-   */
-  public $module = 'menu';
-
-  /**
-   * A flag for whether the link should be rendered in menus.
-   *
-   * @var int
-   */
-  public $hidden = 0;
-
-  /**
-   * A flag to indicate if the link points to a full URL starting with a
-   * protocol, like http:// (1 = external, 0 = internal).
-   *
-   * @var int
-   */
-  public $external;
-
-  /**
-   * Flag indicating whether any links have this link as a parent.
-   *
-   * @var int
-   */
-  public $has_children = 0;
-
-  /**
-   * Flag for whether this link should be rendered as expanded in menus.
-   * Expanded links always have their child links displayed, instead of only
-   * when the link is in the active trail.
-   *
-   * @var int
-   */
-  public $expanded = 0;
-
-  /**
-   * Link weight among links in the same menu at the same depth.
-   *
-   * @var int
-   */
-  public $weight = 0;
-
-  /**
-   * The depth relative to the top level. A link with plid == 0 will have
-   * depth == 1.
-   *
-   * @var int
-   */
-  public $depth;
-
-  /**
-   * A flag to indicate that the user has manually created or edited the link.
-   *
-   * @var int
-   */
-  public $customized = 0;
-
-  /**
-   * The first entity ID in the materialized path.
-   *
-   * @var int
-   *
-   * @todo Investigate whether the p1, p2, .. pX properties can be moved to a
-   * single array property.
-   */
-  public $p1;
-
-  /**
-   * The second entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p2;
-
-  /**
-   * The third entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p3;
-
-  /**
-   * The fourth entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p4;
-
-  /**
-   * The fifth entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p5;
-
-  /**
-   * The sixth entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p6;
-
-  /**
-   * The seventh entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p7;
-
-  /**
-   * The eighth entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p8;
-
-  /**
-   * The ninth entity ID in the materialized path.
-   *
-   * @var int
-   */
-  public $p9;
-
-  /**
-   * The menu link modification timestamp.
+   * The route object associated with this menu link, if any.
    *
-   * @var int
+   * @var \Symfony\Component\Routing\Route
    */
-  public $updated = 0;
+  protected $routeObject;
 
   /**
-   * The name of the route associated with this menu link, if any.
+   * Crap coming from the old routing system.
    *
-   * @var string
-   */
-  public $route_name;
-
-  /**
-   * The parameters of the route associated with this menu link, if any.
+   * @todo Remove when we rip out the old routing system.
    *
    * @var array
    */
-  public $route_parameters;
+  protected $oldRouterItem = array();
 
   /**
    * The route object associated with this menu link, if any.
    *
    * @var \Symfony\Component\Routing\Route
    */
-  protected $routeObject;
+  protected $oldRoutingProperties = array(
+    'path', 'load_functions', 'to_arg_functions', 'access_callback',
+    'access_arguments', 'page_callback', 'page_arguments', 'fit',
+    'number_parts', 'context', 'tab_parent', 'tab_root', 'title',
+    'title_callback', 'title_arguments', 'theme_callback', 'theme_arguments',
+    'type', 'description', 'description_callback', 'description_arguments',
+    'position', 'include_file',
+  );
 
   /**
-   * Overrides Entity::id().
+   * {@inheritdoc}
    */
   public function id() {
-    return $this->mlid;
+    return $this->get('mlid')->value;
   }
 
   /**
@@ -287,7 +107,8 @@ public function bundle() {
    */
   public function createDuplicate() {
     $duplicate = parent::createDuplicate();
-    $duplicate->plid = NULL;
+
+    $duplicate->get('plid')->offsetGet(0)->set('value', NULL);
     return $duplicate;
   }
 
@@ -295,12 +116,12 @@ public function createDuplicate() {
    * {@inheritdoc}
    */
   public function getRoute() {
-    if (!$this->route_name) {
+    if (!$this->getRouteName()) {
       return NULL;
     }
     if (!($this->routeObject instanceof Route)) {
       $route_provider = \Drupal::service('router.route_provider');
-      $this->routeObject = $route_provider->getRouteByName($this->route_name);
+      $this->routeObject = $route_provider->getRouteByName($this->getRouteName());
     }
     return $this->routeObject;
   }
@@ -322,11 +143,11 @@ public function reset() {
     // not stored anywhere else. Since resetting a link happens rarely and this
     // is a one-time operation, retrieving the full menu router does no harm.
     $menu = menu_get_router();
-    $router_item = $menu[$this->router_path];
+    $router_item = $menu[$this->getRouterPath()];
     $new_link = self::buildFromRouterItem($router_item);
     // Merge existing menu link's ID and 'has_children' property.
     foreach (array('mlid', 'has_children') as $key) {
-      $new_link->{$key} = $this->{$key};
+      $new_link->{$key}->value = $this->{$key}->value;
     }
     $new_link->save();
     return $new_link;
@@ -351,37 +172,101 @@ public static function buildFromRouterItem(array $item) {
       'link_title' => $item['title'],
       'link_path' => $item['path'],
       'options' => empty($item['description']) ? array() : array('attributes' => array('title' => $item['description'])),
+      'route_name' => $item['route_name'],
     );
     return \Drupal::entityManager()
       ->getStorageController('menu_link')->create($item);
   }
 
   /**
-   * Implements ArrayAccess::offsetExists().
+   * {@inheritdoc}
    */
   public function offsetExists($offset) {
-    return isset($this->{$offset});
+    if (in_array($offset, $this->oldRoutingProperties)) {
+      return isset($this->oldRouterItem[$offset]);
+    }
+    return isset($this->{$offset}->value);
   }
 
   /**
-   * Implements ArrayAccess::offsetGet().
+   * {@inheritdoc}
    */
-  public function &offsetGet($offset) {
-    return $this->{$offset};
+  public function offsetGet($offset) {
+    if (in_array($offset, $this->oldRoutingProperties)) {
+      // For now we simply return NULL if a property isn't found.
+      return (isset($this->oldRouterItem[$offset])) ? $this->oldRouterItem[$offset] : NULL;
+    }
+    elseif (in_array($offset, array('options', 'localized_options', 'route_parameters'))) {
+      $values = $this->{$offset}[0]->getValue();
+      return $values;
+    }
+    elseif ($this->getPropertyDefinition($offset)) {
+      return $this->get($offset)->value;
+    }
+    else {
+      return $this->$offset;
+    }
   }
 
   /**
-   * Implements ArrayAccess::offsetSet().
+   * {@inheritdoc}
    */
   public function offsetSet($offset, $value) {
-    $this->{$offset} = $value;
+    if (in_array($offset, $this->oldRoutingProperties)) {
+      $this->oldRouterItem[$offset] = $value;
+    }
+    elseif (in_array($offset, array('options', 'localized_options', 'route_parameters'))) {
+      if (is_array($value)) {
+        foreach ($value as $delta => $value_item) {
+          if (!is_numeric($delta)) {
+            $this->{$offset} = array($value);
+            return;
+          }
+        }
+      }
+    }
+    elseif ($this->getPropertyDefinition($offset)) {
+      $this->{$offset} = $value;
+    }
+    else {
+      $this->{$offset} = $value;
+    }
   }
 
   /**
-   * Implements ArrayAccess::offsetUnset().
+   * {@inheritdoc}
    */
   public function offsetUnset($offset) {
-    unset($this->{$offset});
+    if (in_array($offset, $this->oldRoutingProperties)) {
+      unset($this->oldRouterItem[$offset]);
+    }
+    elseif (in_array($offset, array('options', 'localized_options', 'route_parameters'))) {
+      $this->{$offset}[0]->setValue(NULL);
+    }
+    elseif ($this->getPropertyDefinition($offset)) {
+      $this->get($offset)->setValue(NULL);
+    }
+    else {
+      $this->get($offset)->setValue(NULL);
+    }
+  }
+
+  public function __set($name, $value) {
+    if (in_array($name, $this->oldRoutingProperties)) {
+      $this->oldRouterItem[$name] = $value;
+    }
+    else {
+      parent::__set($name, $value);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function preCreate(EntityStorageControllerInterface $storage_controller, array &$values) {
+    if (empty($values['menu_name'])) {
+      $values['menu_name'] = $values['bundle'] = 'tools';
+    }
   }
 
   /**
@@ -397,10 +282,10 @@ public static function preDelete(EntityStorageControllerInterface $storage_contr
 
     foreach ($entities as $entity) {
       // Children get re-attached to the item's parent.
-      if ($entity->has_children) {
-        $children = $storage_controller->loadByProperties(array('plid' => $entity->plid));
+      if ($entity->hasChildren()) {
+        $children = $storage_controller->loadByProperties(array('plid' => $entity->getParentLinkId()));
         foreach ($children as $child) {
-          $child->plid = $entity->plid;
+          $child->setParentLinkId($entity->getParentLinkId());
           $storage_controller->save($child);
         }
       }
@@ -421,8 +306,8 @@ public static function postDelete(EntityStorageControllerInterface $storage_cont
       }
 
       // Store all menu names for which we need to clear the cache.
-      if (!isset($affected_menus[$entity->menu_name])) {
-        $affected_menus[$entity->menu_name] = $entity->menu_name;
+      if (!isset($affected_menus[$entity->getMenuName()])) {
+        $affected_menus[$entity->getMenuName()] = $entity->getMenuName();
       }
     }
 
@@ -440,67 +325,70 @@ public function preSave(EntityStorageControllerInterface $storage_controller) {
 
     // This is the easiest way to handle the unique internal path '<front>',
     // since a path marked as external does not need to match a router path.
-    $this->external = (url_is_external($this->link_path) || $this->link_path == '<front>') ? 1 : 0;
+    $this->external->value = (url_is_external($this->getLinkPath()) || $this->getLinkPath() == '<front>') ? 1 : 0;
 
     // Try to find a parent link. If found, assign it and derive its menu.
     $parent_candidates = !empty($this->parentCandidates) ? $this->parentCandidates : array();
     $parent = $this->findParent($storage_controller, $parent_candidates);
     if ($parent) {
-      $this->plid = $parent->id();
-      $this->menu_name = $parent->menu_name;
+      $this->setParentLinkId($parent->id());
+      $this->setMenuName($parent->getMenuName());
     }
     // If no corresponding parent link was found, move the link to the top-level.
     else {
-      $this->plid = 0;
+      $this->setParentLinkId(0);
     }
 
     // Directly fill parents for top-level links.
-    if ($this->plid == 0) {
-      $this->p1 = $this->id();
+    if ($this->getParentLinkId() == 0) {
+      $this->p1->value = $this->id();
       for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) {
         $parent_property = "p$i";
-        $this->{$parent_property} = 0;
+        $this->{$parent_property}->value = 0;
       }
-      $this->depth = 1;
+      $this->depth->value = 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) {
+      if ($this->hasChildren() && $this->original) {
         $limit = MENU_MAX_DEPTH - $storage_controller->findChildrenRelativeDepth($this->original) - 1;
       }
       else {
         $limit = MENU_MAX_DEPTH - 1;
       }
-      if ($parent->depth > $limit) {
+      if ($parent->getDepth() > $limit) {
         return FALSE;
       }
-      $this->depth = $parent->depth + 1;
+      $this->depth->value = $parent->getDepth() + 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)) {
+    if (isset($this->original) && ($this->getParentLinkId() != $this->original->getParentLinkId() || $this->getMenuName() != $this->original->getMenuName())) {
       $storage_controller->moveChildren($this, $this->original);
     }
     // Find the router_path.
-    if (empty($this->router_path) || empty($this->original) || (isset($this->original) && $this->original->link_path != $this->link_path)) {
-      if ($this->external) {
-        $this->router_path = '';
+    if (!($this->getRouterPath()) || empty($this->original) || (isset($this->original) && $this->original->getLinkPath() != $this->getLinkPath())) {
+      if ($this->isExternal()) {
+        $this->setRouterPath('');
       }
       else {
         // Find the router path which will serve this path.
-        $this->parts = explode('/', $this->link_path, MENU_MAX_PARTS);
-        $this->router_path = _menu_find_router_path($this->link_path);
+        // @todo Where do we need 'parts'?
+        $this->parts = explode('/', $this->getLinkPath(), MENU_MAX_PARTS);
+        $this->setRouterPath(_menu_find_router_path($this->getLinkPath()));
       }
     }
+
     // Find the route_name.
-    if (!isset($this->route_name)) {
-      if ($result = static::findRouteNameParameters($this->link_path)) {
-        list($this->route_name, $this->route_parameters) = $result;
+    if ($this->getRouteName() === NULL) {
+      if ($result = static::findRouteNameParameters($this->getLinkPath())) {
+        $this->setRouteName($result[0]);
+        $this->route_parameters = $result[1];
       }
       else {
-        $this->route_name = '';
+        $this->setRouteName('');
         $this->route_parameters = array();
       }
     }
@@ -515,9 +403,9 @@ public function postSave(EntityStorageControllerInterface $storage_controller, $
     // Check the has_children status of the parent.
     $storage_controller->updateParentalStatus($this);
 
-    menu_cache_clear($this->menu_name);
-    if (isset($this->original) && $this->menu_name != $this->original->menu_name) {
-      menu_cache_clear($this->original->menu_name);
+    menu_cache_clear($this->getMenuName());
+    if (isset($this->original) && $this->getMenuName() != $this->original->getMenuName()) {
+      menu_cache_clear($this->original->getMenuName());
     }
 
     // Now clear the cache.
@@ -549,46 +437,46 @@ public static function findRouteNameParameters($link_path) {
   /**
    * {@inheritdoc}
    */
-  public function setParents(EntityInterface $parent) {
+  public function setParents(MenuLinkInterface $parent) {
     $i = 1;
-    while ($i < $this->depth) {
-      $p = 'p' . $i++;
-      $this->{$p} = $parent->{$p};
+    while ($i < $this->getDepth()) {
+      $p = 'p' . $i;
+      $this->{$p}->value = $parent->getMaterializedPathEntity($i);
+      $i++;
     }
     $p = 'p' . $i++;
     // The parent (p1 - p9) corresponding to the depth always equals the mlid.
-    $this->{$p} = $this->id();
+    $this->{$p}->value = $this->id();
     while ($i <= MENU_MAX_DEPTH) {
       $p = 'p' . $i++;
-      $this->{$p} = 0;
+      $this->{$p}->value = 0;
     }
   }
 
   /**
    * {@inheritdoc}
    */
-  public function findParent(EntityStorageControllerInterface $storage_controller, array $parent_candidates = array()) {
+  public function findParent(MenuLinkStorageControllerInterface $storage_controller, array $parent_candidates = array()) {
     $parent = FALSE;
 
+    $plid = $this->getParentLinkId();
     // This item is explicitely top-level, skip the rest of the parenting.
-    if (isset($this->plid) && empty($this->plid)) {
+    if ($plid === 0) {
       return $parent;
     }
 
     // If we have a parent link ID, try to use that.
     $candidates = array();
-    if (isset($this->plid)) {
-      $candidates[] = $this->plid;
+    if ($plid) {
+      $candidates = array($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;
+    if ($this->getDepth() > 1) {
+      for ($depth = $this->getDepth() - 1; $depth >= 1; $depth--) {
+        $candidates[] = $this->getMaterializedPathEntity($depth);
       }
     }
-
     foreach ($candidates as $mlid) {
       if (isset($parent_candidates[$mlid])) {
         $parent = $parent_candidates[$mlid];
@@ -604,12 +492,469 @@ public function findParent(EntityStorageControllerInterface $storage_controller,
     // If everything else failed, try to derive the parent from the path
     // hierarchy. This only makes sense for links derived from menu router
     // items (ie. from hook_menu()).
-    if ($this->module == 'system') {
+    if ($this->getModule() == 'system') {
       $parent = $storage_controller->getParentFromHierarchy($this);
     }
 
     return $parent;
   }
 
+  /**
+   * overrides \Drupal\Core\Entity\EntityNG::set()
+   */
+  public function set($property_name, $value, $notify = TRUE) {
+    // work-around entity_form_submit_build_entity() -> entity.set() trying to
+    // set a non-existing fields into MenuLink.
+    $definition = $this->getPropertyDefinition($property_name);
+    if (!$definition) {
+      $this->$property_name = $value;
+    }
+    else {
+      parent::set($property_name, $value, $notify);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMenuName() {
+    return $this->get('menu_name')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setMenuName($menu_name) {
+    $this->set('menu_name', $menu_name);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getParentLinkId() {
+    return $this->get('plid')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setParentLinkId($plid) {
+    $this->set('plid', $plid);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLinkPath() {
+    return $this->get('link_path')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setLinkPath($link_path) {
+    $this->set('link_path', $link_path);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getLinkTitle() {
+    return $this->get('link_title')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setLinkTitle($link_title) {
+    $this->set('link_title', $link_title);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouterPath() {
+    return $this->get('router_path')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setRouterPath($router_path) {
+    $this->set('router_path', $router_path);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getOptions() {
+    return $this->get('options')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setOptions(array $options) {
+    $this->set('options', $options);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getModule() {
+    return $this->get('module')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setModule($module) {
+    $this->set('module', $module);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setExpanded($expanded) {
+    $this->set('expanded', $expanded ? 1 : 0);
+  }
+
+  /**v
+   */
+  public function isExpanded() {
+    return (bool) $this->get('expanded')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isExternal() {
+    return (bool) $this->get('external')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setCustomized($customized) {
+    $this->set('customized', $customized);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isCustomized() {
+    return (bool) $this->get('customized')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setHidden($hidden) {
+    $this->set('hidden', $hidden ? 1 : 0);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function isHidden() {
+    return (bool)$this->get('hidden')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasChildren() {
+    return (bool)$this->get('has_children')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDepth() {
+    return $this->get('depth')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWeight() {
+    return $this->get('weight')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setWeight($weight) {
+    $this->set('weight', $weight);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMaterializedPathEntity($depth) {
+    if ($depth < 1 || $depth > 9) {
+      return NULL;
+    }
+    return $this->get('p' . $depth)->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getChangedTime() {
+    return $this->get('updated')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteName() {
+    return $this->get('route_name')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setRouteName($route_name) {
+    $this->set('route_name', $route_name);
+    return $this;
+  }
+
+  public function getAccess() {
+    return $this->get('access')->value;
+  }
+
+  public function setAccess($access) {
+    $this->set('access', $access);
+    return $this;
+  }
+  /**
+   * {@inheritdoc}
+   */
+  public function getRouteParams() {
+    return $this->get('route_parameters')->value;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setRouteParams($route_parameters) {
+    $this->set('route_parameters', $route_parameters);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function baseFieldDefinitions($entity_type) {
+    $properties['menu_name'] = array(
+      'label' => t('Menu name'),
+      'description' => t('The menu name. All links with the same menu name (such as "tools") are part of the same menu.'),
+      'type' => 'string_field',
+      'settings' => array(
+        'default_value' => 'tools',
+      ),
+    );
+    $properties['mlid'] = array(
+      'label' => t('Menu link ID'),
+      'description' => t('The menu link ID.'),
+      'type' => 'integer_field',
+      'read-only' => TRUE,
+    );
+    $properties['uuid'] = array(
+      'label' => t('UUID'),
+      'description' => t('The menu link UUID.'),
+      'type' => 'uuid_field',
+      'read-only' => TRUE,
+    );
+    $properties['plid'] = array(
+      'label' => t('Parent ID'),
+      'description' => t('The parent menu link ID.'),
+      'type' => 'entity_reference_field',
+      'settings' => array('target_type' => 'menu_link'),
+    );
+    $properties['link_path'] = array(
+      'label' => t('Link path'),
+      'description' => t('The Drupal path or external path this link points to.'),
+      'type' => 'string_field',
+    );
+    $properties['router_path'] = array(
+      'label' => t('Router path'),
+      'description' => t('For links corresponding to a Drupal path (external = 0), this connects the link to a {menu_router}.path.'),
+      'type' => 'string_field',
+    );
+    $properties['langcode'] = array(
+      'label' => t('Language code'),
+      'description' => t('The menu link language code.'),
+      'type' => 'language_field',
+    );
+    $properties['link_title'] = array(
+      'label' => t('Title'),
+      'description' => t('The text displayed for the link, which may be modified by a title callback stored in {menu_router}.'),
+      'type' => 'string_field',
+      'settings' => array(
+        'default_value' => '',
+      ),
+    );
+    $properties['options'] = array(
+      'label' => t('Options'),
+      'description' => t('A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.'),
+      'type' => 'map_field',
+    );
+    $properties['module'] = array(
+      'label' => t('Module'),
+      'description' => t('The name of the module that generated this link.'),
+      'type' => 'string_field',
+      'settings' => array(
+        'default_value' => 'menu',
+      ),
+    );
+    $properties['hidden'] = array(
+      'label' => t('Hidden'),
+      'description' => t('A flag for whether the link should be rendered in menus. (1 = a disabled menu item that may be shown on admin screens, -1 = a menu callback, 0 = a normal, visible link).'),
+      'type' => 'boolean_field',
+      'settings' => array(
+        'default_value' => 0,
+      ),
+    );
+    $properties['external'] = array(
+      'label' => t('External'),
+      'description' => t('A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).'),
+      'type' => 'boolean_field',
+      'settings' => array(
+        'default_value' => 0,
+      ),
+    );
+    $properties['has_children'] = array(
+      'label' => t('Has children'),
+      'description' => t('Flag indicating whether any links have this link as a parent (1 = children exist, 0 = no children).'),
+      'type' => 'boolean_field',
+      'settings' => array(
+        'default_value' => 0,
+      ),
+    );
+    $properties['expanded'] = array(
+      'label' => t('Expanded'),
+      'description' => t('Flag for whether this link should be rendered as expanded in menus - expanded links always have their child links displayed, instead of only when the link is in the active trail (1 = expanded, 0 = not expanded).'),
+      'type' => 'boolean_field',
+      'settings' => array(
+        'default_value' => 0,
+      ),
+    );
+    $properties['weight'] = array(
+      'label' => t('Weight'),
+      'description' => t('Link weight among links in the same menu at the same depth.'),
+      'type' => 'integer_field',
+      'settings' => array(
+        'default_value' => 0,
+      ),
+    );
+    $properties['depth'] = array(
+      'label' => t('Depth'),
+      'description' => t('The depth relative to the top level. A link with plid == 0 will have depth == 1.'),
+      'type' => 'integer_field',
+    );
+    $properties['customized'] = array(
+      'label' => t('Customized'),
+      'description' => t('A flag to indicate that the user has manually created or edited the link (1 = customized, 0 = not customized).'),
+      'type' => 'boolean_field',
+      'settings' => array(
+        'default_value' => 0,
+      ),
+    );
+    // @todo Declaring these pX properties as integer for the moment, we need to
+    // investigate if using 'entity_reference_field' cripples performance.
+    $properties['p1'] = array(
+      'label' => t('Parent 1'),
+      'description' => t('The first mlid in the materialized path.'),
+      'type' => 'integer_field',
+    );
+    $properties['p2'] = array(
+      'label' => t('Parent 2'),
+      'description' => t('The second mlid in the materialized path.'),
+      'type' => 'integer_field',
+    );
+    $properties['p3'] = array(
+      'label' => t('Parent 3'),
+      'description' => t('The third mlid in the materialized path.'),
+      'type' => 'integer_field',
+    );
+    $properties['p4'] = array(
+      'label' => t('Parent 4'),
+      'description' => t('The fourth mlid in the materialized path.'),
+      'type' => 'integer_field',
+    );
+    $properties['p5'] = array(
+      'label' => t('Parent 5'),
+      'description' => t('The fifth mlid in the materialized path.'),
+      'type' => 'integer_field',
+    );
+    $properties['p6'] = array(
+      'label' => t('Parent 6'),
+      'description' => t('The sixth mlid in the materialized path.'),
+      'type' => 'integer_field',
+    );
+    $properties['p7'] = array(
+      'label' => t('Parent 7'),
+      'description' => t('The seventh mlid in the materialized path.'),
+      'type' => 'integer_field',
+    );
+    $properties['p8'] = array(
+      'label' => t('Parent 8'),
+      'description' => t('The eighth mlid in the materialized path.'),
+      'type' => 'integer_field',
+    );
+    $properties['p9'] = array(
+      'label' => t('Parent 9'),
+      'description' => t('The ninth mlid in the materialized path.'),
+      'type' => 'integer_field',
+    );
+    $properties['updated'] = array(
+      'label' => t('Updated'),
+      'description' => t('Flag that indicates that this link was generated during the update from Drupal 5.'),
+      'type' => 'boolean_field',
+    );
+    $properties['route_name'] = array(
+      'label' => t('Route name'),
+      'description' => t('The machine name of a defined Symfony Route this menu item represents.'),
+      'type' => 'string_field',
+    );
+    $properties['route_parameters'] = array(
+      'label' => t('Route parameters'),
+      'description' => t('A serialized array of route parameters of this menu link.'),
+      'type' => 'map_field',
+    );
+
+    // @todo Most of these should probably go away.
+    $properties['access'] = array(
+      'label' => t('(old router) Access'),
+      'description' => t(''),
+      'type' => 'boolean_field',
+      'computed' => TRUE,
+    );
+    $properties['in_active_trail'] = array(
+      'label' => t('In active trail'),
+      'description' => t(''),
+      'type' => 'boolean_field',
+      'computed' => TRUE,
+    );
+    $properties['localized_options'] = array(
+      'label' => t('Localized options'),
+      'description' => t(''),
+      'type' => 'map_field',
+      'computed' => TRUE,
+    );
+    return $properties;
+  }
 
 }
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkAccessController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkAccessController.php
index a1c2c69..e710775 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkAccessController.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkAccessController.php
@@ -27,11 +27,11 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A
       switch ($operation) {
         case 'reset':
           // Reset allowed for items defined via hook_menu() and customized.
-          return $entity->module == 'system' && $entity->customized;
+          return $entity->module->value == 'system' && $entity->customized->value;
 
         case 'delete':
           // Only items created by the menu module can be deleted.
-          return $entity->module == 'menu' || $entity->updated == 1;
+          return $entity->module->value == 'menu' || $entity->updated->value == 1;
 
       }
     }
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php
index 4f437ec..ab09332 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php
@@ -7,7 +7,8 @@
 
 namespace Drupal\menu_link;
 
-use Drupal\Core\Entity\EntityFormController;
+use Drupal\Core\Entity\EntityControllerInterface;
+use Drupal\Core\Entity\EntityFormControllerNG;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Path\AliasManagerInterface;
 use Drupal\Core\Routing\UrlGenerator;
@@ -17,7 +18,7 @@
 /**
  * Form controller for the node edit forms.
  */
-class MenuLinkFormController extends EntityFormController {
+class MenuLinkFormController extends EntityFormControllerNG {
 
   /**
    * The menu link storage controller.
@@ -79,26 +80,26 @@ public function form(array $form, array &$form_state) {
     $form['link_title'] = array(
       '#type' => 'textfield',
       '#title' => t('Menu link title'),
-      '#default_value' => $menu_link->link_title,
+      '#default_value' => $menu_link->link_title->value,
       '#description' => t('The text to be used for this link in the menu.'),
       '#required' => TRUE,
     );
     foreach (array('link_path', 'mlid', 'module', 'has_children', 'options') as $key) {
-      $form[$key] = array('#type' => 'value', '#value' => $menu_link->{$key});
+      $form[$key] = array('#type' => 'value', '#value' => $menu_link->{$key}->getValue());
     }
     // Any item created or edited via this interface is considered "customized".
     $form['customized'] = array('#type' => 'value', '#value' => 1);
 
     // We are not using url() when constructing this path because it would add
     // $base_path.
-    $path = $menu_link->link_path;
-    if (isset($menu_link->options['query'])) {
-      $path .= '?' . $this->urlGenerator->httpBuildQuery($menu_link->options['query']);
+    $path = $menu_link->link_path->value;
+    if (isset($menu_link->options->query)) {
+      $path .= '?' . $this->urlGenerator->httpBuildQuery($menu_link->options->query);
     }
-    if (isset($menu_link->options['fragment'])) {
-      $path .= '#' . $menu_link->options['fragment'];
+    if (isset($menu_link->options->fragment)) {
+      $path .= '#' . $menu_link->options->fragment;
     }
-    if ($menu_link->module == 'menu') {
+    if ($menu_link->module->value == 'menu') {
       $form['link_path'] = array(
         '#type' => 'textfield',
         '#title' => t('Path'),
@@ -112,33 +113,33 @@ public function form(array $form, array &$form_state) {
       $form['_path'] = array(
         '#type' => 'item',
         '#title' => t('Path'),
-        '#description' => l($menu_link->link_title, $menu_link->href, $menu_link->options),
+        '#description' => l($menu_link->link_title->value, $menu_link->href, $menu_link['options']),
       );
     }
 
     $form['description'] = array(
       '#type' => 'textarea',
       '#title' => t('Description'),
-      '#default_value' => isset($menu_link->options['attributes']['title']) ? $menu_link->options['attributes']['title'] : '',
+      '#default_value' => isset($menu_link->options->attributes['title']) ? $menu_link->options->attributes['title'] : '',
       '#rows' => 1,
       '#description' => t('Shown when hovering over the menu link.'),
     );
     $form['enabled'] = array(
       '#type' => 'checkbox',
       '#title' => t('Enabled'),
-      '#default_value' => !$menu_link->hidden,
+      '#default_value' => !$menu_link->hidden->value,
       '#description' => t('Menu links that are not enabled will not be listed in any menu.'),
     );
     $form['expanded'] = array(
       '#type' => 'checkbox',
       '#title' => t('Show as expanded'),
-      '#default_value' => $menu_link->expanded,
+      '#default_value' => $menu_link->expanded->value,
       '#description' => t('If selected and this menu link has children, the menu will always appear expanded.'),
     );
 
     // Generate a list of possible parents (not including this link or descendants).
     $options = menu_parent_options(menu_get_menus(), $menu_link);
-    $default = $menu_link->menu_name . ':' . $menu_link->plid;
+    $default = $menu_link->menu_name->value . ':' . $menu_link->plid->target_id;
     if (!isset($options[$default])) {
       $default = 'tools:0';
     }
@@ -152,13 +153,13 @@ public function form(array $form, array &$form_state) {
     );
 
     // Get number of items in menu so the weight selector is sized appropriately.
-    $delta = $this->menuLinkStorageController->countMenuLinks($menu_link->menu_name);
+    $delta = $this->menuLinkStorageController->countMenuLinks($menu_link->menu_name->value);
     $form['weight'] = array(
       '#type' => 'weight',
       '#title' => t('Weight'),
       // Old hardcoded value.
       '#delta' => max($delta, 50),
-      '#default_value' => $menu_link->weight,
+      '#default_value' => $menu_link->weight->value,
       '#description' => t('Optional. In the menu, the heavier links will sink and the lighter links will be positioned nearer the top.'),
     );
 
@@ -168,13 +169,13 @@ public function form(array $form, array &$form_state) {
     // be configured individually.
     if ($this->moduleHandler->moduleExists('language')) {
       $language_configuration = language_get_default_configuration('menu_link', $menu_link->bundle());
-      $default_langcode = ($menu_link->isNew() ? $language_configuration['langcode'] : $menu_link->langcode);
+      $default_langcode = ($menu_link->isNew() ? $language_configuration['langcode'] : $menu_link->langcode->value);
       $language_show = $language_configuration['language_show'];
     }
     // Without Language module menu links inherit the menu language and no
     // language selector is shown.
     else {
-      $default_langcode = ($menu_link->isNew() ? entity_load('menu', $menu_link->menu_name)->langcode : $menu_link->langcode);
+      $default_langcode = ($menu_link->isNew() ? entity_load('menu', $menu_link->menu_name->value)->langcode : $menu_link->langcode->value);
       $language_show = FALSE;
     }
 
@@ -201,74 +202,65 @@ protected function actions(array $form, array &$form_state) {
   }
 
   /**
-   * Overrides EntityFormController::validate().
+   * {@inheritdoc}
    */
   public function validate(array $form, array &$form_state) {
     $menu_link = $this->buildEntity($form, $form_state);
 
-    $normal_path = $this->pathAliasManager->getSystemPath($menu_link->link_path);
-    if ($menu_link->link_path != $normal_path) {
-      drupal_set_message(t('The menu system stores system paths only, but will use the URL alias for display. %link_path has been stored as %normal_path', array('%link_path' => $menu_link->link_path, '%normal_path' => $normal_path)));
-      $menu_link->link_path = $normal_path;
+    $normal_path = $this->pathAliasManager->getSystemPath($menu_link->getLinkPath());
+    if ($menu_link->getLinkPath() != $normal_path) {
+      drupal_set_message(t('The menu system stores system paths only, but will use the URL alias for display. %link_path has been stored as %normal_path', array('%link_path' => $menu_link->getLinkPath(), '%normal_path' => $normal_path)));
       $form_state['values']['link_path'] = $normal_path;
+      $menu_link->setLinkPath($normal_path);
     }
-    if (!url_is_external($menu_link->link_path)) {
-      $parsed_link = parse_url($menu_link->link_path);
+    if (!url_is_external($menu_link->getLinkPath())) {
+      $parsed_link = parse_url($menu_link->getLinkPath());
       if (isset($parsed_link['query'])) {
-        $menu_link->options['query'] = array();
-        parse_str($parsed_link['query'], $menu_link->options['query']);
+        $query = array();
+        parse_str($parsed_link['query'], $query);
+        $menu_link->options->query = $query;
       }
       else {
-        // Use unset() rather than setting to empty string
-        // to avoid redundant serialized data being stored.
-        unset($menu_link->options['query']);
+        $menu_link->options->query = '';
       }
       if (isset($parsed_link['fragment'])) {
-        $menu_link->options['fragment'] = $parsed_link['fragment'];
+        $menu_link->options->fragment = $parsed_link['fragment'];
       }
       else {
-        unset($menu_link->options['fragment']);
+        $menu_link->options->fragment = '';
       }
-      if (isset($parsed_link['path']) && $menu_link->link_path != $parsed_link['path']) {
-        $menu_link->link_path = $parsed_link['path'];
+      if (isset($parsed_link['path']) && $menu_link->getLinkPath() != $parsed_link['path']) {
+        $menu_link->setLinkPath($parsed_link['path']);
       }
     }
-    if (!trim($menu_link->link_path) || !drupal_valid_path($menu_link->link_path, TRUE)) {
-      form_set_error('link_path', t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $menu_link->link_path)));
+    if (!trim($menu_link->getLinkPath()) || !drupal_valid_path($menu_link->getLinkPath(), TRUE)) {
+      form_set_error('link_path', t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $menu_link->getLinkPath())));
     }
 
     parent::validate($form, $form_state);
   }
 
-  /**
-   * Overrides EntityFormController::submit().
-   */
-  public function submit(array $form, array &$form_state) {
-    // Build the menu link object from the submitted values.
-    $menu_link = parent::submit($form, $form_state);
-
+  public function buildEntity(array $form, array &$form_state) {
+    $entity = parent::buildEntity($form, $form_state);
     // The value of "hidden" is the opposite of the value supplied by the
     // "enabled" checkbox.
-    $menu_link->hidden = (int) !$menu_link->enabled;
-    unset($menu_link->enabled);
-
-    $menu_link->options['attributes']['title'] = $menu_link->description;
-    list($menu_link->menu_name, $menu_link->plid) = explode(':', $menu_link->parent);
-
-    return $menu_link;
+    $entity->hidden->value = (int) !$form_state['values']['enabled'];
+    $substrs = explode(':', $form_state['values']['parent']);
+    $entity->setMenuName($substrs[0]);
+    $entity->plid = intval($substrs[1]);
+    $attributes = $entity->options->attributes;
+    $attributes['title'] = $form_state['values']['description'];
+    $entity->options->attributes = $attributes;
+    return $entity;
   }
 
   /**
    * Overrides EntityFormController::save().
    */
   public function save(array $form, array &$form_state) {
-    $menu_link = $this->entity;
-
-    $saved = $menu_link->save();
-
-    if ($saved) {
+    if ($this->entity->save()) {
       drupal_set_message(t('The menu link has been saved.'));
-      $form_state['redirect'] = 'admin/structure/menu/manage/' . $menu_link->menu_name;
+      $form_state['redirect'] = 'admin/structure/menu/manage/' . $this->entity->getMenuName();
     }
     else {
       drupal_set_message(t('There was an error saving the menu link.'), 'error');
@@ -280,7 +272,6 @@ public function save(array $form, array &$form_state) {
    * Overrides EntityFormController::delete().
    */
   public function delete(array $form, array &$form_state) {
-    $menu_link = $this->entity;
-    $form_state['redirect'] = 'admin/structure/menu/item/' . $menu_link->id() . '/delete';
+    $form_state['redirect'] = 'admin/structure/menu/item/' . $this->entity->id() . '/delete';
   }
 }
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkInterface.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkInterface.php
index b8e639e..3b307ba 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkInterface.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkInterface.php
@@ -10,7 +10,6 @@
 use Symfony\Component\Routing\Route;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityStorageControllerInterface;
 
 /**
  * Provides an interface defining a menu link entity.
@@ -70,10 +69,10 @@ public static function findRouteNameParameters($link_path);
   /**
    * Sets the p1 through p9 properties for a menu link entity being saved.
    *
-   * @param \Drupal\Core\Entity\EntityInterface $parent
+   * @param \Drupal\menu_link\MenuLinkInterface $parent
    *   A menu link entity.
    */
-  public function setParents(EntityInterface $parent);
+  public function setParents(MenuLinkInterface $parent);
 
   /**
    * Finds a possible parent for a given menu link entity.
@@ -86,7 +85,7 @@ public function setParents(EntityInterface $parent);
    *  - else, for system menu links (derived from hook_menu()), reparent
    *    based on the path hierarchy.
    *
-   * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage_controller
+   * @param \Drupal\menu_link\MenuLinkStorageControllerInterface $storage_controller
    *   Storage controller object.
    * @param array $parent_candidates
    *   An array of menu link entities keyed by mlid.
@@ -95,5 +94,301 @@ public function setParents(EntityInterface $parent);
    *   A menu link entity structure of the possible parent or FALSE if no valid
    *   parent has been found.
    */
-  public function findParent(EntityStorageControllerInterface $storage_controller, array $parent_candidates = array());
+  public function findParent(MenuLinkStorageControllerInterface $storage_controller, array $parent_candidates = array());
+
+  /**
+   * Returns the menu name of this menu link.
+   *
+   * @return string
+   *   The name of the menu.
+   */
+  public function getMenuName();
+
+  /**
+   * Sets the menu name of this menu link.
+   *
+   * @param string $menu_name
+   *   The name of the menu.
+   *
+   * @return \Drupal\menu_link\MenuLinkInterface
+   *   The called menu link entity.
+   */
+  public function setMenuName($menu_name);
+
+  /**
+   * Returns the parent menu link ID of this menu link.
+   *
+   * @return int
+   *   The parent link ID of the menu.
+   */
+  public function getParentLinkId();
+
+  /**
+   * Sets the parent menu link id of this menu link.
+   *
+   * @param int $plid
+   *   The parent link ID of the menu.
+   *
+   * @return \Drupal\menu_link\MenuLinkInterface
+   *   The called menu link entity.
+   */
+  public function setParentLinkId($plid);
+
+  /**
+   * Returns the Drupal path or external path this link points to.
+   *
+   * @return string
+   *   The Drupal path or external path this link points to.
+   */
+  public function getLinkPath();
+
+  /**
+   * Sets the Drupal path or external path this link points to.
+   *
+   * @param string $link_path
+   *   The the Drupal path or external path this link points to.
+   *
+   * @return \Drupal\menu_link\MenuLinkInterface
+   *   The called menu link entity.
+   */
+  public function setLinkPath($link_path);
+
+  /**
+   * Returns the title of this menu link.
+   *
+   * @return string
+   *   The title of this menu link.
+   */
+  public function getLinkTitle();
+
+  /**
+   * Sets the title of this menu link.
+   *
+   * @param string $link_title
+   *   The title of this menu link.
+   *
+   * @return \Drupal\menu_link\MenuLinkInterface
+   *   The called menu link entity.
+   */
+  public function setLinkTitle($link_title);
+
+  /**
+   * Returns the menu router path for this link.
+   *
+   * @return string
+   *   The menu router path for this link.
+   */
+  public function getRouterPath();
+
+  /**
+   * Sets the Drupal path or external path this link points to.
+   *
+   * @param string $router_path
+   *   The menu router path for this link.
+   *
+   * @return \Drupal\menu_link\MenuLinkInterface
+   *   The called menu link entity.
+   */
+  public function setRouterPath($router_path);
+
+  /**
+   * Returns the menu link options.
+   *
+   * @return array
+   *   The menu link options, to be passed to to l() or url().
+   */
+  public function getOptions();
+
+  /**
+   * Sets the menu link options.
+   *
+   * @todo: Add a method to add options?
+   *
+   * @param array $options
+   *   The menu link options.
+   *
+   * @return \Drupal\menu_link\MenuLinkInterface
+   *   The called menu link entity.
+   */
+  public function setOptions(array $options);
+
+  /**
+   * Returns the name of the module that generated this link.
+   *
+   * @return string
+   *   The name of the module that generated this link.
+   */
+  public function getModule();
+
+  /**
+   * Sets the name of the module that generated this link.
+   *
+   * @param string $module
+   *   The name of the module that generated this link.
+   *
+   * @return \Drupal\menu_link\MenuLinkInterface
+   *   The called menu link entity.
+   */
+  public function setModule($module);
+
+  /**
+   * Sets whether the menu link is expanded.
+   *
+   * @param bool $expanded
+   *   TRUE if the menu link is expanded, FALSE if not.
+   *
+   * @return \Drupal\menu_link\MenuLinkInterface
+   *   The called menu link entity.
+   */
+  public function setExpanded($expanded);
+
+  /**
+   * Returns whether the menu link is expanded
+   *
+   * @return string
+   *   TRUE if the menu link is expanded, FALSE if not.
+   */
+  public function isExpanded();
+
+  /**
+   * Returns whether the menu link is external.
+   *
+   * @return string
+   *   TRUE if the menu link is external, FALSE if not.
+   */
+  public function isExternal();
+
+  /**
+   * Sets if the menu link is customized.
+   *
+   * @param bool $customized
+   *   TRUE if the menu link is customized, FALSE if not.
+   *
+   * @return \Drupal\menu_link\MenuLinkInterface
+   *   The called menu link entity.
+   */
+  public function setCustomized($customized);
+
+  /**
+   * Returns whether the menu link is customized.
+   *
+   * @return string
+   *   TRUE if the menu link is customized, FALSE if not.
+   */
+  public function isCustomized();
+
+  /**
+   * Sets whether the menu link is hidden.
+   *
+   * @param bool $hidden
+   *   TRUE if the menu link is hidden, FALSE if not.
+   *
+   * @return \Drupal\menu_link\MenuLinkInterface
+   *   The called menu link entity.
+   */
+  public function setHidden($hidden);
+
+  /**
+   * Returns whether the menu link is hidden.
+   *
+   * @return string
+   *   TRUE if the menu link is hidden, FALSE if not.
+   */
+  public function isHidden();
+
+  /**
+   * Returns whether the menu link is expanded
+   *
+   * @return string
+   *   TRUE if the menu link has children, FALSE if not.
+   */
+  public function hasChildren();
+
+  /**
+   * Returns the depth relative to the top level of this link.
+   *
+   * A link with parent ID 0 will have depth of 1.
+   *
+   * @return int
+   *   The menu link depth.
+   */
+  public function getDepth();
+
+  /**
+   * Returns the weight among menu links with the same depth.
+   *
+   * @return int
+   *   The menu link weight.
+   */
+  public function getWeight();
+
+  /**
+   * Sets the weight among menu links with the same depth.
+   *
+   *·@param int $weight
+   *   The menu link weight.
+   *
+   * @return \Drupal\menu_link\MenuLinkInterface
+   *   The called menu link entity.
+   */
+  public function setWeight($weight);
+
+  /**
+   * Returns the nth depth entity ID of the materialized path.
+   *
+   * @param int $depth
+   *   Indicates the depth of entity ID of the materialized path that should be
+   *   returned, between 1 and 9.
+   *
+   * @return int
+   *   Nth Entity ID of the materialized path.
+   */
+  public function getMaterializedPathEntity($depth);
+
+  /**
+   * Returns the menu link modification timestamp.
+   *
+   * @return int
+   *   Menu link modification timestamp.
+   */
+  public function getChangedTime();
+
+  /**
+   * Returns the route name associated with this menu link, if any.
+   *
+   * @return string|null
+   *   The route name of this menu link.
+   */
+  public function getRouteName();
+
+  /**
+   * Sets the route name associated with this menu link.
+   *
+   * @param string|null $route_name
+   *   The route name associated with this menu link.
+   *
+   * @return \Drupal\menu_link\MenuLinkInterface
+   *   The called menu link entity.
+   */
+  public function setRouteName($route_name);
+
+  /**
+   * Returns the route parameters associated with this menu link, if any.
+   *
+   * @return array
+   *   The route parameters of this menu link.
+   */
+  public function getRouteParams();
+  
+  /**
+   * Sets the route parameters associated with this menu link.
+   *
+   * @param array $route_parameters
+   *   The route parameters associated with this menu link.
+   *
+   * @return \Drupal\menu_link\MenuLinkInterface
+   *   The called menu link entity.
+  */
+  public function setRouteParams($route_parameters);
+  
 }
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
index 1fa6140..7f691fb 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
@@ -8,7 +8,7 @@
 namespace Drupal\menu_link;
 
 use Drupal\Component\Uuid\UuidInterface;
-use Drupal\Core\Entity\DatabaseStorageController;
+use Drupal\Core\Entity\DatabaseStorageControllerNG;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Database\Connection;
@@ -17,12 +17,9 @@
 use Symfony\Cmf\Component\Routing\RouteProviderInterface;
 
 /**
- * Controller class for menu links.
- *
- * This extends the Drupal\entity\DatabaseStorageController class, adding
- * required special handling for menu_link entities.
+ * Defines the storage controller class for menu links.
  */
-class MenuLinkStorageController extends DatabaseStorageController implements MenuLinkStorageControllerInterface {
+class MenuLinkStorageController extends DatabaseStorageControllerNG implements MenuLinkStorageControllerInterface {
 
   /**
    * Indicates whether the delete operation should re-parent children items.
@@ -67,7 +64,7 @@ public function __construct($entity_type, array $entity_info, Connection $databa
     $this->routeProvider = $route_provider;
 
     if (empty(static::$routerItemFields)) {
-      static::$routerItemFields = array_diff(drupal_schema_fields_sql('menu_router'), array('weight'));
+      static::$routerItemFields = array_diff(drupal_schema_fields_sql('menu_router'), array('weight', 'route_name'));
     }
   }
 
@@ -80,6 +77,7 @@ public function create(array $values) {
     if (!isset($values['bundle']) && isset($values['menu_name'])) {
       $values['bundle'] = $values['menu_name'];
     }
+
     return parent::create($values);
   }
 
@@ -98,7 +96,7 @@ public static function createInstance(ContainerInterface $container, $entity_typ
   }
 
   /**
-   * Overrides DatabaseStorageController::buildQuery().
+   * {@inheritdoc}
    */
   protected function buildQuery($ids, $revision_id = FALSE) {
     $query = parent::buildQuery($ids, $revision_id);
@@ -109,51 +107,73 @@ protected function buildQuery($ids, $revision_id = FALSE) {
   }
 
   /**
-   * Overrides DatabaseStorageController::attachLoad().
-   *
-   * @todo Don't call parent::attachLoad() at all because we want to be able to
-   * control the entity load hooks.
+   * {@inheritdoc}
    */
-  protected function attachLoad(&$menu_links, $load_revision = FALSE) {
+  protected function attachLoad(&$queried_entities, $load_revision = FALSE) {
     $routes = array();
 
-    foreach ($menu_links as &$menu_link) {
-      $menu_link->options = unserialize($menu_link->options);
-      $menu_link->route_parameters = unserialize($menu_link->route_parameters);
+    foreach ($queried_entities as &$record) {
+      $record->options = unserialize($record->options);
+      $record->route_parameters = isset($record->route_parameters) ? unserialize($record->route_parameters) : array();
 
       // Use the weight property from the menu link.
-      $menu_link->router_item['weight'] = $menu_link->weight;
+      $record->router_item['weight'] = $record->weight;
 
       // By default use the menu_name as type.
-      $menu_link->bundle = $menu_link->menu_name;
+      $record->bundle = $record->menu_name;
 
       // For all links that have an associated route, load the route object now
       // and save it on the object. That way we avoid a select N+1 problem later.
-      if ($menu_link->route_name) {
-        $routes[$menu_link->id()] = $menu_link->route_name;
+      if ($record->route_name) {
+        $routes[$record->{$this->idKey}] = $record->route_name;
       }
     }
 
+    parent::attachLoad($queried_entities, $load_revision);
+
     // Now mass-load any routes needed and associate them.
     if ($routes) {
       $route_objects = $this->routeProvider->getRoutesByNames($routes);
       foreach ($routes as $entity_id => $route) {
         // Not all stored routes will be valid on load.
         if (isset($route_objects[$route])) {
-          $menu_links[$entity_id]->setRouteObject($route_objects[$route]);
+          $queried_entities[$entity_id]->setRouteObject($route_objects[$route]);
         }
       }
     }
+  }
 
-    parent::attachLoad($menu_links, $load_revision);
+  /**
+   * {@inheritdoc}
+   */
+  protected function mapFromStorageRecords(array $records, $load_revision = FALSE) {
+    $entities = parent::mapFromStorageRecords($records, $load_revision);
+
+    foreach ($entities as &$entity) {
+      foreach (static::$routerItemFields as $router_field) {
+        $entity->offsetSet($router_field, $records[$entity->id()]->{$router_field});
+      }
+    }
+
+    return $entities;
   }
 
   /**
+   * Overrides DatabaseStorageControllerNG::mapToStorageRecord().
+   */
+  protected function mapToStorageRecord(EntityInterface $entity) {
+    $record = parent::mapToStorageRecord($entity);
+    foreach (array('options', 'route_parameters') as $property) {
+      $record->$property = $entity->$property->getValue();
+    }
+    
+    return $record;
+  }
+  
+  /**
    * Overrides DatabaseStorageController::save().
    */
   public function save(EntityInterface $entity) {
-    $entity_class = $this->entityInfo['class'];
-
     // We return SAVED_UPDATED by default because the logic below might not
     // update the entity if its values haven't changed, so returning FALSE
     // would be confusing in that situation.
@@ -161,6 +181,9 @@ public function save(EntityInterface $entity) {
 
     $transaction = $this->database->startTransaction();
     try {
+      // Sync the changes made in the fields array to the internal values array.
+      $entity->updateOriginalValues();
+
       // Load the stored entity, if any.
       if (!$entity->isNew() && !isset($entity->original)) {
         $entity->original = entity_load_unchanged($this->entityType, $entity->id());
@@ -183,20 +206,46 @@ public function save(EntityInterface $entity) {
       // array_intersect_key() with the $entity as the first parameter because
       // $entity may have additional keys left over from building a router entry.
       // The intersect removes the extra keys, allowing a meaningful comparison.
-      if ($entity->isNew() || (array_intersect_key(get_object_vars($entity), get_object_vars($entity->original)) != get_object_vars($entity->original))) {
-        $return = drupal_write_record($this->entityInfo['base_table'], $entity, $this->idKey);
+      if ($entity->isNew() || (array_intersect_key($entity->getPropertyValues(), $entity->original->getPropertyValues()) != $entity->original->getPropertyValues())) {
+        // Create the storage record to be saved.
+        $record = $this->mapToStorageRecord($entity);
+        $return = drupal_write_record($this->entityInfo['base_table'], $record, $this->idKey);
 
         if ($return) {
           if (!$entity->isNew()) {
-            $this->resetCache(array($entity->{$this->idKey}));
+            // @todo, should a different value be returned when saving an entity
+            // with $isDefaultRevision = FALSE?
+            if (!$entity->isDefaultRevision()) {
+              $return = FALSE;
+            }
+
+            if ($this->revisionKey) {
+              $record->{$this->revisionKey} = $this->saveRevision($entity);
+            }
+            if ($this->dataTable) {
+              $this->savePropertyData($entity);
+            }
+            $this->resetCache(array($entity->id()));
             $entity->postSave($this, TRUE);
             $this->invokeFieldMethod('update', $entity);
             $this->saveFieldItems($entity, TRUE);
             $this->invokeHook('update', $entity);
+            if ($this->dataTable) {
+              $this->invokeTranslationHooks($entity);
+            }
           }
           else {
             $return = SAVED_NEW;
-            $this->resetCache();
+            if ($this->revisionKey) {
+              $record->{$this->revisionKey} = $this->saveRevision($entity);
+            }
+            $entity->{$this->idKey}->value = $record->{$this->idKey};
+            if ($this->dataTable) {
+              $this->savePropertyData($entity);
+            }
+
+            // Reset general caches, but keep caches specific to certain entities.
+            $this->resetCache(array());
 
             $entity->enforceIsNew(FALSE);
             $entity->postSave($this, FALSE);
@@ -238,6 +287,10 @@ public function getPreventReparenting() {
    * {@inheritdoc}
    */
   public function loadUpdatedCustomized(array $router_paths) {
+    $menu_links = array();
+
+    // @todo This doesn't really make sense anymore with EntityNG.. and EFQ got
+    // OR condition support in the meantime, so convert this query.
     $query = parent::buildQuery(NULL);
     $query
       ->condition(db_or()
@@ -248,16 +301,12 @@ public function loadUpdatedCustomized(array $router_paths) {
         ->condition('customized', 1)
         )
       );
-    $query_result = $query->execute();
 
-    if (!empty($this->entityInfo['class'])) {
-      // We provide the necessary arguments for PDO to create objects of the
-      // specified entity class.
-      // @see Drupal\Core\Entity\EntityInterface::__construct()
-      $query_result->setFetchMode(\PDO::FETCH_CLASS, $this->entityInfo['class'], array(array(), $this->entityType));
+    if ($ids = $query->execute()->fetchCol(1)) {
+      $menu_links = $this->load($ids);
     }
 
-    return $query_result->fetchAllAssoc($this->idKey);
+    return $menu_links;
   }
 
   /**
@@ -281,13 +330,13 @@ public function loadModuleAdminTasks() {
    */
   public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE) {
     // If plid == 0, there is nothing to update.
-    if ($entity->plid) {
+    if ($entity->plid->target_id) {
       // Check if at least one visible child exists in the table.
       $query = \Drupal::entityQuery($this->entityType);
       $query
-        ->condition('menu_name', $entity->menu_name)
+        ->condition('menu_name', $entity->menu_name->value)
         ->condition('hidden', 0)
-        ->condition('plid', $entity->plid)
+        ->condition('plid', $entity->plid->target_id)
         ->count();
 
       if ($exclude) {
@@ -297,7 +346,7 @@ public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE)
       $parent_has_children = ((bool) $query->execute()) ? 1 : 0;
       $this->database->update('menu_links')
         ->fields(array('has_children' => $parent_has_children))
-        ->condition('mlid', $entity->plid)
+        ->condition('mlid', $entity->plid->target_id)
         ->execute();
     }
   }
@@ -310,20 +359,20 @@ public function findChildrenRelativeDepth(EntityInterface $entity) {
     // 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->condition('menu_name', $entity->menu_name->value);
     $query->orderBy('depth', 'DESC');
     $query->range(0, 1);
 
     $i = 1;
     $p = 'p1';
-    while ($i <= MENU_MAX_DEPTH && $entity->{$p}) {
-      $query->condition($p, $entity->{$p});
+    while ($i <= MENU_MAX_DEPTH && $entity->{$p}->value) {
+      $query->condition($p, $entity->{$p}->value);
       $p = 'p' . ++$i;
     }
 
     $max_depth = $query->execute()->fetchField();
 
-    return ($max_depth > $entity->depth) ? $max_depth - $entity->depth : 0;
+    return ($max_depth > $entity->depth->value) ? $max_depth - $entity->depth->value : 0;
   }
 
   /**
@@ -332,14 +381,14 @@ public function findChildrenRelativeDepth(EntityInterface $entity) {
   public function moveChildren(EntityInterface $entity) {
     $query = $this->database->update($this->entityInfo['base_table']);
 
-    $query->fields(array('menu_name' => $entity->menu_name));
+    $query->fields(array('menu_name' => $entity->menu_name->value));
 
     $p = 'p1';
     $expressions = array();
-    for ($i = 1; $i <= $entity->depth; $p = 'p' . ++$i) {
-      $expressions[] = array($p, ":p_$i", array(":p_$i" => $entity->{$p}));
+    for ($i = 1; $i <= $entity->depth->value; $p = 'p' . ++$i) {
+      $expressions[] = array($p, ":p_$i", array(":p_$i" => $entity->{$p}->value));
     }
-    $j = $entity->original->depth + 1;
+    $j = $entity->original->depth->value + 1;
     while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) {
       $expressions[] = array('p' . $i++, 'p' . $j++, array());
     }
@@ -347,7 +396,7 @@ public function moveChildren(EntityInterface $entity) {
       $expressions[] = array('p' . $i++, 0, array());
     }
 
-    $shift = $entity->depth - $entity->original->depth;
+    $shift = $entity->depth->value - $entity->original->depth->value;
     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
@@ -360,10 +409,10 @@ public function moveChildren(EntityInterface $entity) {
     }
 
     $query->expression('depth', 'depth + :depth', array(':depth' => $shift));
-    $query->condition('menu_name', $entity->original->menu_name);
+    $query->condition('menu_name', $entity->original->menu_name->value);
     $p = 'p1';
-    for ($i = 1; $i <= MENU_MAX_DEPTH && $entity->original->{$p}; $p = 'p' . ++$i) {
-      $query->condition($p, $entity->original->{$p});
+    for ($i = 1; $i <= MENU_MAX_DEPTH && $entity->original->{$p}->value; $p = 'p' . ++$i) {
+      $query->condition($p, $entity->original->{$p}->value);
     }
 
     $query->execute();
@@ -387,7 +436,7 @@ public function countMenuLinks($menu_name) {
    * {@inheritdoc}
    */
   public function getParentFromHierarchy(EntityInterface $entity) {
-    $parent_path = $entity->link_path;
+    $parent_path = $entity->link_path->value;
     do {
       $parent = FALSE;
       $parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
@@ -398,7 +447,7 @@ public function getParentFromHierarchy(EntityInterface $entity) {
         ->condition('module', 'system')
         // We always respect the link's 'menu_name'; inheritance for router
         // items is ensured in _menu_router_build().
-        ->condition('menu_name', $entity->menu_name)
+        ->condition('menu_name', $entity->menu_name->value)
         ->condition('link_path', $parent_path);
 
       $result = $query->execute();
diff --git a/core/modules/menu_link/menu_link.api.php b/core/modules/menu_link/menu_link.api.php
index bfb8a2e..f5ec959 100644
--- a/core/modules/menu_link/menu_link.api.php
+++ b/core/modules/menu_link/menu_link.api.php
@@ -32,7 +32,7 @@
  */
 function hook_menu_link_load($menu_links) {
   foreach ($menu_links as $menu_link) {
-    if ($menu_link->href == 'devel/cache/clear') {
+    if ($menu_link->href->value == 'devel/cache/clear') {
       $menu_link->options['query'] = drupal_get_destination();
     }
   }
@@ -49,17 +49,17 @@ function hook_menu_link_load($menu_links) {
  */
 function hook_menu_link_presave(\Drupal\menu_link\Entity\MenuLink $menu_link) {
   // Make all new admin links hidden (a.k.a disabled).
-  if (strpos($menu_link->link_path, 'admin') === 0 && $menu_link->isNew()) {
+  if (strpos($menu_link->link_path->value, 'admin') === 0 && $menu_link->isNew()) {
     $menu_link->hidden = 1;
   }
   // Flag a link to be altered by hook_menu_link_load().
-  if ($menu_link->link_path == 'devel/cache/clear') {
+  if ($menu_link->link_path->value == 'devel/cache/clear') {
     $menu_link->options['alter'] = TRUE;
   }
   // Flag a menu link to be altered by hook_menu_link_load(), but only if it is
   // derived from a menu router item; i.e., do not alter a custom menu link
   // pointing to the same path that has been created by a user.
-  if ($menu_link->link_path == 'user' && $menu_link->module == 'system') {
+  if ($menu_link->link_path->value == 'user' && $menu_link->module == 'system') {
     $menu_link->options['alter'] = TRUE;
   }
 }
@@ -82,7 +82,7 @@ function hook_menu_link_insert(\Drupal\menu_link\Entity\MenuLink $menu_link) {
   // In our sample case, we track menu items as editing sections
   // of the site. These are stored in our table as 'disabled' items.
   $record['mlid'] = $menu_link->id();
-  $record['menu_name'] = $menu_link->menu_name;
+  $record['menu_name'] = $menu_link->menu_name->value;
   $record['status'] = 0;
   drupal_write_record('menu_example', $record);
 }
@@ -104,9 +104,9 @@ function hook_menu_link_insert(\Drupal\menu_link\Entity\MenuLink $menu_link) {
 function hook_menu_link_update(\Drupal\menu_link\Entity\MenuLink $menu_link) {
   // If the parent menu has changed, update our record.
   $menu_name = db_query("SELECT menu_name FROM {menu_example} WHERE mlid = :mlid", array(':mlid' => $menu_link->id()))->fetchField();
-  if ($menu_name != $menu_link->menu_name) {
+  if ($menu_name != $menu_link->menu_name->value) {
     db_update('menu_example')
-      ->fields(array('menu_name' => $menu_link->menu_name))
+      ->fields(array('menu_name' => $menu_link->menu_name->value))
       ->condition('mlid', $menu_link->id())
       ->execute();
   }
diff --git a/core/modules/menu_link/menu_link.module b/core/modules/menu_link/menu_link.module
index 7e0358e..b7b4449 100644
--- a/core/modules/menu_link/menu_link.module
+++ b/core/modules/menu_link/menu_link.module
@@ -208,7 +208,7 @@ function menu_link_system_breadcrumb_alter(array &$breadcrumb, array $attributes
     $menu_link = $attributes['menu_link'];
     if (($menu_link instanceof MenuLinkInterface) && !$menu_link->isNew()) {
       // Add a link to the menu admin screen.
-      $menu = entity_load('menu', $menu_link->menu_name);
+      $menu = entity_load('menu', $menu_link->menu_name->value);
       $breadcrumb[] = Drupal::l($menu->label(), 'menu.menu_edit', array('menu' => $menu->id));
     }
   }
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Form/LinkDelete.php b/core/modules/shortcut/lib/Drupal/shortcut/Form/LinkDelete.php
index a0012dc..6f0008f 100644
--- a/core/modules/shortcut/lib/Drupal/shortcut/Form/LinkDelete.php
+++ b/core/modules/shortcut/lib/Drupal/shortcut/Form/LinkDelete.php
@@ -33,7 +33,7 @@ public function getFormID() {
    * {@inheritdoc}
    */
   public function getQuestion() {
-    return t('Are you sure you want to delete the shortcut %title?', array('%title' => $this->menuLink->link_title));
+    return t('Are you sure you want to delete the shortcut %title?', array('%title' => $this->menuLink->link_title->value));
   }
 
   /**
@@ -43,7 +43,7 @@ public function getCancelRoute() {
     return array(
       'route_name' => 'shortcut.set_customize',
       'route_parameters' => array(
-        'shortcut_set' => str_replace('shortcut-', '', $this->menuLink->menu_name),
+        'shortcut_set' => str_replace('shortcut-', '', $this->menuLink->menu_name->value),
       ),
     );
   }
@@ -68,10 +68,10 @@ public function buildForm(array $form, array &$form_state, MenuLink $menu_link =
    * {@inheritdoc}
    */
   public function submitForm(array &$form, array &$form_state) {
-    menu_link_delete($this->menuLink->mlid);
-    $set_name = str_replace('shortcut-', '' , $this->menuLink->menu_name);
+    menu_link_delete($this->menuLink->id());
+    $set_name = str_replace('shortcut-', '' , $this->menuLink->menu_name->value);
     $form_state['redirect'] = 'admin/config/user-interface/shortcut/manage/' . $set_name;
-    drupal_set_message(t('The shortcut %title has been deleted.', array('%title' => $this->menuLink->link_title)));
+    drupal_set_message(t('The shortcut %title has been deleted.', array('%title' => $this->menuLink->link_title->value)));
   }
 
 }
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Form/SetCustomize.php b/core/modules/shortcut/lib/Drupal/shortcut/Form/SetCustomize.php
index 29da4a8..97aafee 100644
--- a/core/modules/shortcut/lib/Drupal/shortcut/Form/SetCustomize.php
+++ b/core/modules/shortcut/lib/Drupal/shortcut/Form/SetCustomize.php
@@ -38,13 +38,13 @@ public function form(array $form, array &$form_state) {
     foreach ($this->entity->links as $link) {
       $mlid = $link->id();
       $form['shortcuts']['links'][$mlid]['#attributes']['class'][] = 'draggable';
-      $form['shortcuts']['links'][$mlid]['name']['#markup'] = l($link->link_title, $link->link_path);
-      $form['shortcuts']['links'][$mlid]['#weight'] = $link->weight;
+      $form['shortcuts']['links'][$mlid]['name']['#markup'] = l($link->link_title->value, $link->link_path->value);
+      $form['shortcuts']['links'][$mlid]['#weight'] = $link->weight->value;
       $form['shortcuts']['links'][$mlid]['weight'] = array(
         '#type' => 'weight',
-        '#title' => t('Weight for @title', array('@title' => $link->link_title)),
+        '#title' => t('Weight for @title', array('@title' => $link->link_title->value)),
         '#title_display' => 'invisible',
-        '#default_value' => $link->weight,
+        '#default_value' => $link->weight->value,
         '#attributes' => array('class' => array('shortcut-weight')),
       );
 
@@ -88,7 +88,7 @@ protected function actions(array $form, array &$form_state) {
    */
   public function save(array $form, array &$form_state) {
     foreach ($this->entity->links as $link) {
-      $link->weight = $form_state['values']['shortcuts']['links'][$link->mlid]['weight'];
+      $link->weight = $form_state['values']['shortcuts']['links'][$link->id()]['weight'];
       $link->save();
     }
     drupal_set_message(t('The shortcut set has been updated.'));
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php
index bd2d194..08d70dc 100644
--- a/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php
+++ b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutLinksTest.php
@@ -78,7 +78,7 @@ function testShortcutQuickLink() {
 
     $link = reset($this->set->links);
 
-    $this->drupalGet($link->link_path);
+    $this->drupalGet($link->getLinkPath());
     $this->assertRaw(t('Remove from %title shortcuts', array('%title' => $this->set->label())), '"Add to shortcuts" link properly switched to "Remove from shortcuts".');
   }
 
@@ -92,7 +92,7 @@ function testShortcutLinkRename() {
     $new_link_name = $this->randomName();
 
     $link = reset($set->links);
-    $this->drupalPostForm('admin/config/user-interface/shortcut/link/' . $link->mlid, array('shortcut_link[link_title]' => $new_link_name, 'shortcut_link[link_path]' => $link->link_path), t('Save'));
+    $this->drupalPostForm('admin/config/user-interface/shortcut/link/' . $link->id(), array('shortcut_link[link_title]' => $new_link_name, 'shortcut_link[link_path]' => $link->getLinkPath()), t('Save'));
     $saved_set = shortcut_set_load($set->id());
     $titles = $this->getShortcutInformation($saved_set, 'link_title');
     $this->assertTrue(in_array($new_link_name, $titles), 'Shortcut renamed: ' . $new_link_name);
@@ -109,7 +109,7 @@ function testShortcutLinkChangePath() {
     $new_link_path = 'admin/config';
 
     $link = reset($set->links);
-    $this->drupalPostForm('admin/config/user-interface/shortcut/link/' . $link->mlid, array('shortcut_link[link_title]' => $link->link_title, 'shortcut_link[link_path]' => $new_link_path), t('Save'));
+    $this->drupalPostForm('admin/config/user-interface/shortcut/link/' . $link->id(), array('shortcut_link[link_title]' => $link->getLinkTitle(), 'shortcut_link[link_path]' => $new_link_path), t('Save'));
     $saved_set = shortcut_set_load($set->id());
     $paths = $this->getShortcutInformation($saved_set, 'link_path');
     $this->assertTrue(in_array($new_link_path, $paths), 'Shortcut path changed: ' . $new_link_path);
@@ -123,10 +123,10 @@ function testShortcutLinkDelete() {
     $set = $this->set;
 
     $link = reset($set->links);
-    $this->drupalPostForm('admin/config/user-interface/shortcut/link/' . $link->mlid . '/delete', array(), 'Delete');
+    $this->drupalPostForm('admin/config/user-interface/shortcut/link/' . $link->id() . '/delete', array(), 'Delete');
     $saved_set = shortcut_set_load($set->id());
     $mlids = $this->getShortcutInformation($saved_set, 'mlid');
-    $this->assertFalse(in_array($link->mlid, $mlids), 'Successfully deleted a shortcut.');
+    $this->assertFalse(in_array($link->id(), $mlids), 'Successfully deleted a shortcut.');
 
     // Delete all the remaining shortcut menu links.
     foreach (array_filter($mlids) as $mlid) {
diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutTestBase.php b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutTestBase.php
index 06088ef..2fcfa5b 100644
--- a/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutTestBase.php
+++ b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutTestBase.php
@@ -129,7 +129,7 @@ function generateShortcutLink($path, $title = '') {
   function getShortcutInformation($set, $key) {
     $info = array();
     foreach ($set->links as $uuid => $link) {
-      $info[] = $link->{$key};
+      $info[] = $link->{$key}->value;
     }
     return $info;
   }
diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module
index 8ca0472..355bc61 100644
--- a/core/modules/shortcut/shortcut.module
+++ b/core/modules/shortcut/shortcut.module
@@ -56,7 +56,7 @@ function shortcut_entity_bundle_info() {
 function shortcut_menu_link_load($entities) {
   foreach ($entities as $entity) {
     // Change the bundle of menu links related to a shortcut.
-    if (strpos($entity->menu_name, 'shortcut-') === 0) {
+    if (strpos($entity->menu_name->value, 'shortcut-') === 0) {
       $entity->bundle = 'shortcut';
     }
   }
@@ -219,9 +219,9 @@ function shortcut_link_access($menu_link) {
  */
 function shortcut_menu_link_delete($menu_link) {
   // If the deleted menu link was in a shortcut set, remove it.
-  if (strpos($menu_link->menu_name, 'shortcut-') === 0) {
-    $shortcut = entity_load('shortcut_set', str_replace('shortcut-', '', $menu_link->menu_name));
-    unset($shortcut->links[$menu_link->uuid]);
+  if (strpos($menu_link->menu_name->value, 'shortcut-') === 0) {
+    $shortcut = entity_load('shortcut_set', str_replace('shortcut-', '', $menu_link->menu_name->value));
+    unset($shortcut->links[$menu_link->uuid()]);
     $shortcut->save();
   }
 }
@@ -458,8 +458,8 @@ function shortcut_preprocess_page(&$variables) {
 
     // Check if $link is already a shortcut and set $link_mode accordingly.
     foreach ($shortcut_set->links as $shortcut) {
-      if ($link == $shortcut['link_path']) {
-        $mlid = $shortcut['mlid'];
+      if ($link == $shortcut->getLinkPath()) {
+        $mlid = $shortcut->id();
         break;
       }
     }
diff --git a/core/modules/system/lib/Drupal/system/Controller/SystemController.php b/core/modules/system/lib/Drupal/system/Controller/SystemController.php
index eaaa8a0..b5eef16 100644
--- a/core/modules/system/lib/Drupal/system/Controller/SystemController.php
+++ b/core/modules/system/lib/Drupal/system/Controller/SystemController.php
@@ -78,7 +78,7 @@ public function overview() {
       $system_link = reset($system_link);
       $query = $this->queryFactory->get('menu_link')
         ->condition('link_path', 'admin/help', '<>')
-        ->condition('menu_name', $system_link->menu_name)
+        ->condition('menu_name', $system_link->getMenuName())
         ->condition('plid', $system_link->id())
         ->condition('hidden', 0);
       $result = $query->execute();
@@ -86,16 +86,20 @@ public function overview() {
         $menu_links = $menu_link_storage->loadMultiple($result);
         foreach ($menu_links as $item) {
           _menu_link_translate($item);
-          if (!$item['access']) {
+          if (!$item->getAccess()) {
             continue;
           }
           // The link description, either derived from 'description' in hook_menu()
           // or customized via menu module is used as title attribute.
-          if (!empty($item['localized_options']['attributes']['title'])) {
-            $item['description'] = $item['localized_options']['attributes']['title'];
-            unset($item['localized_options']['attributes']['title']);
+          if (!empty($item->localized_options->attributes['title'])) {
+            $item['description'] = $item->localized_options->attributes['title'];
+            unset($item->localized_options->attributes['title']);
           }
-          $block = $item;
+          $block = array(
+            'title' => $item->getLinkTitle(),
+            'description' => $item['description'],
+            'position' => $item['position'],
+          );
           $block['content'] = array(
             '#theme' => 'admin_block_content',
             '#content' => $this->systemManager->getAdminBlock($item),
@@ -107,7 +111,7 @@ public function overview() {
 
           // Prepare for sorting as in function _menu_tree_check_access().
           // The weight is offset so it is always positive, with a uniform 5-digits.
-          $blocks[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $block;
+          $blocks[(50000 + $item->getWeight()) . ' ' . $item['title'] . ' ' . $item->id()] = $block;
         }
       }
     }
diff --git a/core/modules/system/lib/Drupal/system/SystemManager.php b/core/modules/system/lib/Drupal/system/SystemManager.php
index 619642e..57d893f 100644
--- a/core/modules/system/lib/Drupal/system/SystemManager.php
+++ b/core/modules/system/lib/Drupal/system/SystemManager.php
@@ -198,7 +198,7 @@ public function getAdminBlock($item) {
       $menu_links = $this->menuLinkStorage->loadByProperties(array('router_path' => $item['path'], 'module' => 'system'));
       $menu_link = reset($menu_links);
       $item['mlid'] = $menu_link->id();
-      $item['menu_name'] = $menu_link->menu_name;
+      $item['menu_name'] = $menu_link->menu_name->value;
     }
 
     if (isset($this->menuItems[$item['mlid']])) {
@@ -209,16 +209,16 @@ public function getAdminBlock($item) {
     $menu_links = $this->menuLinkStorage->loadByProperties(array('plid' => $item['mlid'], 'menu_name' => $item['menu_name'], 'hidden' => 0));
     foreach ($menu_links as $link) {
       _menu_link_translate($link);
-      if ($link['access']) {
+      if ($link->getAccess()) {
         // The link description, either derived from 'description' in
         // hook_menu() or customized via menu module is used as title attribute.
-        if (!empty($link['localized_options']['attributes']['title'])) {
-          $link['description'] = $link['localized_options']['attributes']['title'];
-          unset($link['localized_options']['attributes']['title']);
+        if (!empty($link->localized_options->attributes['title'])) {
+          $link['description'] = $link->localized_options->attributes['title'];
+          unset($link->localized_options->attributes['title']);
         }
         // Prepare for sorting as in function _menu_tree_check_access().
         // The weight is offset so it is always positive, with a uniform 5-digits.
-        $key = (50000 + $link['weight']) . ' ' . Unicode::strtolower($link['title']) . ' ' . $link['mlid'];
+        $key = (50000 + $link->getWeight()) . ' ' . Unicode::strtolower($link['title']) . ' ' . $link->id();
         $content[$key] = $link;
       }
     }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/LinksTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/LinksTest.php
index bcf7c96..b05f9e3 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/LinksTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/LinksTest.php
@@ -246,19 +246,19 @@ public function testRouterIntegration() {
       'link_path' => 'router_test/test1',
     ));
     $menu_link->save();
-    $this->assertEqual($menu_link->route_name, 'router_test.1');
-    $this->assertEqual($menu_link->route_parameters, array());
+    $this->assertEqual($menu_link->getRouteName(), 'router_test.1');
+    $this->assertEqual($menu_link->getRouteParams(), array());
 
     $menu_link = entity_create('menu_link', array(
       'link_path' => 'router_test/test3/test',
     ));
     $menu_link->save();
-    $this->assertEqual($menu_link->route_name, 'router_test.3');
-    $this->assertEqual($menu_link->route_parameters, array('value' => 'test'));
+    $this->assertEqual($menu_link->getRouteName(), 'router_test.3');
+    $this->assertEqual($menu_link->getRouteParams(), 'test');
 
     $menu_link = entity_load('menu_link', $menu_link->id());
-    $this->assertEqual($menu_link->route_name, 'router_test.3');
-    $this->assertEqual($menu_link->route_parameters, array('value' => 'test'));
+    $this->assertEqual($menu_link->getRouteName(), 'router_test.3');
+    $this->assertEqual($menu_link->getRouteParams(), 'test');
   }
 
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php
index 381e891..86e1eb3 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php
@@ -323,7 +323,7 @@ function testMenuName() {
 
     $menu_links = entity_load_multiple_by_properties('menu_link', array('router_path' => 'menu_name_test'));
     $menu_link = reset($menu_links);
-    $this->assertEqual($menu_link->menu_name, 'original', 'Menu name is "original".');
+    $this->assertEqual($menu_link->menu_name->value, 'original', 'Menu name is "original".');
 
     // Change the menu_name parameter in menu_test.module, then force a menu
     // rebuild.
@@ -333,7 +333,7 @@ function testMenuName() {
 
     $menu_links = entity_load_multiple_by_properties('menu_link', array('router_path' => 'menu_name_test'));
     $menu_link = reset($menu_links);
-    $this->assertEqual($menu_link->menu_name, 'changed', 'Menu name was successfully changed after rebuild.');
+    $this->assertEqual($menu_link->menu_name->value, 'changed', 'Menu name was successfully changed after rebuild.');
   }
 
   /**
@@ -364,7 +364,7 @@ function testMenuHidden() {
 
     $links = array();
     foreach ($menu_links as $menu_link) {
-      $links[$menu_link->router_path] = $menu_link;
+      $links[$menu_link->router_path->value] = $menu_link;
     }
 
     $parent = $links['menu-test/hidden/menu'];
@@ -416,7 +416,7 @@ function testMenuHidden() {
 
     $links = array();
     foreach ($menu_links as $menu_link) {
-      $links[$menu_link->router_path] = $menu_link;
+      $links[$menu_link->router_path->value] = $menu_link;
     }
 
     $parent = $links['menu-test/hidden/block'];
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeAccessTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeAccessTest.php
index df10877..c54a2aa 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeAccessTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeAccessTest.php
@@ -38,7 +38,7 @@ class TreeAccessTest extends DrupalUnitTestBase {
    *
    * @var array
    */
-  public static $modules = array('menu_link');
+  public static $modules = array('field', 'menu_link');
 
   public static function getInfo() {
     return array(
@@ -100,8 +100,8 @@ public function testRouteItemMenuLinksAccess() {
 
     // Setup the links with the route items.
     $this->links = array(
-      new MenuLink(array('mlid' => 1, 'route_name' => 'menu_test_1', 'depth' => 1, 'link_path' => 'menu_test/test_1'), 'menu_link'),
-      new MenuLink(array('mlid' => 2, 'route_name' => 'menu_test_2', 'depth' => 1, 'link_path' => 'menu_test/test_2'), 'menu_link'),
+      entity_create('menu_link', array('mlid' => 1, 'route_name' => 'menu_test_1', 'depth' => 1, 'link_path' => 'menu_test/test_1')),
+      entity_create('menu_link', array('mlid' => 2, 'route_name' => 'menu_test_2', 'depth' => 1, 'link_path' => 'menu_test/test_2')),
     );
 
     // Build the menu tree and check access for all of the items.
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeDataUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeDataUnitTest.php
index 8b6c4a1..ce4c9ab 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeDataUnitTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeDataUnitTest.php
@@ -8,12 +8,20 @@
 namespace Drupal\system\Tests\Menu;
 
 use Drupal\menu_link\Entity\MenuLink;
-use Drupal\simpletest\UnitTestBase;
+use Drupal\simpletest\DrupalUnitTestBase;
 
 /**
  * Menu tree data related tests.
  */
-class TreeDataUnitTest extends UnitTestBase {
+class TreeDataUnitTest extends DrupalUnitTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('field', 'menu_link');
+
   /**
    * Dummy link structure acceptable for menu_tree_data().
    */
@@ -32,11 +40,11 @@ public static function getInfo() {
    */
   public function testMenuTreeData() {
     $this->links = array(
-      1 => new MenuLink(array('mlid' => 1, 'depth' => 1), 'menu_link'),
-      2 => new MenuLink(array('mlid' => 2, 'depth' => 1), 'menu_link'),
-      3 => new MenuLink(array('mlid' => 3, 'depth' => 2), 'menu_link'),
-      4 => new MenuLink(array('mlid' => 4, 'depth' => 3), 'menu_link'),
-      5 => new MenuLink(array('mlid' => 5, 'depth' => 1), 'menu_link'),
+      1 => entity_create('menu_link', array('mlid' => 1, 'depth' => 1)),
+      2 => entity_create('menu_link', array('mlid' => 2, 'depth' => 1)),
+      3 => entity_create('menu_link', array('mlid' => 3, 'depth' => 2)),
+      4 => entity_create('menu_link', array('mlid' => 4, 'depth' => 3)),
+      5 => entity_create('menu_link', array('mlid' => 5, 'depth' => 1)),
     );
 
     $tree = menu_tree_data($this->links);
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index f1a428f..5fc94d1 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -344,7 +344,7 @@ function theme_admin_block_content($variables) {
     }
     $output .= '<dl class="' . $class . '">';
     foreach ($content as $item) {
-      $output .= '<dt>' . l($item['title'], $item['href'], $item['localized_options']) . '</dt>';
+      $output .= '<dt>' . l($item['link_title'], $item['link_path'], $item['localized_options']) . '</dt>';
       if (!$compact && isset($item['description'])) {
         $output .= '<dd>' . filter_xss_admin($item['description']) . '</dd>';
       }
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 596122b..68f831e 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -2288,6 +2288,7 @@ function system_update_8060() {
     'size' => 'big',
     'not null' => FALSE,
     'serialize' => TRUE,
+    'initial' => 'a:0:{}',
   );
 
   db_add_field('menu_links', 'route_parameters', $spec);
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index fb96b52..38c1452 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -2900,9 +2900,9 @@ function system_get_module_admin_tasks($module, $info) {
         $task = $links[$path];
         // The link description, either derived from 'description' in
         // hook_menu() or customized via menu module is used as title attribute.
-        if (!empty($task['localized_options']['attributes']['title'])) {
-          $task['description'] = $task['localized_options']['attributes']['title'];
-          unset($task['localized_options']['attributes']['title']);
+        if (!empty($link->localized_options->attributes['title'])) {
+          $link['description'] = $link->localized_options->attributes['title'];
+          unset($link->localized_options->attributes['title']);
         }
 
         // Check the admin tasks for duplicate names. If one is found,
diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module
index b20474b..a31a393 100644
--- a/core/modules/toolbar/toolbar.module
+++ b/core/modules/toolbar/toolbar.module
@@ -495,9 +495,9 @@ function toolbar_get_menu_tree() {
   if (!empty($result)) {
     $admin_link = menu_link_load(reset($result));
     $tree = menu_build_tree('admin', array(
-      'expanded' => array($admin_link['mlid']),
-      'min_depth' => $admin_link['depth'] + 1,
-      'max_depth' => $admin_link['depth'] + 1,
+      'expanded' => array($admin_link->id()),
+      'min_depth' => $admin_link->getDepth() + 1,
+      'max_depth' => $admin_link->getDepth() + 1,
     ));
   }
 
@@ -521,11 +521,11 @@ function toolbar_menu_navigation_links(&$tree) {
     }
     // Make sure we have a path specific ID in place, so we can attach icons
     // and behaviors to the items.
-    $tree[$key]['link']['localized_options']['attributes'] = array(
-      'id' => 'toolbar-link-' . str_replace(array('/', '<', '>'), array('-', '', ''), $item['link']['link_path']),
+    $l_options = $tree[$key]['link']->localized_options->attributes = array(
+      'id' => 'toolbar-link-' . str_replace(array('/', '<', '>'), array('-', '', ''), $item['link']->getLinkPath()),
       'class' => array(
         'toolbar-icon',
-        'toolbar-icon-' . strtolower(str_replace(' ', '-', $item['link']['link_title'])),
+        'toolbar-icon-' . strtolower(str_replace(' ', '-', $item['link']->getLinkTitle())),
       ),
       'title' => check_plain($item['link']['description']),
     );
@@ -540,16 +540,17 @@ function toolbar_get_rendered_subtrees() {
   $tree = toolbar_get_menu_tree();
   foreach ($tree as $tree_item) {
     $item = $tree_item['link'];
-    if (!$item['hidden'] && $item['access']) {
-      if ($item['has_children']) {
+    if (!$item->isHidden() && $item->getAccess()) {
+      if ($item->hasChildren()) {
         $query = db_select('menu_links');
         $query->addField('menu_links', 'mlid');
         $query->condition('has_children', 1);
-        for ($i=1; $i <= $item['depth']; $i++) {
-          $query->condition('p' . $i, $item['p' . $i]);
+        for ($i=1; $i <= $item->getDepth(); $i++) {
+          $p_i = 'p' . $i;
+          $query->condition('p' . $i, $item->{$p_i}->value);
         }
         $parents = $query->execute()->fetchCol();
-        $subtree = menu_build_tree($item['menu_name'], array('expanded' => $parents, 'min_depth' => $item['depth']+1));
+        $subtree = menu_build_tree($item->getMenuName(), array('expanded' => $parents, 'min_depth' => $item->getDepth()+1));
         toolbar_menu_navigation_links($subtree);
         $subtree = menu_tree_output($subtree);
         $subtree = drupal_render($subtree);
@@ -655,7 +656,7 @@ function toolbar_modules_uninstalled($modules) {
  * Implements hook_ENTITY_TYPE_update().
  */
 function toolbar_menu_link_update(MenuLinkInterface $menu_link) {
-  if ($menu_link->get('menu_name') === 'admin') {
+  if ($menu_link->getMenuName() === 'admin') {
     _toolbar_clear_user_cache();
   }
 }
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 24aee74..ac0de9f 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -877,13 +877,13 @@ function user_menu_link_presave(MenuLink $menu_link) {
   // for authenticated users. Authenticated users should see "My account", but
   // anonymous users should not see it at all. Therefore, invoke
   // user_menu_link_load() to conditionally hide the link.
-  if ($menu_link->link_path == 'user' && $menu_link->module == 'system') {
-    $menu_link->options['alter'] = TRUE;
+  if ($menu_link->getLinkPath() == 'user' && $menu_link->getModule() == 'system') {
+    //$menu_link->options['alter'] = TRUE;
   }
 
   // Force the Logout link to appear on the top-level of 'account' menu by
   // default (i.e., unless it has been customized).
-  if ($menu_link->link_path == 'user/logout' && $menu_link->module == 'system' && empty($menu_link->customized)) {
+  if ($menu_link->getLinkPath() == 'user/logout' && $menu_link->getModule() == 'system' && !$menu_link->isCustomized()) {
     $menu_link->plid = 0;
   }
 }
@@ -905,8 +905,8 @@ function user_menu_breadcrumb_alter(&$active_trail, $item) {
 function user_menu_link_load($menu_links) {
   // Hide the "User account" link for anonymous users.
   foreach ($menu_links as $link) {
-    if ($link['link_path'] == 'user' && $link['module'] == 'system' && !$GLOBALS['user']->id()) {
-      $link['hidden'] = 1;
+    if ($link->getLinkPath() == 'user' && $link->getModule() == 'system' && $GLOBALS['user']->isAnonymous()) {
+      $link->hidden = 1;
     }
   }
 }
