diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
index 1ce4456..f827bc5 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
@@ -329,6 +329,7 @@ protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
    */
   public function create(array $values) {
     $class = $this->entityInfo['class'];
+    $class::preCreate($this, $values);
 
     // Set default language to site default if not provided.
     $values += array('langcode' => language_default()->langcode);
@@ -343,6 +344,7 @@ public function create(array $values) {
       $uuid = new Uuid();
       $entity->{$this->uuidKey} = $uuid->generate();
     }
+    $entity->postCreate($this);
 
     // Modules might need to add or change the data initially held by the new
     // entity object, for instance to fill-in default values.
@@ -365,7 +367,8 @@ public function delete(array $entities) {
       return;
     }
 
-    $this->preDelete($entities);
+    $entity_class = $this->entityInfo['class'];
+    $entity_class::preDelete($this, $entities);
     foreach ($entities as $id => $entity) {
       $this->invokeHook('predelete', $entity);
     }
@@ -384,7 +387,7 @@ public function delete(array $entities) {
       $manifest->save();
     }
 
-    $this->postDelete($entities);
+    $entity_class::postDelete($this, $entities);
     foreach ($entities as $id => $entity) {
       $this->invokeHook('delete', $entity);
     }
@@ -427,7 +430,7 @@ public function save(EntityInterface $entity) {
       $this->configFactory->rename($prefix . $id, $prefix . $entity->id());
     }
 
-    $this->preSave($entity);
+    $entity->preSave($this);
     $this->invokeHook('presave', $entity);
 
     // Retrieve the desired properties and set them in config.
@@ -438,7 +441,7 @@ public function save(EntityInterface $entity) {
     if (!$is_new) {
       $return = SAVED_UPDATED;
       $config->save();
-      $this->postSave($entity, TRUE);
+      $entity->postSave($this,TRUE);
       $this->invokeHook('update', $entity);
 
       // Immediately update the original ID.
@@ -448,7 +451,7 @@ public function save(EntityInterface $entity) {
       $return = SAVED_NEW;
       $config->save();
       $entity->enforceIsNew(FALSE);
-      $this->postSave($entity, FALSE);
+      $entity->postSave($this, FALSE);
       $this->invokeHook('insert', $entity);
     }
 
@@ -480,45 +483,6 @@ public function save(EntityInterface $entity) {
   }
 
   /**
-   * Acts on an entity before the presave hook is invoked.
-   *
-   * Used before the entity is saved and before invoking the presave hook.
-   */
-  protected function preSave(EntityInterface $entity) {
-  }
-
-  /**
-   * Acts on a saved entity before the insert or update hook is invoked.
-   *
-   * Used after the entity is saved, but before invoking the insert or update
-   * hook.
-   *
-   * @param EntityInterface $entity
-   *   The entity to act on.
-   * @param $update
-   *   (bool) TRUE if the entity has been updated, or FALSE if it has been
-   *   inserted.
-   */
-  protected function postSave(EntityInterface $entity, $update) {
-  }
-
-  /**
-   * Acts on entities before they are deleted.
-   *
-   * Used before the entities are deleted and before invoking the delete hook.
-   */
-  protected function preDelete($entities) {
-  }
-
-  /**
-   * Acts on deleted entities before the delete hook is invoked.
-   *
-   * Used after the entities are deleted but before invoking the delete hook.
-   */
-  protected function postDelete($entities) {
-  }
-
-  /**
    * Implements Drupal\Core\Entity\EntityStorageControllerInterface::getFieldDefinitions().
    */
   public function getFieldDefinitions(array $constraints) {
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
index bb8e2bb..235fe6e 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
@@ -475,15 +475,17 @@ protected function cacheSet($entities) {
    * Implements \Drupal\Core\Entity\EntityStorageControllerInterface::create().
    */
   public function create(array $values) {
-    $class = $this->entityInfo['class'];
+    $entity_class = $this->entityInfo['class'];
+    $entity_class::preCreate($this, $values);
 
-    $entity = new $class($values, $this->entityType);
+    $entity = new $entity_class($values, $this->entityType);
 
     // Assign a new UUID if there is none yet.
     if ($this->uuidKey && !isset($entity->{$this->uuidKey})) {
       $uuid = new Uuid();
       $entity->{$this->uuidKey} = $uuid->generate();
     }
+    $entity->postCreate($this);
 
     // Modules might need to add or change the data initially held by the new
     // entity object, for instance to fill-in default values.
@@ -503,7 +505,8 @@ public function delete(array $entities) {
     $transaction = $this->database->startTransaction();
 
     try {
-      $this->preDelete($entities);
+      $entity_class = $this->entityInfo['class'];
+      $entity_class::preDelete($this, $entities);
       foreach ($entities as $id => $entity) {
         $this->invokeHook('predelete', $entity);
       }
@@ -522,7 +525,7 @@ public function delete(array $entities) {
       // Reset the cache as soon as the changes have been applied.
       $this->resetCache($ids);
 
-      $this->postDelete($entities);
+      $entity_class::postDelete($this, $entities);
       foreach ($entities as $id => $entity) {
         $this->invokeHook('delete', $entity);
       }
@@ -547,7 +550,7 @@ public function save(EntityInterface $entity) {
         $entity->original = entity_load_unchanged($this->entityType, $entity->id());
       }
 
-      $this->preSave($entity);
+      $entity->preSave($this);
       $this->invokeHook('presave', $entity);
 
       if (!$entity->isNew()) {
@@ -563,7 +566,7 @@ public function save(EntityInterface $entity) {
           $this->saveRevision($entity);
         }
         $this->resetCache(array($entity->id()));
-        $this->postSave($entity, TRUE);
+        $entity->postSave($this,TRUE);
         $this->invokeHook('update', $entity);
       }
       else {
@@ -575,7 +578,7 @@ public function save(EntityInterface $entity) {
         $this->resetCache(array());
 
         $entity->enforceIsNew(FALSE);
-        $this->postSave($entity, FALSE);
+        $entity->postSave($this, FALSE);
         $this->invokeHook('insert', $entity);
       }
 
@@ -612,7 +615,7 @@ protected function saveRevision(EntityInterface $entity) {
     // Cast to object as preSaveRevision() expects one to be compatible with the
     // upcoming NG storage controller.
     $record = (object) $record;
-    $this->preSaveRevision($record, $entity);
+    $entity->preSaveRevision($this, $record);
     $record = (array) $record;
 
     if ($entity->isNewRevision()) {
@@ -633,49 +636,6 @@ protected function saveRevision(EntityInterface $entity) {
   }
 
   /**
-   * Acts on an entity before the presave hook is invoked.
-   *
-   * Used before the entity is saved and before invoking the presave hook.
-   */
-  protected function preSave(EntityInterface $entity) { }
-
-  /**
-   * Acts on a saved entity before the insert or update hook is invoked.
-   *
-   * Used after the entity is saved, but before invoking the insert or update
-   * hook.
-   *
-   * @param $update
-   *   (bool) TRUE if the entity has been updated, or FALSE if it has been
-   *   inserted.
-   */
-  protected function postSave(EntityInterface $entity, $update) { }
-
-  /**
-   * Acts on entities before they are deleted.
-   *
-   * Used before the entities are deleted and before invoking the delete hook.
-   */
-  protected function preDelete($entities) { }
-
-  /**
-   * Acts on deleted entities before the delete hook is invoked.
-   *
-   * Used after the entities are deleted but before invoking the delete hook.
-   */
-  protected function postDelete($entities) { }
-
-  /**
-   * Act on a revision before being saved.
-   *
-   * @param \stdClass $record
-   *   The revision object.
-   * @param Drupal\Core\Entity\EntityInterface $entity
-   *   The entity object.
-   */
-  protected function preSaveRevision(\stdClass $record, EntityInterface $entity) { }
-
-  /**
    * Invokes a hook on behalf of the entity.
    *
    * @param $hook
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
index 91ec62d..c3a76e2 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
@@ -98,6 +98,9 @@ public function __construct($entity_type, array $entity_info, Connection $databa
    *   A new entity object.
    */
   public function create(array $values) {
+    $entity_class = $this->entityClass;
+    $entity_class::preCreate($this, $values);
+
     // We have to determine the bundle first.
     $bundle = FALSE;
     if ($this->bundleKey) {
@@ -118,6 +121,7 @@ public function create(array $values) {
       $uuid = new Uuid();
       $entity->{$this->uuidKey} = $uuid->generate();
     }
+    $entity->postCreate($this);
 
     // Modules might need to add or change the data initially held by the new
     // entity object, for instance to fill-in default values.
@@ -354,7 +358,7 @@ public function save(EntityInterface $entity) {
         $entity->original = entity_load_unchanged($this->entityType, $entity->id());
       }
 
-      $this->preSave($entity);
+      $entity->preSave($this);
       $this->invokeHook('presave', $entity);
 
       // Create the storage record to be saved.
@@ -376,7 +380,7 @@ public function save(EntityInterface $entity) {
           $this->savePropertyData($entity);
         }
         $this->resetCache(array($entity->id()));
-        $this->postSave($entity, TRUE);
+        $entity->postSave($this, TRUE);
         $this->invokeHook('update', $entity);
       }
       else {
@@ -394,7 +398,7 @@ public function save(EntityInterface $entity) {
         $this->resetCache(array());
 
         $entity->enforceIsNew(FALSE);
-        $this->postSave($entity, FALSE);
+        $entity->postSave($this, FALSE);
         $this->invokeHook('insert', $entity);
       }
 
@@ -445,7 +449,7 @@ protected function saveRevision(EntityInterface $entity) {
         $record->{$this->revisionKey} = NULL;
       }
 
-      $this->preSaveRevision($record, $entity);
+      $entity->preSaveRevision($this, $record);
 
       if ($entity->isNewRevision()) {
         drupal_write_record($this->revisionTable, $record);
@@ -596,12 +600,14 @@ public function delete(array $entities) {
 
     $transaction = $this->database->startTransaction();
     try {
+      $entity_class = $this->entityClass;
+      $entity_class::preDelete($this, $entities);
+
       // Ensure we are dealing with the actual entities.
       foreach ($entities as $id => $entity) {
         $entities[$id] = $entity->getNGEntity();
       }
 
-      $this->preDelete($entities);
       foreach ($entities as $id => $entity) {
         $this->invokeHook('predelete', $entity);
       }
@@ -626,7 +632,7 @@ public function delete(array $entities) {
       // Reset the cache as soon as the changes have been applied.
       $this->resetCache($ids);
 
-      $this->postDelete($entities);
+      $entity_class::postDelete($this, $entities);
       foreach ($entities as $id => $entity) {
         $this->invokeHook('delete', $entity);
       }
diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php
index 7ea8b62..2d3c230 100644
--- a/core/lib/Drupal/Core/Entity/Entity.php
+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -516,4 +516,36 @@ public function isTranslatable() {
     return !empty($bundles[$this->bundle()]['translatable']);
   }
 
+  public function preSave(EntityStorageControllerInterface $storage_controller) {
+
+  }
+
+  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
+
+  }
+
+  public static function preCreate(EntityStorageControllerInterface $storage_controller, &$values) {
+
+  }
+
+  public function postCreate(EntityStorageControllerInterface $storage_controller) {
+
+  }
+
+  public static function preDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+
+  }
+
+  public static function postDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+
+  }
+
+  public static function postLoad(EntityStorageControllerInterface $storage_controller, $entities) {
+
+  }
+
+  public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record) {
+
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php
index ffb0e0c..8819143 100644
--- a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php
+++ b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php
@@ -525,4 +525,36 @@ public function onChange($property_name) {
   public function isTranslatable() {
     return $this->decorated->isTranslatable();
   }
+
+  public function preSave(EntityStorageControllerInterface $storage_controller) {
+    $this->decorated->preSave($storage_controller);
+  }
+
+  public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record) {
+    $this->decorated->preSave($storage_controller, $record);
+  }
+
+  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
+    $this->decorated->postSave($storage_controller, $update);
+  }
+
+  public static function preCreate(EntityStorageControllerInterface $storage_controller, &$values) {
+    // No idea here.
+  }
+
+  public function postCreate(EntityStorageControllerInterface $storage_controller) {
+    $this->decorated->postCreate($storage_controller);
+  }
+
+  public static function preDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+    // No idea here.
+  }
+
+  public static function postDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+    // No idea here.
+  }
+
+  public static function postLoad(EntityStorageControllerInterface $storage_controller, $entities) {
+    // No idea here.
+  }
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php
index 57fe186..807915b 100644
--- a/core/lib/Drupal/Core/Entity/EntityInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityInterface.php
@@ -155,6 +155,46 @@ public function save();
   public function delete();
 
   /**
+   * Acts on an entity before the presave hook is invoked.
+   *
+   * Used before the entity is saved and before invoking the presave hook.
+   */
+  public function preSave(EntityStorageControllerInterface $storage_controller);
+
+  /**
+   * Act on a revision before being saved.
+   *
+   *
+   * @param EntityStorageControllerInterface $storage_controller
+   * @param \stdClass $record
+   */
+  public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record);
+
+  /**
+   * Acts on a saved entity before the insert or update hook is invoked.
+   *
+   * Used after the entity is saved, but before invoking the insert or update
+   * hook.
+   *
+   * @param EntityInterface $entity
+   *   The entity to act on.
+   * @param $update
+   *   (bool) TRUE if the entity has been updated, or FALSE if it has been
+   *   inserted.
+   */
+  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE);
+
+  public static function preCreate(EntityStorageControllerInterface $storage_controller, &$values);
+
+  public function postCreate(EntityStorageControllerInterface $storage_controller);
+
+  public static function preDelete(EntityStorageControllerInterface $storage_controller, $entities);
+
+  public static function postDelete(EntityStorageControllerInterface $storage_controller, $entities);
+
+  public static function postLoad(EntityStorageControllerInterface $storage_controller, $entities);
+
+  /**
    * Creates a duplicate of the entity.
    *
    * @return \Drupal\Core\Entity\EntityInterface
diff --git a/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php
index 365212a..c2f9920 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php
@@ -33,7 +33,7 @@ public function resetCache(array $ids = NULL);
    * Loads one or more entities.
    *
    * @param $ids
-   *   An array of entity IDs, or FALSE to load all entities.
+   *   An array of entity IDs, or NULL to load all entities.
    *
    * @return
    *   An array of entity objects indexed by their ids.
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/FeedStorageController.php b/core/modules/aggregator/lib/Drupal/aggregator/FeedStorageController.php
index 82a5643..d9ced42 100644
--- a/core/modules/aggregator/lib/Drupal/aggregator/FeedStorageController.php
+++ b/core/modules/aggregator/lib/Drupal/aggregator/FeedStorageController.php
@@ -8,6 +8,7 @@
 namespace Drupal\aggregator;
 
 use Drupal\Core\Entity\DatabaseStorageControllerNG;
+use Drupal\aggregator\Plugin\Core\Entity\Feed;
 use Drupal\Core\Entity\EntityInterface;
 
 /**
@@ -16,101 +17,14 @@
  * This extends the Drupal\Core\Entity\DatabaseStorageController class, adding
  * required special handling for feed entities.
  */
-class FeedStorageController extends DatabaseStorageControllerNG {
-
-  /**
-   * Overrides Drupal\Core\Entity\DataBaseStorageController::create().
-   */
-  public function create(array $values) {
-    $values += array(
-      'link' => '',
-      'description' => '',
-      'image' => '',
-    );
-    return parent::create($values);
-  }
+class FeedStorageController extends DatabaseStorageControllerNG implements FeedStorageControllerInterface {
 
   /**
    * Overrides Drupal\Core\Entity\DataBaseStorageController::attachLoad().
    */
   protected function attachLoad(&$queried_entities, $load_revision = FALSE) {
     parent::attachLoad($queried_entities, $load_revision);
-    foreach ($queried_entities as $item) {
-      $item->categories = db_query('SELECT c.cid, c.title FROM {aggregator_category} c JOIN {aggregator_category_feed} f ON c.cid = f.cid AND f.fid = :fid ORDER BY title', array(':fid' => $item->id()))->fetchAllKeyed();
-    }
-  }
-
-  /**
-   * Overrides Drupal\Core\Entity\DataBaseStorageController::preDelete().
-   */
-  protected function preDelete($entities) {
-    parent::preDelete($entities);
-
-    // Invalidate the block cache to update aggregator feed-based derivatives.
-    if (module_exists('block')) {
-      \Drupal::service('plugin.manager.block')->clearCachedDefinitions();
-    }
-    foreach ($entities as $entity) {
-      // Notify processors to remove stored items.
-      $manager = \Drupal::service('plugin.manager.aggregator.processor');
-      foreach ($manager->getDefinitions() as $id => $definition) {
-        $manager->createInstance($id)->remove($entity);
-      }
-    }
-  }
-
-  /**
-   * Overrides Drupal\Core\Entity\DataBaseStorageController::postDelete().
-   */
-  protected function postDelete($entities) {
-    parent::postDelete($entities);
-
-    foreach ($entities as $entity) {
-      // Make sure there is no active block for this feed.
-      $block_configs = config_get_storage_names_with_prefix('plugin.core.block');
-      foreach ($block_configs as $config_id) {
-        $config = config($config_id);
-        if ($config->get('id') == 'aggregator_feed_block:' . $entity->id()) {
-          $config->delete();
-        }
-      }
-    }
-  }
-
-  /**
-   * Overrides Drupal\Core\Entity\DataBaseStorageController::preSave().
-   */
-  protected function preSave(EntityInterface $entity) {
-    parent::preSave($entity);
-
-    // Invalidate the block cache to update aggregator feed-based derivatives.
-    if (module_exists('block')) {
-      drupal_container()->get('plugin.manager.block')->clearCachedDefinitions();
-    }
-    // An existing feed is being modified, delete the category listings.
-    db_delete('aggregator_category_feed')
-      ->condition('fid', $entity->id())
-      ->execute();
-  }
-
-  /**
-   * Overrides Drupal\Core\Entity\DataBaseStorageController::postSave().
-   */
-  protected function postSave(EntityInterface $entity, $update) {
-    parent::postSave($entity, $update);
-
-    if (!empty($entity->categories)) {
-      foreach ($entity->categories as $cid => $value) {
-        if ($value) {
-          db_insert('aggregator_category_feed')
-            ->fields(array(
-              'fid' => $entity->id(),
-              'cid' => $cid,
-            ))
-            ->execute();
-        }
-      }
-    }
+    $this->loadCategories($queried_entities);
   }
 
   /**
@@ -191,4 +105,39 @@ public function baseFieldDefinitions() {
     return $fields;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function loadCategories($entities) {
+    foreach ($entities as $item) {
+      $item->categories = db_query('SELECT c.cid, c.title FROM {aggregator_category} c JOIN {aggregator_category_feed} f ON c.cid = f.cid AND f.fid = :fid ORDER BY title', array(':fid' => $item->id()))->fetchAllKeyed();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function saveCategories($feed, array $categories) {
+    foreach ($categories as $cid => $value) {
+      if ($value) {
+        $this->database->insert('aggregator_category_feed')
+          ->fields(array(
+            'fid' => $feed->id(),
+            'cid' => $cid,
+          ))
+          ->execute();
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteCategories(array $feeds) {
+    // An existing feed is being modified, delete the category listings.
+    $this->database->delete('aggregator_category_feed')
+      ->condition('fid', array_keys($feeds))
+      ->execute();
+  }
+
 }
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/FeedStorageControllerInterface.php b/core/modules/aggregator/lib/Drupal/aggregator/FeedStorageControllerInterface.php
new file mode 100644
index 0000000..0ed9bd8
--- /dev/null
+++ b/core/modules/aggregator/lib/Drupal/aggregator/FeedStorageControllerInterface.php
@@ -0,0 +1,33 @@
+<?php
+/**
+ * @file
+ * Contains
+ */
+
+namespace Drupal\aggregator;
+
+use Drupal\aggregator\Plugin\Core\Entity\Feed;
+
+interface FeedStorageControllerInterface {
+
+  public function loadCategories($entities);
+
+  /**
+   * Save the categories of a feed.
+   *
+   * @param $feed
+   *   The feed object.
+   * @param array $categories
+   *   The array of categories.
+   */
+  public function saveCategories($feed, array $categories);
+
+  /**
+   * Delete the categories of a feed.
+   *
+   * @param array $feeds
+   *   A list of feed objects
+   */
+  public function deleteCategories(array $feeds);
+
+}
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/ItemStorageController.php b/core/modules/aggregator/lib/Drupal/aggregator/ItemStorageController.php
index 69876ab..8d9ab84 100644
--- a/core/modules/aggregator/lib/Drupal/aggregator/ItemStorageController.php
+++ b/core/modules/aggregator/lib/Drupal/aggregator/ItemStorageController.php
@@ -8,6 +8,7 @@
 namespace Drupal\aggregator;
 
 use Drupal\Core\Entity\DatabaseStorageControllerNG;
+use Drupal\aggregator\Plugin\Core\Entity\Item;
 use Drupal\Core\Entity\EntityInterface;
 
 /**
@@ -16,55 +17,14 @@
  * This extends the Drupal\Core\Entity\DatabaseStorageController class, adding
  * required special handling for feed item entities.
  */
-class ItemStorageController extends DatabaseStorageControllerNG {
-
-  /**
-   * Overrides Drupal\Core\Entity\DataBaseStorageController::create().
-   */
-  public function create(array $values) {
-    $entity = parent::create($values);
-
-    // Set an initial timestamp, this will be overwritten if known.
-    $entity->timestamp->value = REQUEST_TIME;
-    return $entity;
-  }
+class ItemStorageController extends DatabaseStorageControllerNG implements ItemStorageControllerInterface {
 
   /**
    * Overrides Drupal\Core\Entity\DataBaseStorageController::attachLoad().
    */
   protected function attachLoad(&$queried_entities, $load_revision = FALSE) {
     parent::attachLoad($queried_entities, $load_revision);
-    foreach ($queried_entities as $item) {
-      $item->categories = db_query('SELECT c.title, c.cid FROM {aggregator_category_item} ci LEFT JOIN {aggregator_category} c ON ci.cid = c.cid WHERE ci.iid = :iid ORDER BY c.title', array(':iid' => $item->id()))->fetchAll();
-    }
-  }
-
-  /**
-   * Overrides Drupal\Core\Entity\DataBaseStorageController::preDelete().
-   */
-  protected function preDelete($entities) {
-    parent::preDelete($entities);
-
-    db_delete('aggregator_category_item')
-      ->condition('iid', array_keys($entities), 'IN')
-      ->execute();
-  }
-
-  /**
-   * Overrides Drupal\Core\Entity\DataBaseStorageController::postSave().
-   */
-  protected function postSave(EntityInterface $entity, $update) {
-    parent::postSave($entity, $update);
-
-    $result = db_query('SELECT cid FROM {aggregator_category_feed} WHERE fid = :fid', array(':fid' => $entity->fid->value));
-    foreach ($result as $category) {
-      db_merge('aggregator_category_item')
-        ->key(array(
-          'iid' => $entity->id(),
-          'cid' => $category->cid,
-        ))
-        ->execute();
-    }
+    $this->loadCategories($queried_entities);
   }
 
   /**
@@ -120,4 +80,36 @@ public function baseFieldDefinitions() {
     return $fields;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function loadCategories($entities) {
+    foreach ($entities as $item) {
+      $item->categories = db_query('SELECT c.title, c.cid FROM {aggregator_category_item} ci LEFT JOIN {aggregator_category} c ON ci.cid = c.cid WHERE ci.iid = :iid ORDER BY c.title', array(':iid' => $item->id()))->fetchAll();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteCategories($entities) {
+    $this->database->delete('aggregator_category_item')
+      ->condition('iid', array_keys($entities))
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function saveCategories(Item $item) {
+    $result = $this->database->query('SELECT cid FROM {aggregator_category_feed} WHERE fid = :fid', array(':fid' => $item->fid->value));
+    foreach ($result as $category) {
+      $this->database->merge('aggregator_category_item')
+        ->key(array(
+          'iid' => $item->id(),
+          'cid' => $category->cid,
+        ))
+        ->execute();
+    }
+  }
 }
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/ItemStorageControllerInterface.php b/core/modules/aggregator/lib/Drupal/aggregator/ItemStorageControllerInterface.php
new file mode 100644
index 0000000..94a6d1c
--- /dev/null
+++ b/core/modules/aggregator/lib/Drupal/aggregator/ItemStorageControllerInterface.php
@@ -0,0 +1,19 @@
+<?php
+/**
+ * @file
+ * Contains
+ */
+
+namespace Drupal\aggregator;
+
+use Drupal\aggregator\Plugin\Core\Entity\Item;
+use Drupal\core\Entity\EntityStorageControllerInterface;
+
+interface ItemStorageControllerInterface extends EntityStorageControllerInterface {
+
+  public function loadCategories($entities);
+
+  public function deleteCategories($entities);
+
+  public function saveCategories(Item $item);
+}
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Core/Entity/Feed.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Core/Entity/Feed.php
index f915caa..79deb00 100644
--- a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Core/Entity/Feed.php
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Core/Entity/Feed.php
@@ -8,6 +8,8 @@
 namespace Drupal\aggregator\Plugin\Core\Entity;
 
 use Drupal\Core\Entity\EntityNG;
+use Symfony\Component\DependencyInjection\Container;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Annotation\Translation;
 use Drupal\aggregator\FeedInterface;
@@ -173,4 +175,69 @@ public function id() {
   public function label($langcode = NULL) {
     return $this->get('title')->value;
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function preCreate(EntityStorageControllerInterface $storage_controller, &$values) {
+    $values += array(
+      'link' => '',
+      'description' => '',
+      'image' => '',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function preDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+        // Invalidate the block cache to update aggregator feed-based derivatives.
+    if (module_exists('block')) {
+      \Drupal::service('plugin.manager.block')->clearCachedDefinitions();
+    }
+    $storage_controller->deleteCategories($entities);
+    foreach ($entities as $entity) {
+      // Notify processors to remove stored items.
+      $manager = \Drupal::service('plugin.manager.aggregator.processor');
+      foreach ($manager->getDefinitions() as $id => $definition) {
+        $manager->createInstance($id)->remove($entity);
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function postDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+    foreach ($entities as $entity) {
+      // Make sure there is no active block for this feed.
+      $block_configs = config_get_storage_names_with_prefix('plugin.core.block');
+      foreach ($block_configs as $config_id) {
+        $config = config($config_id);
+        if ($config->get('id') == 'aggregator_feed_block:' . $entity->id()) {
+          $config->delete();
+        }
+      }
+    }
+  }
+
+  public function preSave(EntityStorageControllerInterface $storage_controller) {
+    $this->clearBlockCacheDefinitions();
+    $storage_controller->deleteCategories(array($this->id() => $this));
+  }
+
+  public function postSave(EntityStorageControllerInterface $storage_controller, $update = FALSE) {
+    if (!empty($this->categories)) {
+      $storage_controller->saveCategories($this, $this->categories);
+    }
+  }
+
+  /**
+   * Invalidate the block cache to update aggregator feed-based derivatives.
+   */
+  protected function clearBlockCacheDefinitions() {
+    if ($block_manager = \Drupal::getContainer()->get('plugin.manager.block', Container::NULL_ON_INVALID_REFERENCE)) {
+      $block_manager->clearCachedDefinitions();
+    }
+  }
 }
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Core/Entity/Item.php b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Core/Entity/Item.php
index 51f150a..1220cdc 100644
--- a/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Core/Entity/Item.php
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Plugin/Core/Entity/Item.php
@@ -8,6 +8,7 @@
 namespace Drupal\aggregator\Plugin\Core\Entity;
 
 use Drupal\Core\Entity\EntityNG;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Annotation\Translation;
 use Drupal\aggregator\ItemInterface;
@@ -130,4 +131,25 @@ public function id() {
   public function label($langcode = NULL) {
     return $this->get('title')->value;
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postCreate(EntityStorageControllerInterface $storage_controller) {
+    $this->timestamp->value = REQUEST_TIME;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
+    $storage_controller->saveCategories($this);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function preDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+    $storage_controller->deleteCategories($entities);
+  }
 }
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php
index 046b065..2e64a7e 100644
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php
@@ -16,33 +16,7 @@
  * This extends the Drupal\Core\Entity\DatabaseStorageController class, adding
  * required special handling for custom block entities.
  */
-class CustomBlockStorageController extends DatabaseStorageControllerNG {
-
-  /**
-   * Overrides \Drupal\Core\Entity\DatabaseStorageController::preSaveRevision().
-   */
-  protected function preSaveRevision(\stdClass $record, EntityInterface $entity) {
-    if ($entity->isNewRevision()) {
-      // When inserting either a new custom block or a new custom_block
-      // revision, $entity->log must be set because {block_custom_revision}.log
-      // is a text column and therefore cannot have a default value. However,
-      // it might not be set at this point (for example, if the user submitting
-      // the form does not have permission to create revisions), so we ensure
-      // that it is at least an empty string in that case.
-      // @todo: Make the {block_custom_revision}.log column nullable so that we
-      // can remove this check.
-      if (!isset($record->log)) {
-        $record->log = '';
-      }
-    }
-    elseif (isset($entity->original) && (!isset($record->log) || $record->log === '')) {
-      // If we are updating an existing custom_block without adding a new
-      // revision, we need to make sure $entity->log is reset whenever it is
-      // empty. Therefore, this code allows us to avoid clobbering an existing
-      // log entry with an empty one.
-      $record->log = $entity->original->log->value;
-    }
-  }
+class CustomBlockStorageController extends DatabaseStorageControllerNG implements CustomBlockStorageControllerInterface {
 
   /**
    * Overrides \Drupal\Core\Entity\DatabaseStorageController::attachLoad().
@@ -63,14 +37,6 @@ protected function attachLoad(&$blocks, $load_revision = FALSE) {
   }
 
   /**
-   * Overrides \Drupal\Core\Entity\DatabaseStorageController::postSave().
-   */
-  protected function postSave(EntityInterface $block, $update) {
-    // Invalidate the block cache to update custom block-based derivatives.
-    drupal_container()->get('plugin.manager.block')->clearCachedDefinitions();
-  }
-
-  /**
    * Implements \Drupal\Core\Entity\DataBaseStorageControllerNG::basePropertyDefinitions().
    */
   public function baseFieldDefinitions() {
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageControllerInterface.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageControllerInterface.php
new file mode 100644
index 0000000..516ac5b
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageControllerInterface.php
@@ -0,0 +1,14 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\CustomBlockStorageControllerInterface.
+ */
+
+namespace Drupal\custom_block;
+
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+
+interface CustomBlockStorageControllerInterface extends EntityStorageControllerInterface {
+
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php
index deabda4..2d867eb 100644
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php
@@ -8,6 +8,7 @@
 namespace Drupal\custom_block\Plugin\Core\Entity;
 
 use Drupal\Core\Entity\EntityNG;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Annotation\Translation;
 use Drupal\custom_block\CustomBlockInterface;
@@ -180,4 +181,39 @@ public function uri() {
       )
     );
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
+    // Invalidate the block cache to update custom block-based derivatives.
+    drupal_container()->get('plugin.manager.block')->clearCachedDefinitions();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record) {
+    if ($this->isNewRevision()) {
+      // When inserting either a new custom block or a new custom_block
+      // revision, $entity->log must be set because {block_custom_revision}.log
+      // is a text column and therefore cannot have a default value. However,
+      // it might not be set at this point (for example, if the user submitting
+      // the form does not have permission to create revisions), so we ensure
+      // that it is at least an empty string in that case.
+      // @todo: Make the {block_custom_revision}.log column nullable so that we
+      // can remove this check.
+      if (!isset($record->log)) {
+        $record->log = '';
+      }
+    }
+    elseif (isset($this->original) && (!isset($record->log) || $record->log === '')) {
+      // If we are updating an existing custom_block without adding a new
+      // revision, we need to make sure $entity->log is reset whenever it is
+      // empty. Therefore, this code allows us to avoid clobbering an existing
+      // log entry with an empty one.
+      $record->log = $this->original->log->value;
+    }
+  }
+
 }
diff --git a/core/modules/block/lib/Drupal/block/BlockStorageController.php b/core/modules/block/lib/Drupal/block/BlockStorageController.php
index d6c547d..747aa87 100644
--- a/core/modules/block/lib/Drupal/block/BlockStorageController.php
+++ b/core/modules/block/lib/Drupal/block/BlockStorageController.php
@@ -26,13 +26,4 @@ public function load(array $ids = NULL) {
     });
   }
 
-  /**
-   * {@inheritdoc}
-   */
-  protected function preSave(EntityInterface $entity) {
-    parent::preSave($entity);
-
-    $entity->set('settings', $entity->getPlugin()->getConfig());
-  }
-
 }
diff --git a/core/modules/block/lib/Drupal/block/Plugin/Core/Entity/Block.php b/core/modules/block/lib/Drupal/block/Plugin/Core/Entity/Block.php
index 3c8b14c..eb9b8a7 100644
--- a/core/modules/block/lib/Drupal/block/Plugin/Core/Entity/Block.php
+++ b/core/modules/block/lib/Drupal/block/Plugin/Core/Entity/Block.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Annotation\Translation;
 use Drupal\block\BlockPluginBag;
 use Drupal\block\BlockInterface;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
 
 /**
  * Defines a Block configuration entity class.
@@ -163,4 +164,11 @@ public function getExportProperties() {
     return $properties;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function preSave(EntityStorageControllerInterface $storage_controller) {
+    $this->set('settings', $this->getPlugin()->getConfig());
+  }
+
 }
diff --git a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php
index eef80c8..85d6f3c 100644
--- a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php
+++ b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php
@@ -18,7 +18,7 @@
  * This extends the Drupal\Core\Entity\DatabaseStorageController class, adding
  * required special handling for comment entities.
  */
-class CommentStorageController extends DatabaseStorageControllerNG {
+class CommentStorageController extends DatabaseStorageControllerNG implements CommentStorageControllerInterface {
   /**
    * The thread for which a lock was acquired.
    */
@@ -52,156 +52,9 @@ protected function attachLoad(&$records, $load_revision = FALSE) {
   }
 
   /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageControllerNG::create().
+   * {@inheritdoc}
    */
-  public function create(array $values) {
-    if (empty($values['node_type']) && !empty($values['nid'])) {
-      $node = node_load(is_object($values['nid']) ? $values['nid']->value : $values['nid']);
-      $values['node_type'] = 'comment_node_' . $node->type;
-    }
-    return parent::create($values);
-  }
-
-  /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::preSave().
-   *
-   * @see comment_int_to_alphadecimal()
-   * @see comment_alphadecimal_to_int()
-   */
-  protected function preSave(EntityInterface $comment) {
-    global $user;
-
-    if (!isset($comment->status->value)) {
-      $comment->status->value = user_access('skip comment approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED;
-    }
-    // Make sure we have a proper bundle name.
-    if (!isset($comment->node_type->value)) {
-      $comment->node_type->value = 'comment_node_' . $comment->nid->entity->type;
-    }
-    if ($comment->isNew()) {
-      // Add the comment to database. This next section builds the thread field.
-      // Also see the documentation for comment_view().
-      if (!empty($comment->thread->value)) {
-        // Allow calling code to set thread itself.
-        $thread = $comment->thread->value;
-      }
-      else {
-        if ($this->threadLock) {
-          // As preSave() is protected, this can only happen when this class
-          // is extended in a faulty manner.
-          throw new LogicException('preSave is called again without calling postSave() or releaseThreadLock()');
-        }
-        if ($comment->pid->target_id == 0) {
-          // This is a comment with no parent comment (depth 0): we start
-          // by retrieving the maximum thread level.
-          $max = db_query('SELECT MAX(thread) FROM {comment} WHERE nid = :nid', array(':nid' => $comment->nid->target_id))->fetchField();
-          // Strip the "/" from the end of the thread.
-          $max = rtrim($max, '/');
-          // We need to get the value at the correct depth.
-          $parts = explode('.', $max);
-          $n = comment_alphadecimal_to_int($parts[0]);
-          $prefix = '';
-        }
-        else {
-          // This is a comment with a parent comment, so increase the part of
-          // the thread value at the proper depth.
-
-          // Get the parent comment:
-          $parent = $comment->pid->entity;
-          // Strip the "/" from the end of the parent thread.
-          $parent->thread->value = (string) rtrim((string) $parent->thread->value, '/');
-          $prefix = $parent->thread->value . '.';
-          // Get the max value in *this* thread.
-          $max = db_query("SELECT MAX(thread) FROM {comment} WHERE thread LIKE :thread AND nid = :nid", array(
-            ':thread' => $parent->thread->value . '.%',
-            ':nid' => $comment->nid->target_id,
-          ))->fetchField();
-
-          if ($max == '') {
-            // First child of this parent. As the other two cases do an
-            // increment of the thread number before creating the thread
-            // string set this to -1 so it requires an increment too.
-            $n = -1;
-          }
-          else {
-            // Strip the "/" at the end of the thread.
-            $max = rtrim($max, '/');
-            // Get the value at the correct depth.
-            $parts = explode('.', $max);
-            $parent_depth = count(explode('.', $parent->thread->value));
-            $n = comment_alphadecimal_to_int($parts[$parent_depth]);
-          }
-        }
-        // Finally, build the thread field for this new comment. To avoid
-        // race conditions, get a lock on the thread. If aother process already
-        // has the lock, just move to the next integer.
-        do {
-          $thread = $prefix . comment_int_to_alphadecimal(++$n) . '/';
-        } while (!lock()->acquire("comment:{$comment->nid->target_id}:$thread"));
-        $this->threadLock = $thread;
-      }
-      if (empty($comment->created->value)) {
-        $comment->created->value = REQUEST_TIME;
-      }
-      if (empty($comment->changed->value)) {
-        $comment->changed->value = $comment->created->value;
-      }
-      // We test the value with '===' because we need to modify anonymous
-      // users as well.
-      if ($comment->uid->target_id === $user->uid && $user->uid) {
-        $comment->name->value = $user->name;
-      }
-      // Add the values which aren't passed into the function.
-      $comment->thread->value = $thread;
-      $comment->hostname->value = \Drupal::request()->getClientIP();
-    }
-  }
-
-  /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave().
-   */
-  protected function postSave(EntityInterface $comment, $update) {
-    $this->releaseThreadLock();
-    // Update the {node_comment_statistics} table prior to executing the hook.
-    $this->updateNodeStatistics($comment->nid->target_id);
-    if ($comment->status->value == COMMENT_PUBLISHED) {
-      module_invoke_all('comment_publish', $comment);
-    }
-  }
-
-  /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::postDelete().
-   */
-  protected function postDelete($comments) {
-    // Delete the comments' replies.
-    $query = db_select('comment', 'c')
-      ->fields('c', array('cid'))
-      ->condition('pid', array(array_keys($comments)), 'IN');
-    $child_cids = $query->execute()->fetchCol();
-    comment_delete_multiple($child_cids);
-
-    foreach ($comments as $comment) {
-      $this->updateNodeStatistics($comment->nid->target_id);
-    }
-  }
-
-  /**
-   * Updates the comment statistics for a given node.
-   *
-   * The {node_comment_statistics} table has the following fields:
-   * - last_comment_timestamp: The timestamp of the last comment for this node,
-   *   or the node created timestamp if no comments exist for the node.
-   * - last_comment_name: The name of the anonymous poster for the last comment.
-   * - last_comment_uid: The user ID of the poster for the last comment for
-   *   this node, or the node author's user ID if no comments exist for the
-   *   node.
-   * - comment_count: The total number of approved/published comments on this
-   *   node.
-   *
-   * @param $nid
-   *   The node ID.
-   */
-  protected function updateNodeStatistics($nid) {
+  public function updateNodeStatistics($nid) {
     // Allow bulk updates and inserts to temporarily disable the
     // maintenance of the {node_comment_statistics} table.
     if (!variable_get('comment_maintain_node_statistics', TRUE)) {
@@ -247,16 +100,6 @@ protected function updateNodeStatistics($nid) {
   }
 
   /**
-   * Release the lock acquired for the thread in preSave().
-   */
-  protected function releaseThreadLock() {
-    if ($this->threadLock) {
-      lock()->release($this->threadLock);
-      $this->threadLock = '';
-    }
-  }
-
-  /**
    * Implements \Drupal\Core\Entity\DataBaseStorageControllerNG::basePropertyDefinitions().
    */
   public function baseFieldDefinitions() {
@@ -356,4 +199,23 @@ public function baseFieldDefinitions() {
     );
     return $properties;
   }
+
+  public function getMaxThread(EntityInterface $comment) {
+    return db_query('SELECT MAX(thread) FROM {comment} WHERE nid = :nid', array(':nid' => $comment->nid->target_id))->fetchField();
+  }
+
+  public function getMaxThreadPerThread(EntityInterface $comment) {
+    return $this->database->query("SELECT MAX(thread) FROM {comment} WHERE thread LIKE :thread AND nid = :nid", array(
+      ':thread' => rtrim($comment->pid->entity->thread->value, '/') . '.%',
+      ':nid' => $comment->nid->target_id,
+    ))->fetchField();
+  }
+
+  public function getChildCids(array $comments) {
+    return $this->database->select('comment', 'c')
+      ->fields('c', array('cid'))
+      ->condition('pid', array(array_keys($comments)))
+      ->execute()
+      ->fetchCol();
+  }
 }
diff --git a/core/modules/comment/lib/Drupal/comment/CommentStorageControllerInterface.php b/core/modules/comment/lib/Drupal/comment/CommentStorageControllerInterface.php
new file mode 100644
index 0000000..2b1708f
--- /dev/null
+++ b/core/modules/comment/lib/Drupal/comment/CommentStorageControllerInterface.php
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * 
+ * Developed by Gabor Szanto.
+ *  hello@szantogabor.com
+ *  http://szantogabor.com
+ */
+
+namespace Drupal\comment;
+
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+
+interface CommentStorageControllerInterface extends EntityStorageControllerInterface {
+
+  public function getMaxThread(EntityInterface $comment);
+
+  public function getMaxThreadPerThread(EntityInterface $comment);
+
+  public function getChildCids(array $comments);
+
+  /**
+   * Updates the comment statistics for a given node.
+   *
+   * The {node_comment_statistics} table has the following fields:
+   * - last_comment_timestamp: The timestamp of the last comment for this node,
+   *   or the node created timestamp if no comments exist for the node.
+   * - last_comment_name: The name of the anonymous poster for the last comment.
+   * - last_comment_uid: The user ID of the poster for the last comment for
+   *   this node, or the node author's user ID if no comments exist for the
+   *   node.
+   * - comment_count: The total number of approved/published comments on this
+   *   node.
+   *
+   * @param $nid
+   *   The node ID.
+   */
+  public function updateNodeStatistics($nid);
+
+}
diff --git a/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php b/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php
index 55a1bd3..9bebb4f 100644
--- a/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php
+++ b/core/modules/comment/lib/Drupal/comment/Plugin/Core/Entity/Comment.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Annotation\Translation;
 use Drupal\comment\CommentInterface;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Language\Language;
 
 /**
@@ -219,4 +220,132 @@ protected function init() {
   public function id() {
     return $this->get('cid')->value;
   }
+
+  public static function preCreate(EntityStorageControllerInterface $storage_controller, &$values) {
+    if (empty($values['node_type']) && !empty($values['nid'])) {
+      $node = node_load(is_object($values['nid']) ? $values['nid']->value : $values['nid']);
+      $values['node_type'] = 'comment_node_' . $node->type;
+    }
+  }
+
+  public function preSave(EntityStorageControllerInterface $storage_controller) {
+    global $user;
+
+    if (!isset($this->status->value)) {
+      $this->status->value = user_access('skip comment approval') ? COMMENT_PUBLISHED : COMMENT_NOT_PUBLISHED;
+    }
+    // Make sure we have a proper bundle name.
+    if (!isset($this->node_type->value)) {
+      $this->node_type->value = 'comment_node_' . $this->nid->entity->type;
+    }
+    if ($this->isNew()) {
+      // Add the comment to database. This next section builds the thread field.
+      // Also see the documentation for comment_view().
+      if (!empty($this->thread->value)) {
+        // Allow calling code to set thread itself.
+        $thread = $this->thread->value;
+      }
+      else {
+        if ($this->threadLock) {
+          // As preSave() is protected, this can only happen when this class
+          // is extended in a faulty manner.
+          throw new \LogicException('preSave is called again without calling postSave() or releaseThreadLock()');
+        }
+        if ($this->pid->target_id == 0) {
+          // This is a comment with no parent comment (depth 0): we start
+          // by retrieving the maximum thread level.
+          $max = $storage_controller->getMaxThread($this);
+          // Strip the "/" from the end of the thread.
+          $max = rtrim($max, '/');
+          // We need to get the value at the correct depth.
+          $parts = explode('.', $max);
+          $n = comment_alphadecimal_to_int($parts[0]);
+          $prefix = '';
+        }
+        else {
+          // This is a comment with a parent comment, so increase the part of
+          // the thread value at the proper depth.
+
+          // Get the parent comment:
+          $parent = $this->pid->entity;
+          // Strip the "/" from the end of the parent thread.
+          $parent->thread->value = (string) rtrim((string) $parent->thread->value, '/');
+          $prefix = $parent->thread->value . '.';
+          // Get the max value in *this* thread.
+          $max = $storage_controller->getMaxThreadPerThread($this);
+
+          if ($max == '') {
+            // First child of this parent. As the other two cases do an
+            // increment of the thread number before creating the thread
+            // string set this to -1 so it requires an increment too.
+            $n = -1;
+          }
+          else {
+            // Strip the "/" at the end of the thread.
+            $max = rtrim($max, '/');
+            // Get the value at the correct depth.
+            $parts = explode('.', $max);
+            $parent_depth = count(explode('.', $parent->thread->value));
+            $n = comment_alphadecimal_to_int($parts[$parent_depth]);
+          }
+        }
+        // Finally, build the thread field for this new comment. To avoid
+        // race conditions, get a lock on the thread. If aother process already
+        // has the lock, just move to the next integer.
+        do {
+          $thread = $prefix . comment_int_to_alphadecimal(++$n) . '/';
+        } while (!lock()->acquire("comment:{$this->nid->target_id}:$thread"));
+        $this->threadLock = $thread;
+      }
+      if (empty($this->created->value)) {
+        $this->created->value = REQUEST_TIME;
+      }
+      if (empty($this->changed->value)) {
+        $this->changed->value = $this->created->value;
+      }
+      // We test the value with '===' because we need to modify anonymous
+      // users as well.
+      if ($this->uid->target_id === $user->uid && $user->uid) {
+        $this->name->value = $user->name;
+      }
+      // Add the values which aren't passed into the function.
+      $this->thread->value = $thread;
+      $this->hostname->value = \Drupal::request()->getClientIP();
+    }
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave().
+   */
+  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
+    $this->releaseThreadLock();
+    // Update the {node_comment_statistics} table prior to executing the hook.
+    $storage_controller->updateNodeStatistics($this->nid->target_id);
+    if ($this->status->value == COMMENT_PUBLISHED) {
+      module_invoke_all('comment_publish', $this);
+    }
+  }
+
+  /**
+   * Release the lock acquired for the thread in preSave().
+   */
+  protected function releaseThreadLock() {
+    if ($this->threadLock) {
+      lock()->release($this->threadLock);
+      $this->threadLock = '';
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function postDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+    $children = $storage_controller->getChildCids($entities);
+    comment_delete_multiple($children);
+
+    foreach ($entities as $id => $entity) {
+      $storage_controller->updateNodeStatistics($entity->nid->target_id);
+    }
+  }
+
 }
diff --git a/core/modules/contact/lib/Drupal/contact/CategoryStorageController.php b/core/modules/contact/lib/Drupal/contact/CategoryStorageController.php
index 9350613..90b74db 100644
--- a/core/modules/contact/lib/Drupal/contact/CategoryStorageController.php
+++ b/core/modules/contact/lib/Drupal/contact/CategoryStorageController.php
@@ -8,36 +8,10 @@
 namespace Drupal\contact;
 
 use Drupal\Core\Config\Entity\ConfigStorageController;
-use Drupal\Core\Entity\EntityInterface;
 
 /**
  * Controller class for contact categories.
  */
 class CategoryStorageController extends ConfigStorageController {
 
-  /**
-   * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::postSave().
-   */
-  protected function postSave(EntityInterface $entity, $update) {
-    parent::postSave($entity, $update);
-
-    if (!$update) {
-      entity_invoke_bundle_hook('create', 'contact_message', $entity->id());
-    }
-    elseif ($entity->original->id() != $entity->id()) {
-      entity_invoke_bundle_hook('rename', 'contact_message', $entity->original->id(), $entity->id());
-    }
-  }
-
-  /**
-   * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::postDelete().
-   */
-  protected function postDelete($entities) {
-    parent::postDelete($entities);
-
-    foreach ($entities as $entity) {
-      entity_invoke_bundle_hook('delete', 'contact_message', $entity->id());
-    }
-  }
-
 }
diff --git a/core/modules/contact/lib/Drupal/contact/Plugin/Core/Entity/Category.php b/core/modules/contact/lib/Drupal/contact/Plugin/Core/Entity/Category.php
index a224d1a..abeadb3 100644
--- a/core/modules/contact/lib/Drupal/contact/Plugin/Core/Entity/Category.php
+++ b/core/modules/contact/lib/Drupal/contact/Plugin/Core/Entity/Category.php
@@ -8,6 +8,7 @@
 namespace Drupal\contact\Plugin\Core\Entity;
 
 use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Annotation\Translation;
 use Drupal\contact\CategoryInterface;
@@ -80,4 +81,25 @@ class Category extends ConfigEntityBase implements CategoryInterface {
    */
   public $weight = 0;
 
+  /**
+   * {@inheritdoc}
+   */
+  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
+    if (!$update) {
+      entity_invoke_bundle_hook('create', 'contact_message', $this->id());
+    }
+    elseif ($this->original->id() != $this->id()) {
+      entity_invoke_bundle_hook('rename', 'contact_message', $this->original->id(), $this->id());
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function postDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+    foreach ($entities as $entity) {
+      entity_invoke_bundle_hook('delete', 'contact_message', $entity->id());
+    }
+  }
+
 }
diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/TestEntity.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/TestEntity.php
index 112ba6c..1d8d9d0 100644
--- a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/TestEntity.php
+++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/Plugin/Core/Entity/TestEntity.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Entity\Entity;
 use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
 
 /**
  * Test entity class.
@@ -19,7 +20,7 @@
  *   label = @Translation("Test Entity"),
  *   module = "field_test",
  *   controllers = {
- *     "storage" = "Drupal\field_test\TestEntityController",
+ *     "storage" = "Drupal\Core\Entity\DatabaseStorageController",
  *     "render" = "Drupal\Core\Entity\EntityRenderController",
  *     "form" = {
  *       "default" = "Drupal\field_test\TestEntityFormController"
@@ -86,5 +87,16 @@ public function getRevisionId() {
   public function bundle() {
     return !empty($this->fttype) ? $this->fttype : $this->entityType();
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record) {
+    // Allow for predefined revision ids.
+    if (!empty($record->use_provided_revision_id)) {
+      $record->ftvid = $record->use_provided_revision_id;
+    }
+  }
+
 }
 
diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityController.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityController.php
deleted file mode 100644
index 0fe7602..0000000
--- a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityController.php
+++ /dev/null
@@ -1,28 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\field_test\TestEntityController.
- */
-
-namespace Drupal\field_test;
-
-use Drupal\Core\Entity\DatabaseStorageController;
-use Drupal\Core\Entity\EntityInterface;
-
-/**
- * Controller class for the test entity entity types.
- */
-class TestEntityController extends DatabaseStorageController {
-
-  /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::preSaveRevision().
-   */
-  public function preSaveRevision(\stdClass $record, EntityInterface $entity) {
-    // Allow for predefined revision ids.
-    if (!empty($record->use_provided_revision_id)) {
-      $record->ftvid = $record->use_provided_revision_id;
-    }
-  }
-
-}
diff --git a/core/modules/file/lib/Drupal/file/FileStorageController.php b/core/modules/file/lib/Drupal/file/FileStorageController.php
index d3ca601..aa4f8e7 100644
--- a/core/modules/file/lib/Drupal/file/FileStorageController.php
+++ b/core/modules/file/lib/Drupal/file/FileStorageController.php
@@ -8,73 +8,14 @@
 namespace Drupal\file;
 
 use Drupal\Core\Entity\DatabaseStorageController;
-use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Language\Language;
 
 /**
  * File storage controller for files.
  */
-class FileStorageController extends DatabaseStorageController {
+class FileStorageController extends DatabaseStorageController implements FileStorageControllerInterface {
 
   /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::create().
-   */
-  public function create(array $values) {
-    // Automatically detect filename if not set.
-    if (!isset($values['filename']) && isset($values['uri'])) {
-      $values['filename'] = drupal_basename($values['uri']);
-    }
-
-    // Automatically detect filemime if not set.
-    if (!isset($values['filemime']) && isset($values['uri'])) {
-      $values['filemime'] = file_get_mimetype($values['uri']);
-    }
-    return parent::create($values);
-  }
-
-  /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::presave().
-   */
-  protected function preSave(EntityInterface $entity) {
-    $entity->timestamp = REQUEST_TIME;
-    $entity->filesize = filesize($entity->uri);
-    if (!isset($entity->langcode)) {
-      // Default the file's language code to none, because files are language
-      // neutral more often than language dependent. Until we have better
-      // flexible settings.
-      // @todo See http://drupal.org/node/258785 and followups.
-      $entity->langcode = Language::LANGCODE_NOT_SPECIFIED;
-    }
-  }
-
-  /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::preDelete().
-   */
-  public function preDelete($entities) {
-    foreach ($entities as $entity) {
-      // Delete the actual file. Failures due to invalid files and files that
-      // were already deleted are logged to watchdog but ignored, the
-      // corresponding file entity will be deleted.
-      file_unmanaged_delete($entity->uri);
-    }
-    // Delete corresponding file usage entries.
-    db_delete('file_usage')
-      ->condition('fid', array_keys($entities), 'IN')
-      ->execute();
-  }
-
-  /**
-   * Determines total disk space used by a single user or the whole filesystem.
-   *
-   * @param int $uid
-   *   Optional. A user id, specifying NULL returns the total space used by all
-   *   non-temporary files.
-   * @param $status
-   *   Optional. The file status to consider. The default is to only
-   *   consider files in status FILE_STATUS_PERMANENT.
-   *
-   * @return int
-   *   An integer containing the number of bytes used.
+   * {@inheritdoc}
    */
   public function spaceUsed($uid = NULL, $status = FILE_STATUS_PERMANENT) {
     $query = db_select($this->entityInfo['base_table'], 'f')
@@ -87,10 +28,7 @@ public function spaceUsed($uid = NULL, $status = FILE_STATUS_PERMANENT) {
   }
 
   /**
-   * Retrieve temporary files that are older than DRUPAL_MAXIMUM_TEMP_FILE_AGE.
-   *
-   *  @return
-   *    A list of files to be deleted.
+   * {@inheritdoc}
    */
   public function retrieveTemporaryFiles() {
     // Use separate placeholders for the status to avoid a bug in some versions
diff --git a/core/modules/file/lib/Drupal/file/FileStorageControllerInterface.php b/core/modules/file/lib/Drupal/file/FileStorageControllerInterface.php
new file mode 100644
index 0000000..6b0b730
--- /dev/null
+++ b/core/modules/file/lib/Drupal/file/FileStorageControllerInterface.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\file\FileStorageControllerInterface .
+ */
+
+namespace Drupal\file;
+
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+
+interface FileStorageControllerInterface extends EntityStorageControllerInterface {
+
+  /**
+   * Determines total disk space used by a single user or the whole filesystem.
+   *
+   * @param int $uid
+   *   Optional. A user id, specifying NULL returns the total space used by all
+   *   non-temporary files.
+   * @param $status
+   *   Optional. The file status to consider. The default is to only
+   *   consider files in status FILE_STATUS_PERMANENT.
+   *
+   * @return int
+   *   An integer containing the number of bytes used.
+   */
+  public function spaceUsed($uid = NULL, $status = FILE_STATUS_PERMANENT);
+
+  /**
+   * Retrieve temporary files that are older than DRUPAL_MAXIMUM_TEMP_FILE_AGE.
+   *
+   *  @return
+   *    A list of files to be deleted.
+   */
+  public function retrieveTemporaryFiles();
+
+}
diff --git a/core/modules/file/lib/Drupal/file/Plugin/Core/Entity/File.php b/core/modules/file/lib/Drupal/file/Plugin/Core/Entity/File.php
index 1390cc7..077dde7 100644
--- a/core/modules/file/lib/Drupal/file/Plugin/Core/Entity/File.php
+++ b/core/modules/file/lib/Drupal/file/Plugin/Core/Entity/File.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Entity\Entity;
 use Drupal\Core\Entity\Annotation\EntityType;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Annotation\Translation;
 use Drupal\Core\Language\Language;
 use Drupal\file\FileInterface;
@@ -118,4 +119,44 @@ public function id() {
     return $this->fid;
   }
 
+  public static function preCreate(EntityStorageControllerInterface $storage_controller, &$values) {
+    // Automatically detect filename if not set.
+    if (!isset($values['filename']) && isset($values['uri'])) {
+      $values['filename'] = drupal_basename($values['uri']);
+    }
+
+    // Automatically detect filemime if not set.
+    if (!isset($values['filemime']) && isset($values['uri'])) {
+      $values['filemime'] = file_get_mimetype($values['uri']);
+    }
+  }
+
+  public function preSave(EntityStorageControllerInterface $storage_controller) {
+    $this->timestamp = REQUEST_TIME;
+    $this->filesize = filesize($this->uri);
+    if (!isset($this->langcode)) {
+      // Default the file's language code to none, because files are language
+      // neutral more often than language dependent. Until we have better
+      // flexible settings.
+      // @todo See http://drupal.org/node/258785 and followups.
+      $this->langcode = Language::LANGCODE_NOT_SPECIFIED;
+    }
+  }
+
+  public static function preDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+    foreach ($entities as $entity) {
+      // Delete all remaining references to this file.
+      $file_usage = file_usage()->listUsage($entity);
+      if (!empty($file_usage)) {
+        foreach ($file_usage as $module => $usage) {
+          file_usage()->delete($entity, $module);
+        }
+      }
+      // Delete the actual file. Failures due to invalid files and files that
+      // were already deleted are logged to watchdog but ignored, the
+      // corresponding file entity will be deleted.
+      file_unmanaged_delete($entity->uri);
+    }
+  }
+
 }
diff --git a/core/modules/filter/lib/Drupal/filter/FilterFormatStorageController.php b/core/modules/filter/lib/Drupal/filter/FilterFormatStorageController.php
deleted file mode 100644
index 4c19d8b..0000000
--- a/core/modules/filter/lib/Drupal/filter/FilterFormatStorageController.php
+++ /dev/null
@@ -1,69 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\filter\FilterFormatStorageController.
- */
-
-namespace Drupal\filter;
-
-use Drupal\Core\Config\Entity\ConfigStorageController;
-use Drupal\Core\Entity\EntityInterface;
-
-/**
- * Defines the storage controller class for Filter Format entities.
- */
-class FilterFormatStorageController extends ConfigStorageController {
-
-  /**
-   * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::preSave().
-   */
-  protected function preSave(EntityInterface $entity) {
-    parent::preSave($entity);
-
-    $entity->name = trim($entity->label());
-
-    // @todo Do not save disabled filters whose properties are identical to
-    //   all default properties.
-
-    // Determine whether the format can be cached.
-    // @todo This is a derived/computed definition, not configuration.
-    $entity->cache = TRUE;
-    foreach ($entity->filters() as $filter) {
-      if ($filter->status && !$filter->cache) {
-        $entity->cache = FALSE;
-      }
-    }
-  }
-
-  /**
-   * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::postSave().
-   */
-  protected function postSave(EntityInterface $entity, $update) {
-    parent::postSave($entity, $update);
-
-    // Clear the static caches of filter_formats() and others.
-    filter_formats_reset();
-
-    if ($update) {
-      // Clear the filter cache whenever a text format is updated.
-      cache('filter')->deleteTags(array('filter_format' => $entity->id()));
-    }
-    else {
-      // Default configuration of modules and installation profiles is allowed
-      // to specify a list of user roles to grant access to for the new format;
-      // apply the defined user role permissions when a new format is inserted
-      // and has a non-empty $roles property.
-      // Note: user_role_change_permissions() triggers a call chain back into
-      // filter_permission() and lastly filter_formats(), so its cache must be
-      // reset upfront.
-      if (($roles = $entity->get('roles')) && $permission = filter_permission_name($entity)) {
-        foreach (user_roles() as $rid => $name) {
-          $enabled = in_array($rid, $roles, TRUE);
-          user_role_change_permissions($rid, array($permission => $enabled));
-        }
-      }
-    }
-  }
-
-}
diff --git a/core/modules/filter/lib/Drupal/filter/Plugin/Core/Entity/FilterFormat.php b/core/modules/filter/lib/Drupal/filter/Plugin/Core/Entity/FilterFormat.php
index d6b1fe6..09b4df9 100644
--- a/core/modules/filter/lib/Drupal/filter/Plugin/Core/Entity/FilterFormat.php
+++ b/core/modules/filter/lib/Drupal/filter/Plugin/Core/Entity/FilterFormat.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\filter\FilterFormatInterface;
 use Drupal\filter\FilterBag;
 
@@ -21,7 +22,7 @@
  *   label = @Translation("Text format"),
  *   module = "filter",
  *   controllers = {
- *     "storage" = "Drupal\filter\FilterFormatStorageController"
+ *     "storage" = "Drupal\Core\Config\Entity\ConfigStorageController"
  *   },
  *   config_prefix = "filter.format",
  *   entity_keys = {
@@ -179,4 +180,50 @@ public function disable() {
     return $this;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function preSave(EntityStorageControllerInterface $storage_controller) {
+    $this->name = trim($this->label());
+
+    // @todo Do not save disabled filters whose properties are identical to
+    //   all default properties.
+
+    // Determine whether the format can be cached.
+    // @todo This is a derived/computed definition, not configuration.
+    $this->cache = TRUE;
+    foreach ($this->filters() as $filter) {
+      if ($filter->status && !$filter->cache) {
+        $this->cache = FALSE;
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
+    // Clear the static caches of filter_formats() and others.
+    filter_formats_reset();
+
+    if ($update) {
+      // Clear the filter cache whenever a text format is updated.
+      cache('filter')->deleteTags(array('filter_format' => $this->id()));
+    }
+    else {
+      // Default configuration of modules and installation profiles is allowed
+      // to specify a list of user roles to grant access to for the new format;
+      // apply the defined user role permissions when a new format is inserted
+      // and has a non-empty $roles property.
+      // Note: user_role_change_permissions() triggers a call chain back into
+      // filter_permission() and lastly filter_formats(), so its cache must be
+      // reset upfront.
+      if (($roles = $this->get('roles')) && $permission = filter_permission_name($this)) {
+        foreach (user_roles() as $rid => $name) {
+          $enabled = in_array($rid, $roles, TRUE);
+          user_role_change_permissions($rid, array($permission => $enabled));
+        }
+      }
+    }
+  }
 }
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 3d74bde..c185dc4 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkInterface.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkInterface.php
@@ -9,6 +9,8 @@
 
 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.
@@ -53,4 +55,44 @@ public function reset();
    */
   public static function buildFromRouterItem(array $item);
 
+  /**
+   * Returns the route_name matching a URL.
+   *
+   * @param string $link_path
+   *   The link path to find a route name for.
+   *
+   * @return string
+   *   The route name.
+   */
+  public static function findRouteName($link_path);
+
+  /**
+   * Sets the p1 through p9 properties for a menu link entity being saved.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $parent
+   *   A menu link entity.
+   */
+  public function setParents(EntityInterface $parent);
+
+  /**
+   * Finds a possible parent for a given menu link entity.
+   *
+   * Because the parent of a given link might not exist anymore in the database,
+   * we apply a set of heuristics to determine a proper parent:
+   *
+   *  - use the passed parent link if specified and existing.
+   *  - else, use the first existing link down the previous link hierarchy
+   *  - else, for system menu links (derived from hook_menu()), reparent
+   *    based on the path hierarchy.
+   *
+   * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage_controller
+   *   Storage controller object.
+   * @param array $parent_candidates
+   *   An array of menu link entities keyed by mlid.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface|false
+   *   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());
 }
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 6a3eddb..ca0ac05 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
@@ -13,7 +13,6 @@
 use Drupal\Core\Database\Connection;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Cmf\Component\Routing\RouteProviderInterface;
-use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Controller class for menu links.
@@ -21,7 +20,7 @@
  * This extends the Drupal\entity\DatabaseStorageController class, adding
  * required special handling for menu_link entities.
  */
-class MenuLinkStorageController extends DatabaseStorageController {
+class MenuLinkStorageController extends DatabaseStorageController implements MenuLinkStorageControllerInterface {
 
   /**
    * Indicates whether the delete operation should re-parent children items.
@@ -132,6 +131,8 @@ protected function attachLoad(&$menu_links, $load_revision = FALSE) {
    * 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.
@@ -153,7 +154,7 @@ public function save(EntityInterface $entity) {
       // 'presave' hook first because we want to allow modules to alter the
       // entity before all the logic from our preSave() method.
       $this->invokeHook('presave', $entity);
-      $this->preSave($entity);
+      $entity->preSave($this);
 
       // If every value in $entity->original is the same in the $entity, there
       // is no reason to run the update queries or clear the caches. We use
@@ -166,7 +167,7 @@ public function save(EntityInterface $entity) {
         if ($return) {
           if (!$entity->isNew()) {
             $this->resetCache(array($entity->{$this->idKey}));
-            $this->postSave($entity, TRUE);
+            $entity->postSave($this, TRUE);
             $this->invokeHook('update', $entity);
           }
           else {
@@ -174,7 +175,7 @@ public function save(EntityInterface $entity) {
             $this->resetCache();
 
             $entity->enforceIsNew(FALSE);
-            $this->postSave($entity, FALSE);
+            $entity->postSave($this, FALSE);
             $this->invokeHook('insert', $entity);
           }
         }
@@ -194,178 +195,21 @@ public function save(EntityInterface $entity) {
   }
 
   /**
-   * Overrides DatabaseStorageController::preSave().
+   * {@inheritdoc}
    */
-  protected function preSave(EntityInterface $entity) {
-    // 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.
-    $entity->external = (url_is_external($entity->link_path) || $entity->link_path == '<front>') ? 1 : 0;
-
-    // Try to find a parent link. If found, assign it and derive its menu.
-    $parent_candidates = !empty($entity->parentCandidates) ? $entity->parentCandidates : array();
-    $parent = $this->findParent($entity, $parent_candidates);
-    if ($parent) {
-      $entity->plid = $parent->id();
-      $entity->menu_name = $parent->menu_name;
-    }
-    // If no corresponding parent link was found, move the link to the top-level.
-    else {
-      $entity->plid = 0;
-    }
-
-    // Directly fill parents for top-level links.
-    if ($entity->plid == 0) {
-      $entity->p1 = $entity->id();
-      for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) {
-        $parent_property = "p$i";
-        $entity->$parent_property = 0;
-      }
-      $entity->depth = 1;
-    }
-    // Otherwise, ensure that this link's depth is not beyond the maximum depth
-    // and fill parents based on the parent link.
-    else {
-      if ($entity->has_children && $entity->original) {
-        $limit = MENU_MAX_DEPTH - $this->findChildrenRelativeDepth($entity->original) - 1;
-      }
-      else {
-        $limit = MENU_MAX_DEPTH - 1;
-      }
-      if ($parent->depth > $limit) {
-        return FALSE;
-      }
-      $entity->depth = $parent->depth + 1;
-      $this->setParents($entity, $parent);
-    }
-
-    // Need to check both plid and menu_name, since plid can be 0 in any menu.
-    if (isset($entity->original) && ($entity->plid != $entity->original->plid || $entity->menu_name != $entity->original->menu_name)) {
-      $this->moveChildren($entity, $entity->original);
-    }
-    // Find the router_path.
-    if (empty($entity->router_path) || empty($entity->original) || (isset($entity->original) && $entity->original->link_path != $entity->link_path)) {
-      if ($entity->external) {
-        $entity->router_path = '';
-      }
-      else {
-        // Find the router path which will serve this path.
-        $entity->parts = explode('/', $entity->link_path, MENU_MAX_PARTS);
-        $entity->router_path = _menu_find_router_path($entity->link_path);
-      }
-    }
-    // Find the route_name.
-    if (!isset($entity->route_name)) {
-      $entity->route_name = $this->findRouteName($entity->link_path);
-    }
-  }
-
-  /**
-   * Returns the route_name matching a URL.
-   *
-   * @param string $link_path
-   *   The link path to find a route name for.
-   *
-   * @return string
-   *   The route name.
-   */
-  protected function findRouteName($link_path) {
-    // Look up the route_name used for the given path.
-    $request = Request::create('/' . $link_path);
-    $request->attributes->set('system_path', $link_path);
-    try {
-      // Use router.dynamic instead of router, because router will call the
-      // legacy router which will call hook_menu() and you will get back to
-      // this method.
-      $result = \Drupal::service('router.dynamic')->matchRequest($request);
-      return isset($result['_route']) ? $result['_route'] : '';
-    }
-    catch (\Exception $e) {
-      return '';
-    }
-
-  }
-
-  /**
-   * DatabaseStorageController::postSave().
-   */
-  function postSave(EntityInterface $entity, $update) {
-    // Check the has_children status of the parent.
-    $this->updateParentalStatus($entity);
-
-    menu_cache_clear($entity->menu_name);
-    if (isset($entity->original) && $entity->menu_name != $entity->original->menu_name) {
-      menu_cache_clear($entity->original->menu_name);
-    }
-
-    // Now clear the cache.
-    _menu_clear_page_cache();
-  }
-
-  /**
-   * Sets an internal flag that allows us to prevent the reparenting operations
-   * executed during deletion.
-   *
-   * @param bool $value
-   */
-  public function preventReparenting($value = FALSE) {
+  public function setPreventReparenting($value = FALSE) {
     $this->preventReparenting = $value;
   }
 
   /**
-   * Overrides DatabaseStorageController::preDelete().
+   * {@inheritdoc}
    */
-  protected function preDelete($entities) {
-    // Nothing to do if we don't want to reparent children.
-    if ($this->preventReparenting) {
-      return;
-    }
-
-    foreach ($entities as $entity) {
-      // Children get re-attached to the item's parent.
-      if ($entity->has_children) {
-        $children = $this->loadByProperties(array('plid' => $entity->plid));
-        foreach ($children as $child) {
-          $child->plid = $entity->plid;
-          $this->save($child);
-        }
-      }
-    }
+  public function getPreventReparenting() {
+    return $this->preventReparenting;
   }
 
   /**
-   * Overrides DatabaseStorageController::postDelete().
-   */
-  protected function postDelete($entities) {
-    $affected_menus = array();
-    // Update the has_children status of the parent.
-    foreach ($entities as $entity) {
-      if (!$this->preventReparenting) {
-        $this->updateParentalStatus($entity);
-      }
-
-      // 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;
-      }
-    }
-
-    foreach ($affected_menus as $menu_name) {
-      menu_cache_clear($menu_name);
-    }
-    _menu_clear_page_cache();
-  }
-
-  /**
-   * Loads updated and customized menu links for specific router paths.
-   *
-   * Note that this is a low-level method and it doesn't return fully populated
-   * menu link entities. (e.g. no fields are attached)
-   *
-   * @param array $router_paths
-   *   An array of router paths.
-   *
-   * @return array
-   *   An array of menu link objects indexed by their ids.
+   * {@inheritdoc}
    */
   public function loadUpdatedCustomized(array $router_paths) {
     $query = parent::buildQuery(NULL);
@@ -391,10 +235,7 @@ public function loadUpdatedCustomized(array $router_paths) {
   }
 
   /**
-   * Loads system menu link as needed by system_get_module_admin_tasks().
-   *
-   * @return array
-   *   An array of menu link entities indexed by their IDs.
+   * {@inheritdoc}
    */
   public function loadModuleAdminTasks() {
     $query = $this->buildQuery(NULL);
@@ -410,12 +251,9 @@ public function loadModuleAdminTasks() {
   }
 
   /**
-   * Checks and updates the 'has_children' property for the parent of a link.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   A menu link entity.
+   * {@inheritdoc}
    */
-  protected function updateParentalStatus(EntityInterface $entity, $exclude = FALSE) {
+  public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE) {
     // If plid == 0, there is nothing to update.
     if ($entity->plid) {
       // Check if at least one visible child exists in the table.
@@ -439,125 +277,7 @@ protected function updateParentalStatus(EntityInterface $entity, $exclude = FALS
   }
 
   /**
-   * Finds a possible parent for a given menu link entity.
-   *
-   * Because the parent of a given link might not exist anymore in the database,
-   * we apply a set of heuristics to determine a proper parent:
-   *
-   *  - use the passed parent link if specified and existing.
-   *  - else, use the first existing link down the previous link hierarchy
-   *  - else, for system menu links (derived from hook_menu()), reparent
-   *    based on the path hierarchy.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   A menu link entity.
-   * @param array $parent_candidates
-   *   An array of menu link entities keyed by mlid.
-   *
-   * @return \Drupal\Core\Entity\EntityInterface|false
-   *   A menu link entity structure of the possible parent or FALSE if no valid
-   *   parent has been found.
-   */
-  protected function findParent(EntityInterface $entity, array $parent_candidates = array()) {
-    $parent = FALSE;
-
-    // This item is explicitely top-level, skip the rest of the parenting.
-    if (isset($entity->plid) && empty($entity->plid)) {
-      return $parent;
-    }
-
-    // If we have a parent link ID, try to use that.
-    $candidates = array();
-    if (isset($entity->plid)) {
-      $candidates[] = $entity->plid;
-    }
-
-    // Else, if we have a link hierarchy try to find a valid parent in there.
-    if (!empty($entity->depth) && $entity->depth > 1) {
-      for ($depth = $entity->depth - 1; $depth >= 1; $depth--) {
-        $parent_property = "p$depth";
-        $candidates[] = $entity->$parent_property;
-      }
-    }
-
-    foreach ($candidates as $mlid) {
-      if (isset($parent_candidates[$mlid])) {
-        $parent = $parent_candidates[$mlid];
-      }
-      else {
-        $parent = $this->load(array($mlid));
-        $parent = reset($parent);
-      }
-      if ($parent) {
-        return $parent;
-      }
-    }
-
-    // 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 ($entity->module == 'system') {
-      // Find the parent - it must be unique.
-      $parent_path = $entity->link_path;
-      do {
-        $parent = FALSE;
-        $parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
-
-        $query = \Drupal::entityQuery($this->entityType);
-        $query
-          ->condition('mlid', $entity->id(), '<>')
-          ->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('link_path', $parent_path);
-
-        $result = $query->execute();
-        // Only valid if we get a unique result.
-        if (count($result) == 1) {
-          $parent = $this->load($result);
-          $parent = reset($parent);
-        }
-      } while ($parent === FALSE && $parent_path);
-    }
-
-    return $parent;
-  }
-
-  /**
-   * Sets the p1 through p9 properties for a menu link entity being saved.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   A menu link entity.
-   * @param \Drupal\Core\Entity\EntityInterface $parent
-   *   A menu link entity.
-   */
-  protected function setParents(EntityInterface $entity, EntityInterface $parent) {
-    $i = 1;
-    while ($i < $entity->depth) {
-      $p = 'p' . $i++;
-      $entity->{$p} = $parent->{$p};
-    }
-    $p = 'p' . $i++;
-    // The parent (p1 - p9) corresponding to the depth always equals the mlid.
-    $entity->{$p} = $entity->id();
-    while ($i <= MENU_MAX_DEPTH) {
-      $p = 'p' . $i++;
-      $entity->{$p} = 0;
-    }
-  }
-
-  /**
-   * Finds the depth of an item's children relative to its depth.
-   *
-   * For example, if the item has a depth of 2 and the maximum of any child in
-   * the menu link tree is 5, the relative depth is 3.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   A menu link entity.
-   *
-   * @return int
-   *   The relative depth, or zero.
+   * {@inheritdoc}
    */
   public function findChildrenRelativeDepth(EntityInterface $entity) {
     // @todo Since all we need is a specific field from the base table, does it
@@ -581,15 +301,9 @@ public function findChildrenRelativeDepth(EntityInterface $entity) {
   }
 
   /**
-   * Updates the children of a menu link that is being moved.
-   *
-   * The menu name, parents (p1 - p6), and depth are updated for all children of
-   * the link, and the has_children status of the previous parent is updated.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   A menu link entity.
+   * {@inheritdoc}
    */
-  protected function moveChildren(EntityInterface $entity) {
+  public function moveChildren(EntityInterface $entity) {
     $query = $this->database->update($this->entityInfo['base_table']);
 
     $query->fields(array('menu_name' => $entity->menu_name));
@@ -633,10 +347,7 @@ protected function moveChildren(EntityInterface $entity) {
   }
 
   /**
-   * Returns the number of menu links from a menu.
-   *
-   * @param string $menu_name
-   *   The unique name of a menu.
+   * {@inheritdoc}
    */
   public function countMenuLinks($menu_name) {
     $query = \Drupal::entityQuery($this->entityType);
@@ -645,4 +356,34 @@ public function countMenuLinks($menu_name) {
       ->count();
     return $query->execute();
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getParentFromHierarchy(EntityInterface $entity) {
+    $parent_path = $entity->link_path;
+    do {
+      $parent = FALSE;
+      $parent_path = substr($parent_path, 0, strrpos($parent_path, '/'));
+
+      $query = \Drupal::entityQuery($this->entityType);
+      $query
+        ->condition('mlid', $entity->id(), '<>')
+        ->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('link_path', $parent_path);
+
+      $result = $query->execute();
+      // Only valid if we get a unique result.
+      if (count($result) == 1) {
+        $parent = $this->load($result);
+        $parent = reset($parent);
+      }
+    } while ($parent === FALSE && $parent_path);
+
+    return $parent;
+  }
+
 }
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageControllerInterface.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageControllerInterface.php
new file mode 100644
index 0000000..7e1a976
--- /dev/null
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageControllerInterface.php
@@ -0,0 +1,105 @@
+<?php
+
+/**                                                                                                      
+ * @file
+ * Contains \Drupal\menu_link\MenuLinkStorageControllerInterface.
+*/
+
+namespace Drupal\menu_link;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+
+interface MenuLinkStorageControllerInterface extends EntityStorageControllerInterface {
+
+  /**
+   * Sets an internal flag that allows us to prevent the reparenting operations
+   * executed during deletion.
+   *
+   * @param bool $value
+   */
+  public function setPreventReparenting($value = FALSE);
+
+  /**
+   * Gets value of internal flag that allows/prevents reparenting operations
+   * executed during deletion.
+   *
+   * @return bool
+   */
+  public function getPreventReparenting();
+
+  /**
+   * Loads updated and customized menu links for specific router paths.
+   *
+   * Note that this is a low-level method and it doesn't return fully populated
+   * menu link entities. (e.g. no fields are attached)
+   *
+   * @param array $router_paths
+   *   An array of router paths.
+   *
+   * @return array
+   *   An array of menu link objects indexed by their ids.
+   */
+  public function loadUpdatedCustomized(array $router_paths);
+
+  /**
+   * Loads system menu link as needed by system_get_module_admin_tasks().
+   *
+   * @return array
+   *   An array of menu link entities indexed by their IDs.
+   */
+  public function loadModuleAdminTasks();
+
+  /**
+   * Checks and updates the 'has_children' property for the parent of a link.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   A menu link entity.
+   */
+  public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE);
+
+  /**
+   * Finds the depth of an item's children relative to its depth.
+   *
+   * For example, if the item has a depth of 2 and the maximum of any child in
+   * the menu link tree is 5, the relative depth is 3.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   A menu link entity.
+   *
+   * @return int
+   *   The relative depth, or zero.
+   */
+  public function findChildrenRelativeDepth(EntityInterface $entity);
+
+  /**
+   * Updates the children of a menu link that is being moved.
+   *
+   * The menu name, parents (p1 - p6), and depth are updated for all children of
+   * the link, and the has_children status of the previous parent is updated.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   A menu link entity.
+   */
+  public function moveChildren(EntityInterface $entity);
+
+  /**
+   * Returns the number of menu links from a menu.
+   *
+   * @param string $menu_name
+   *   The unique name of a menu.
+   */
+  public function countMenuLinks($menu_name);
+
+  /**
+   * Tries to derive menu link's parent from the path hierarchy.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   A menu link entity.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface|false
+   *   A menu link entity or FALSE if not valid parent was found.
+   */
+  public function getParentFromHierarchy(EntityInterface $entity);
+
+}
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/Plugin/Core/Entity/MenuLink.php b/core/modules/menu_link/lib/Drupal/menu_link/Plugin/Core/Entity/MenuLink.php
index 5af23d7..7fe3a31 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/Plugin/Core/Entity/MenuLink.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/Plugin/Core/Entity/MenuLink.php
@@ -9,9 +9,13 @@
 
 use Drupal\menu_link\MenuLinkInterface;
 use Symfony\Component\Routing\Route;
+use Symfony\Component\HttpFoundation\Request;
 
 use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Annotation\Translation;
+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;
 
@@ -372,4 +376,217 @@ public function offsetSet($offset, $value) {
   public function offsetUnset($offset) {
     unset($this->{$offset});
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function preDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+    // Nothing to do if we don't want to reparent children.
+    if ($storage_controller->getPreventReparenting()) {
+      return;
+    }
+
+    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));
+        foreach ($children as $child) {
+          $child->plid = $entity->plid;
+          $storage_controller->save($child);
+        }
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function postDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+    $affected_menus = array();
+    // Update the has_children status of the parent.
+    foreach ($entities as $entity) {
+      if (!$storage_controller->getPreventReparenting()) {
+        $storage_controller->updateParentalStatus($entity);
+      }
+
+      // 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;
+      }
+    }
+
+    foreach ($affected_menus as $menu_name) {
+      menu_cache_clear($menu_name);
+    }
+    _menu_clear_page_cache();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  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;
+
+    // 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;
+    }
+    // If no corresponding parent link was found, move the link to the top-level.
+    else {
+      $this->plid = 0;
+    }
+
+    // Directly fill parents for top-level links.
+    if ($this->plid == 0) {
+      $this->p1 = $this->id();
+      for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) {
+        $parent_property = "p$i";
+        $this->{$parent_property} = 0;
+      }
+      $this->depth = 1;
+    }
+    // Otherwise, ensure that this link's depth is not beyond the maximum depth
+    // and fill parents based on the parent link.
+    else {
+      if ($this->has_children && $this->original) {
+        $limit = MENU_MAX_DEPTH - $storage_controller->findChildrenRelativeDepth($this->original) - 1;
+      }
+      else {
+        $limit = MENU_MAX_DEPTH - 1;
+      }
+      if ($parent->depth > $limit) {
+        return FALSE;
+      }
+      $this->depth = $parent->depth + 1;
+      $this->setParents($parent);
+    }
+
+    // Need to check both plid and menu_name, since plid can be 0 in any menu.
+    if (isset($this->original) && ($this->plid != $this->original->plid || $this->menu_name != $this->original->menu_name)) {
+      $storage_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 = '';
+      }
+      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);
+      }
+    }
+    // Find the route_name.
+    if (!isset($this->route_name)) {
+      $this->route_name = $this::findRouteName($this->link_path);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
+    // 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);
+    }
+
+    // Now clear the cache.
+    _menu_clear_page_cache();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function findRouteName($link_path) {
+    // Look up the route_name used for the given path.
+    $request = Request::create('/' . $link_path);
+    $request->attributes->set('system_path', $link_path);
+    try {
+      // Use router.dynamic instead of router, because router will call the
+      // legacy router which will call hook_menu() and you will get back to
+      // this method.
+      $result = \Drupal::service('router.dynamic')->matchRequest($request);
+      return isset($result['_route']) ? $result['_route'] : '';
+    }
+    catch (\Exception $e) {
+      return '';
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setParents(EntityInterface $parent) {
+    $i = 1;
+    while ($i < $this->depth) {
+      $p = 'p' . $i++;
+      $this->{$p} = $parent->{$p};
+    }
+    $p = 'p' . $i++;
+    // The parent (p1 - p9) corresponding to the depth always equals the mlid.
+    $this->{$p} = $this->id();
+    while ($i <= MENU_MAX_DEPTH) {
+      $p = 'p' . $i++;
+      $this->{$p} = 0;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function findParent(EntityStorageControllerInterface $storage_controller, array $parent_candidates = array()) {
+    $parent = FALSE;
+
+    // This item is explicitely top-level, skip the rest of the parenting.
+    if (isset($this->plid) && empty($this->plid)) {
+      return $parent;
+    }
+
+    // If we have a parent link ID, try to use that.
+    $candidates = array();
+    if (isset($this->plid)) {
+      $candidates[] = $this->plid;
+    }
+
+    // Else, if we have a link hierarchy try to find a valid parent in there.
+    if (!empty($this->depth) && $this->depth > 1) {
+      for ($depth = $this->depth - 1; $depth >= 1; $depth--) {
+        $parent_property = "p$depth";
+        $candidates[] = $this->$parent_property;
+      }
+    }
+
+    foreach ($candidates as $mlid) {
+      if (isset($parent_candidates[$mlid])) {
+        $parent = $parent_candidates[$mlid];
+      }
+      else {
+        $parent = $storage_controller->load(array($mlid));
+        $parent = reset($parent);
+      }
+      if ($parent) {
+        return $parent;
+      }
+    }
+
+    // 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') {
+      $parent = $storage_controller->getParentFromHierarchy($this);
+    }
+
+    return $parent;
+  }
+
+
 }
diff --git a/core/modules/menu_link/menu_link.module b/core/modules/menu_link/menu_link.module
index 45e1923..80785f9 100644
--- a/core/modules/menu_link/menu_link.module
+++ b/core/modules/menu_link/menu_link.module
@@ -103,7 +103,7 @@ function menu_link_delete_multiple(array $mlids, $force = FALSE, $prevent_repare
   else {
     $entities = $controller->load($mlids);
   }
-  $controller->preventReparenting($prevent_reparenting);
+  $controller->setPreventReparenting($prevent_reparenting);
   $controller->delete($entities);
 }
 
diff --git a/core/modules/node/lib/Drupal/node/NodeStorageController.php b/core/modules/node/lib/Drupal/node/NodeStorageController.php
index db3ba75..f12ad56 100644
--- a/core/modules/node/lib/Drupal/node/NodeStorageController.php
+++ b/core/modules/node/lib/Drupal/node/NodeStorageController.php
@@ -124,14 +124,6 @@ protected function mapToDataStorageRecord(EntityInterface $entity, $langcode) {
   }
 
   /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::preSave().
-   */
-  protected function preSave(EntityInterface $node) {
-    // Before saving the node, set changed and revision times.
-    $node->changed->value = REQUEST_TIME;
-  }
-
-  /**
    * Overrides Drupal\Core\Entity\DatabaseStorageController::preSaveRevision().
    */
   protected function preSaveRevision(\stdClass $record, EntityInterface $entity) {
@@ -158,18 +150,6 @@ protected function preSaveRevision(\stdClass $record, EntityInterface $entity) {
   }
 
   /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave().
-   */
-  public function postSave(EntityInterface $node, $update) {
-    // Update the node access table for this node, but only if it is the
-    // default revision. There's no need to delete existing records if the node
-    // is new.
-    if ($node->isDefaultRevision()) {
-      node_access_acquire_grants($node->getBCEntity(), $update);
-    }
-  }
-
-  /**
    * Overrides Drupal\Core\Entity\DatabaseStorageController::preDelete().
    */
   public function preDelete($entities) {
diff --git a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
index fc4da34..0c6c885 100644
--- a/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
+++ b/core/modules/node/lib/Drupal/node/Plugin/Core/Entity/Node.php
@@ -8,6 +8,7 @@
 namespace Drupal\node\Plugin\Core\Entity;
 
 use Drupal\Core\Entity\EntityNG;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Annotation\Translation;
 use Drupal\node\NodeInterface;
@@ -234,4 +235,21 @@ public function getRevisionId() {
     return $this->get('vid')->value;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function preSave(EntityStorageControllerInterface $storage_controller) {
+    // Before saving the node, set changed and revision times.
+    $this->changed->value = REQUEST_TIME;
+  }
+
+  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
+    // Update the node access table for this node, but only if it is the
+    // default revision. There's no need to delete existing records if the node
+    // is new.
+    if ($this->isDefaultRevision()) {
+      node_access_acquire_grants($this->getBCEntity(), $update);
+    }
+  }
+
 }
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php
index 28741ec..1258da0 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Term.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Entity\EntityNG;
 use Drupal\Core\Entity\Annotation\EntityType;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Annotation\Translation;
 use Drupal\Core\Language\Language;
 use Drupal\taxonomy\TermInterface;
@@ -147,4 +148,46 @@ protected function init() {
     unset($this->description);
     unset($this->parent);
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function postDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+    // See if any of the term's children are about to be become orphans.
+    $orphans = array();
+    foreach (array_keys($entities) as $tid) {
+      if ($children = taxonomy_term_load_children($tid)) {
+        foreach ($children as $child) {
+          // If the term has multiple parents, we don't delete it.
+          $parents = taxonomy_term_load_parents($child->id());
+          // Because the parent has already been deleted, the parent count might
+          // be 0.
+          if (count($parents) <= 1) {
+            $orphans[] = $child->id();
+          }
+        }
+      }
+    }
+
+    // Delete term hierarchy information after looking up orphans but before
+    // deleting them so that their children/parent information is consistent.
+    $storage_controller->deleteTermHierarchy(array_keys($entities));
+
+    if (!empty($orphans)) {
+      entity_delete_multiple('taxonomy_term', $orphans);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
+    // Only change the parents if a value is set, keep the existing values if
+    // not.
+    if (isset($this->parent->value)) {
+      $storage_controller->deleteTermHierarchy(array($this->id()));
+      $storage_controller->updateTermHierarchy($this);
+    }
+  }
+
 }
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Vocabulary.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Vocabulary.php
index aa66843..580b26b 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Vocabulary.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Plugin/Core/Entity/Vocabulary.php
@@ -8,6 +8,7 @@
 namespace Drupal\taxonomy\Plugin\Core\Entity;
 
 use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Annotation\Translation;
 use Drupal\taxonomy\VocabularyInterface;
@@ -95,4 +96,79 @@ public function uri() {
     );
   }
 
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
+    if (!$update) {
+      entity_invoke_bundle_hook('create', 'taxonomy_term', $this->id());
+    }
+    elseif ($this->getOriginalID() != $this->id()) {
+      // Reflect machine name changes in the definitions of existing 'taxonomy'
+      // fields.
+      $fields = field_read_fields();
+      foreach ($fields as $field_name => $field) {
+        $update_field = FALSE;
+        if ($field['type'] == 'taxonomy_term_reference') {
+          foreach ($field['settings']['allowed_values'] as $key => &$value) {
+            if ($value['vocabulary'] == $this->getOriginalID()) {
+              $value['vocabulary'] = $this->id();
+              $update_field = TRUE;
+            }
+          }
+          if ($update_field) {
+            field_update_field($field);
+          }
+        }
+      }
+      // Update bundles.
+      entity_invoke_bundle_hook('rename', 'taxonomy_term', $this->getOriginalID(), $this->id());
+    }
+    $storage_controller->resetCache($update ? array($this->getOriginalID()) : array());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function preDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+    // Only load terms without a parent, child terms will get deleted too.
+    entity_delete_multiple('taxonomy_term', $storage_controller->getToplevelTids(array_keys($entities)));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function postDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+    $vocabularies = array();
+    foreach ($entities as $vocabulary) {
+      $vocabularies[$vocabulary->id()] = $vocabulary->id();
+    }
+    // Load all Taxonomy module fields and delete those which use only this
+    // vocabulary.
+    $taxonomy_fields = field_read_fields(array('module' => 'taxonomy'));
+    foreach ($taxonomy_fields as $field_name => $taxonomy_field) {
+      $modified_field = FALSE;
+      // Term reference fields may reference terms from more than one
+      // vocabulary.
+      foreach ($taxonomy_field['settings']['allowed_values'] as $key => $allowed_value) {
+        if (isset($vocabularies[$allowed_value['vocabulary']])) {
+          unset($taxonomy_field['settings']['allowed_values'][$key]);
+          $modified_field = TRUE;
+        }
+      }
+      if ($modified_field) {
+        if (empty($taxonomy_field['settings']['allowed_values'])) {
+          field_delete_field($field_name);
+        }
+        else {
+          // Update the field definition with the new allowed values.
+          field_update_field($taxonomy_field);
+        }
+      }
+    }
+    // Reset caches.
+    $storage_controller->resetCache(array_keys($vocabularies));
+  }
+
 }
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php
index 9bb08c0..aa0c120 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php
@@ -14,7 +14,7 @@
 /**
  * Defines a Controller class for taxonomy terms.
  */
-class TermStorageController extends DatabaseStorageControllerNG {
+class TermStorageController extends DatabaseStorageControllerNG implements TermStorageControllerInterface{
 
   /**
    * Overrides Drupal\Core\Entity\DatabaseStorageController::create().
@@ -44,61 +44,6 @@ protected function buildPropertyQuery(QueryInterface $entity_query, array $value
   }
 
   /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::postDelete().
-   */
-  protected function postDelete($entities) {
-    // See if any of the term's children are about to be become orphans.
-    $orphans = array();
-    foreach (array_keys($entities) as $tid) {
-      if ($children = taxonomy_term_load_children($tid)) {
-        foreach ($children as $child) {
-          // If the term has multiple parents, we don't delete it.
-          $parents = taxonomy_term_load_parents($child->id());
-          // Because the parent has already been deleted, the parent count might
-          // be 0.
-          if (count($parents) <= 1) {
-            $orphans[] = $child->id();
-          }
-        }
-      }
-    }
-
-    // Delete term hierarchy information after looking up orphans but before
-    // deleting them so that their children/parent information is consistent.
-    db_delete('taxonomy_term_hierarchy')
-      ->condition('tid', array_keys($entities))
-      ->execute();
-
-    if (!empty($orphans)) {
-      entity_delete_multiple('taxonomy_term', $orphans);
-    }
-  }
-
-  /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave().
-   */
-  protected function postSave(EntityInterface $entity, $update) {
-    // Only change the parents if a value is set, keep the existing values if
-    // not.
-    if (isset($entity->parent->value)) {
-      db_delete('taxonomy_term_hierarchy')
-        ->condition('tid', $entity->id())
-        ->execute();
-
-      $query = db_insert('taxonomy_term_hierarchy')
-        ->fields(array('tid', 'parent'));
-
-      foreach ($entity->parent as $parent) {
-        $query->values(array(
-          'tid' => $entity->id(),
-          'parent' => (int) $parent->value,
-        ));
-      }
-      $query->execute();
-    }
-  }
-
-  /**
    * Overrides Drupal\Core\Entity\DatabaseStorageController::resetCache().
    */
   public function resetCache(array $ids = NULL) {
@@ -167,4 +112,30 @@ public function baseFieldDefinitions() {
     );
     return $properties;
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteTermHierarchy($tids) {
+    db_delete('taxonomy_term_hierarchy')
+      ->condition('tid', $tids)
+      ->execute();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function updateTermHierarchy(EntityInterface $term) {
+    $query = db_insert('taxonomy_term_hierarchy')
+      ->fields(array('tid', 'parent'));
+
+    foreach ($term->parent as $parent) {
+      $query->values(array(
+        'tid' => $term->id(),
+        'parent' => (int) $parent->value,
+      ));
+    }
+    $query->execute();
+  }
+
 }
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageControllerInterface.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageControllerInterface.php
new file mode 100644
index 0000000..96b7073
--- /dev/null
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageControllerInterface.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\taxonomy\TermStorageControllerInterface.
+*/
+
+namespace Drupal\taxonomy;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+
+interface TermStorageControllerInterface extends EntityStorageControllerInterface {
+
+  /**
+   * Removed reference to terms from term_hierarchy.
+   *
+   * @param array
+   *   Array of terms that need to be removed from hierarchy.
+   */
+  public function deleteTermHierarchy($tids);
+
+  /**
+   * Updates terms hierarchy information with term's hierarchy trail.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $term
+   *   Term object that needs to be added to term hierarchy information.
+   */
+  public function updateTermHierarchy(EntityInterface $term);
+}
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php
index c0e377b..a60f140 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php
@@ -8,91 +8,11 @@
 namespace Drupal\taxonomy;
 
 use Drupal\Core\Config\Entity\ConfigStorageController;
-use Drupal\Core\Entity\EntityInterface;
 
 /**
  * Defines a controller class for taxonomy vocabularies.
  */
-class VocabularyStorageController extends ConfigStorageController {
-
-  /**
-   * Overrides Drupal\Core\Config\Entity\ConfigStorageController::postSave().
-   */
-  protected function postSave(EntityInterface $entity, $update) {
-    if (!$update) {
-      entity_invoke_bundle_hook('create', 'taxonomy_term', $entity->id());
-    }
-    elseif ($entity->getOriginalID() != $entity->id()) {
-      // Reflect machine name changes in the definitions of existing 'taxonomy'
-      // fields.
-      $fields = field_read_fields();
-      foreach ($fields as $field_name => $field) {
-        $update_field = FALSE;
-        if ($field['type'] == 'taxonomy_term_reference') {
-          foreach ($field['settings']['allowed_values'] as $key => &$value) {
-            if ($value['vocabulary'] == $entity->getOriginalID()) {
-              $value['vocabulary'] = $entity->id();
-              $update_field = TRUE;
-            }
-          }
-          if ($update_field) {
-            field_update_field($field);
-          }
-        }
-      }
-      // Update bundles.
-      entity_invoke_bundle_hook('rename', 'taxonomy_term', $entity->getOriginalID(), $entity->id());
-    }
-    parent::postSave($entity, $update);
-    $this->resetCache($update ? array($entity->getOriginalID()) : array());
-  }
-
-  /**
-   * Overrides Drupal\Core\Config\Entity\ConfigStorageController::preDelete().
-   */
-  protected function preDelete($entities) {
-    parent::preDelete($entities);
-    // Only load terms without a parent, child terms will get deleted too.
-    $tids = db_query('SELECT t.tid FROM {taxonomy_term_data} t INNER JOIN {taxonomy_term_hierarchy} th ON th.tid = t.tid WHERE t.vid IN (:vids) AND th.parent = 0', array(':vids' => array_keys($entities)))->fetchCol();
-    entity_delete_multiple('taxonomy_term', $tids);
-  }
-
-  /**
-   * Overrides Drupal\Core\Config\Entity\ConfigStorageController::postDelete().
-   */
-  protected function postDelete($entities) {
-    parent::postDelete($entities);
-
-    $vocabularies = array();
-    foreach ($entities as $vocabulary) {
-      $vocabularies[$vocabulary->id()] = $vocabulary->id();
-    }
-    // Load all Taxonomy module fields and delete those which use only this
-    // vocabulary.
-    $taxonomy_fields = field_read_fields(array('module' => 'taxonomy'));
-    foreach ($taxonomy_fields as $field_name => $taxonomy_field) {
-      $modified_field = FALSE;
-      // Term reference fields may reference terms from more than one
-      // vocabulary.
-      foreach ($taxonomy_field['settings']['allowed_values'] as $key => $allowed_value) {
-        if (isset($vocabularies[$allowed_value['vocabulary']])) {
-          unset($taxonomy_field['settings']['allowed_values'][$key]);
-          $modified_field = TRUE;
-        }
-      }
-      if ($modified_field) {
-        if (empty($taxonomy_field['settings']['allowed_values'])) {
-          field_delete_field($field_name);
-        }
-        else {
-          // Update the field definition with the new allowed values.
-          field_update_field($taxonomy_field);
-        }
-      }
-    }
-    // Reset caches.
-    $this->resetCache(array_keys($vocabularies));
-  }
+class VocabularyStorageController extends ConfigStorageController implements VocabularyStorageControllerInterface {
 
   /**
    * Overrides Drupal\Core\Config\Entity\ConfigStorageController::resetCache().
@@ -104,4 +24,11 @@ public function resetCache(array $ids = NULL) {
     entity_info_cache_clear();
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getToplevelTids($vids) {
+    return db_query('SELECT t.tid FROM {taxonomy_term_data} t INNER JOIN {taxonomy_term_hierarchy} th ON th.tid = t.tid WHERE t.vid IN (:vids) AND th.parent = 0', array(':vids' => $vids))->fetchCol();
+  }
+
 }
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageControllerInterface.php b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageControllerInterface.php
new file mode 100644
index 0000000..c194257
--- /dev/null
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageControllerInterface.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\taxonomy\VocabularyStorageControllerInterface.
+ */
+
+namespace Drupal\taxonomy;
+
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+
+interface VocabularyStorageControllerInterface extends EntityStorageControllerInterface {
+
+  /**
+   * Gets top-level term IDs of vocabularies.
+   *
+   * @param array $vids
+   *   Array of vocabulary IDs.
+   *
+   * @return array
+   *   Array of top-level term IDs.
+   */
+  public function getToplevelTids($vids);
+
+}
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/Role.php b/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/Role.php
index 5a288c8..d72a1c6 100644
--- a/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/Role.php
+++ b/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/Role.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Annotation\Translation;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\user\RoleInterface;
 
 /**
@@ -78,4 +79,23 @@ public function uri() {
     );
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function preSave(EntityStorageControllerInterface $storage_controller) {
+    if (!isset($this->weight) && ($roles = $storage_controller->load())) {
+      // Set a role weight to make this new role last.
+      $max = array_reduce($roles, function($max, $this) {
+        return $max > $this->weight ? $max : $this->weight;
+      });
+      $this->weight = $max + 1;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function postDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+    $storage_controller->deleteRoles(array_keys($entities));
+  }
 }
diff --git a/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php b/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php
index 6797848..c8e37e6 100644
--- a/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php
+++ b/core/modules/user/lib/Drupal/user/Plugin/Core/Entity/User.php
@@ -8,6 +8,9 @@
 namespace Drupal\user\Plugin\Core\Entity;
 
 use Drupal\Core\Entity\Entity;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\user\UserStorageControllerInterface;
+use Drupal\Core\Entity\EntityMalformedException;
 use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Annotation\Translation;
 use Drupal\Core\Language\Language;
@@ -179,4 +182,93 @@ class User extends Entity implements UserInterface {
   public function id() {
     return $this->uid;
   }
+
+  static function preCreate(EntityStorageControllerInterface $storage_controller, &$values) {
+    if (!isset($values['created'])) {
+      $values['created'] = REQUEST_TIME;
+    }
+    // Users always have the authenticated user role.
+    $values['roles'][DRUPAL_AUTHENTICATED_RID] = DRUPAL_AUTHENTICATED_RID;
+  }
+  /**
+   * {@inheritdoc}
+   */
+  public function preSave(EntityStorageControllerInterface $storage_controller) {
+    // Update the user password if it has changed.
+    if ($this->isNew() || (!empty($this->pass) && $this->pass != $this->original->pass)) {
+      // Allow alternate password hashing schemes.
+      $this->pass = \Drupal::service('password')->hash(trim($this->pass));
+      // Abort if the hashing failed and returned FALSE.
+      if (!$this->pass) {
+        throw new EntityMalformedException('The entity does not have a password.');
+      }
+    }
+
+    if (!$this->isNew()) {
+      // If the password is empty, that means it was not changed, so use the
+      // original password.
+      if (empty($this->pass)) {
+        $this->pass = $this->original->pass;
+      }
+    }
+
+    // Prepare user roles.
+    if (isset($this->roles)) {
+      $this->roles = array_filter($this->roles);
+    }
+
+    // Store account cancellation information.
+    foreach (array('user_cancel_method', 'user_cancel_notify') as $key) {
+      if (isset($this->{$key})) {
+        \Drupal::service('user.data')->set('user', $this->id(), substr($key, 5), $this->{$key});
+      }
+    }
+  }
+
+  function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
+    if ($update) {
+      // Save user roles if changed.
+      if ($this->roles != $this->original->roles) {
+        $storage_controller->deleteUserRoles(array($this->id()));
+        $storage_controller->saveRoles($this);
+      }
+
+      // If the password has been changed, delete all open sessions for the
+      // user and recreate the current one.
+      if ($this->pass != $this->original->pass) {
+        drupal_session_destroy_uid($this->uid);
+        if ($this->uid == $GLOBALS['user']->uid) {
+          drupal_session_regenerate();
+        }
+      }
+
+      // Remove roles that are no longer enabled for the user.
+      $this->roles = array_filter($this->roles);
+
+      // If the user was blocked, delete the user's sessions to force a logout.
+      if ($this->original->status != $this->status && $this->status == 0) {
+        drupal_session_destroy_uid($this->uid);
+      }
+
+      // Send emails after we have the new user object.
+      if ($this->status != $this->original->status) {
+        // The user's status is changing; conditionally send notification email.
+        $op = $this->status == 1 ? 'status_activated' : 'status_blocked';
+        _user_mail_notify($op, $this);
+      }
+    }
+    else {
+      // Save user roles.
+      if (count($this->roles) > 1) {
+        $storage_controller->saveRoles($this);
+      }
+    }
+  }
+
+  public static function postDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+    $uids = array_keys($entities);
+    \Drupal::service('user.data')->delete(NULL, $uids);
+    $storage_controller->deleteUserRoles($uids);
+  }
+
 }
diff --git a/core/modules/user/lib/Drupal/user/RoleStorageController.php b/core/modules/user/lib/Drupal/user/RoleStorageController.php
index 680b412..0bae6f2 100644
--- a/core/modules/user/lib/Drupal/user/RoleStorageController.php
+++ b/core/modules/user/lib/Drupal/user/RoleStorageController.php
@@ -13,21 +13,7 @@
 /**
  * Controller class for user roles.
  */
-class RoleStorageController extends ConfigStorageController {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function preSave(EntityInterface $entity) {
-    if (!isset($entity->weight) && $roles = entity_load_multiple('user_role')) {
-      // Set a role weight to make this new role last.
-      $max = array_reduce($roles, function($max, $entity) {
-        return $max > $entity->weight ? $max : $entity->weight;
-      });
-      $entity->weight = $max + 1;
-    }
-    parent::preSave($entity);
-  }
+class RoleStorageController extends ConfigStorageController implements RoleStorageControllerInterface {
 
   /**
    * {@inheritdoc}
@@ -43,9 +29,7 @@ public function resetCache(array $ids = NULL) {
   /**
    * {@inheritdoc}
    */
-  protected function postDelete($entities) {
-    $rids = array_keys($entities);
-
+  public function deleteRoles(array $rids) {
     // Delete permission assignments.
     db_delete('role_permission')
       ->condition('rid', $rids)
diff --git a/core/modules/user/lib/Drupal/user/RoleStorageControllerInterface.php b/core/modules/user/lib/Drupal/user/RoleStorageControllerInterface.php
new file mode 100644
index 0000000..9d9128a
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/RoleStorageControllerInterface.php
@@ -0,0 +1,19 @@
+<?php
+/**
+ * @file
+ * Contains
+ */
+
+namespace Drupal\user;
+
+
+interface RoleStorageControllerInterface {
+
+  /**
+   * Delete roles.
+   *
+   * @param array $rids
+   *   The list of role IDs to delete.
+   */
+  function deleteRoles(array $rids);
+}
diff --git a/core/modules/user/lib/Drupal/user/UserStorageController.php b/core/modules/user/lib/Drupal/user/UserStorageController.php
index e282226..0295862 100644
--- a/core/modules/user/lib/Drupal/user/UserStorageController.php
+++ b/core/modules/user/lib/Drupal/user/UserStorageController.php
@@ -8,7 +8,6 @@
 namespace Drupal\user;
 
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Entity\EntityMalformedException;
 use Drupal\Core\Entity\DatabaseStorageController;
 use Drupal\Core\Password\PasswordInterface;
 use Drupal\Core\Database\Connection;
@@ -21,7 +20,7 @@
  * This extends the Drupal\Core\Entity\DatabaseStorageController class, adding
  * required special handling for user objects.
  */
-class UserStorageController extends DatabaseStorageController {
+class UserStorageController extends DatabaseStorageController implements UserStorageControllerInterface {
 
   /**
    * Provides the password hashing service object.
@@ -86,10 +85,7 @@ function attachLoad(&$queried_users, $load_revision = FALSE) {
     }
 
     // Add any additional roles from the database.
-    $result = db_query('SELECT rid, uid FROM {users_roles} WHERE uid IN (:uids)', array(':uids' => array_keys($queried_users)));
-    foreach ($result as $record) {
-      $queried_users[$record->uid]->roles[$record->rid] = $record->rid;
-    }
+    $this->addRoles($queried_users);
 
     // Call the default attachLoad() method. This will add fields and call
     // hook_user_load().
@@ -97,19 +93,6 @@ function attachLoad(&$queried_users, $load_revision = FALSE) {
   }
 
   /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::create().
-   */
-  public function create(array $values) {
-    if (!isset($values['created'])) {
-      $values['created'] = REQUEST_TIME;
-    }
-    // Users always have the authenticated user role.
-    $values['roles'][DRUPAL_AUTHENTICATED_RID] = DRUPAL_AUTHENTICATED_RID;
-
-    return parent::create($values);
-  }
-
-  /**
    * Overrides Drupal\Core\Entity\DatabaseStorageController::save().
    */
   public function save(EntityInterface $entity) {
@@ -121,112 +104,38 @@ public function save(EntityInterface $entity) {
   }
 
   /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::preSave().
+   * {@inheritdoc}
    */
-  protected function preSave(EntityInterface $entity) {
-    // Update the user password if it has changed.
-    if ($entity->isNew() || (!empty($entity->pass) && $entity->pass != $entity->original->pass)) {
-      // Allow alternate password hashing schemes.
-      $entity->pass = $this->password->hash(trim($entity->pass));
-      // Abort if the hashing failed and returned FALSE.
-      if (!$entity->pass) {
-        throw new EntityMalformedException('The entity does not have a password.');
+  public function saveRoles(EntityInterface $user) {
+    $query = $this->database->insert('users_roles')->fields(array('uid', 'rid'));
+    foreach (array_keys($user->roles) as $rid) {
+      if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
+        $query->values(array(
+          'uid' => $user->id(),
+          'rid' => $rid,
+        ));
       }
     }
+    $query->execute();
+  }
 
-    if (!$entity->isNew()) {
-      // If the password is empty, that means it was not changed, so use the
-      // original password.
-      if (empty($entity->pass)) {
-        $entity->pass = $entity->original->pass;
-      }
-    }
-
-    // Prepare user roles.
-    if (isset($entity->roles)) {
-      $entity->roles = array_filter($entity->roles);
-    }
-
-    // Store account cancellation information.
-    foreach (array('user_cancel_method', 'user_cancel_notify') as $key) {
-      if (isset($entity->{$key})) {
-        $this->userData->set('user', $entity->id(), substr($key, 5), $entity->{$key});
-      }
+  /**
+   * {@inheritdoc}
+   */
+  public function addRoles(array $users) {
+    $result = db_query('SELECT rid, uid FROM {users_roles} WHERE uid IN (:uids)', array(':uids' => array_keys($users)));
+    foreach ($result as $record) {
+      $users[$record->uid]->roles[$record->rid] = $record->rid;
     }
   }
 
   /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave().
+   * {@inheritdoc}
    */
-  protected function postSave(EntityInterface $entity, $update) {
-
-    if ($update) {
-      // If the password has been changed, delete all open sessions for the
-      // user and recreate the current one.
-      if ($entity->pass != $entity->original->pass) {
-        drupal_session_destroy_uid($entity->uid);
-        if ($entity->uid == $GLOBALS['user']->uid) {
-          drupal_session_regenerate();
-        }
-      }
-
-      // Remove roles that are no longer enabled for the user.
-      $entity->roles = array_filter($entity->roles);
-
-      // Reload user roles if provided.
-      if ($entity->roles != $entity->original->roles) {
-        $this->database->delete('users_roles')
-          ->condition('uid', $entity->uid)
-          ->execute();
-
-        $query = $this->database->insert('users_roles')->fields(array('uid', 'rid'));
-        foreach (array_keys($entity->roles) as $rid) {
-          if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
-            $query->values(array(
-              'uid' => $entity->uid,
-              'rid' => $rid,
-            ));
-          }
-        }
-        $query->execute();
-      }
-
-      // If the user was blocked, delete the user's sessions to force a logout.
-      if ($entity->original->status != $entity->status && $entity->status == 0) {
-        drupal_session_destroy_uid($entity->uid);
-      }
-
-      // Send emails after we have the new user object.
-      if ($entity->status != $entity->original->status) {
-        // The user's status is changing; conditionally send notification email.
-        $op = $entity->status == 1 ? 'status_activated' : 'status_blocked';
-        _user_mail_notify($op, $entity);
-      }
-    }
-    else {
-      // Save user roles.
-      if (count($entity->roles) > 1) {
-        $query = $this->database->insert('users_roles')->fields(array('uid', 'rid'));
-        foreach (array_keys($entity->roles) as $rid) {
-          if (!in_array($rid, array(DRUPAL_ANONYMOUS_RID, DRUPAL_AUTHENTICATED_RID))) {
-            $query->values(array(
-              'uid' => $entity->uid,
-              'rid' => $rid,
-            ));
-          }
-        }
-        $query->execute();
-      }
-    }
-  }
-
-  /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::postDelete().
-   */
-  protected function postDelete($entities) {
+  public function deleteUserRoles(array $uids) {
     $this->database->delete('users_roles')
-      ->condition('uid', array_keys($entities), 'IN')
+      ->condition('uid', $uids)
       ->execute();
-    $this->userData->delete(NULL, array_keys($entities));
   }
+
 }
diff --git a/core/modules/user/lib/Drupal/user/UserStorageControllerInterface.php b/core/modules/user/lib/Drupal/user/UserStorageControllerInterface.php
new file mode 100644
index 0000000..9a5b474
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/UserStorageControllerInterface.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\user\UserStorageControllerInterface .
+ */
+
+namespace Drupal\user;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+
+interface UserStorageControllerInterface extends EntityStorageControllerInterface {
+
+  /**
+   * Add any roles from the storage to the user.
+   *
+   * @param array $users
+   */
+  public function addRoles(array $users);
+
+  /**
+   * Save the user's roles.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $user
+   */
+  public function saveRoles(EntityInterface $user);
+
+  /**
+   * Remove the roles of a user.
+   *
+   * @param array $uids
+   */
+  public function deleteUserRoles(array $uids);
+
+}
diff --git a/core/modules/views/lib/Drupal/views/Plugin/Core/Entity/View.php b/core/modules/views/lib/Drupal/views/Plugin/Core/Entity/View.php
index 6e65758..b6f7f95 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/Core/Entity/View.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/Core/Entity/View.php
@@ -8,6 +8,7 @@
 namespace Drupal\views\Plugin\Core\Entity;
 
 use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\views\Views;
 use Drupal\views_ui\ViewUI;
 use Drupal\views\ViewStorageInterface;
@@ -361,4 +362,63 @@ public function getExportProperties() {
     return $properties;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
+    views_invalidate_cache();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function preCreate(EntityStorageControllerInterface $storage_controller, &$values) {
+    // If there is no information about displays available add at least the
+    // default display.
+    $values += array(
+      'display' => array(
+        'default' => array(
+          'display_plugin' => 'default',
+          'id' => 'default',
+          'display_title' => 'Master',
+          'position' => 0,
+          'display_options' => array(),
+        ),
+      )
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function postCreate(EntityStorageControllerInterface $storage_controller) {
+    $this->mergeDefaultDisplaysOptions();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function mergeDefaultDisplaysOptions() {
+    $displays = array();
+    foreach ($this->get('display') as $key => $options) {
+      $options += array(
+        'display_options' => array(),
+        'display_plugin' => NULL,
+        'id' => NULL,
+        'display_title' => '',
+        'position' => NULL,
+      );
+      // Add the defaults for the display.
+      $displays[$key] = $options;
+    }
+    // Sort the displays.
+    uasort($displays, function ($display1, $display2) {
+      if ($display1['position'] != $display2['position']) {
+        return $display1['position'] < $display2['position'] ? -1 : 1;
+      }
+      return 0;
+    });
+    $this->set('display', $displays);
+  }
+
 }
diff --git a/core/modules/views/lib/Drupal/views/ViewStorageController.php b/core/modules/views/lib/Drupal/views/ViewStorageController.php
index 3ac6731..a7e1319 100644
--- a/core/modules/views/lib/Drupal/views/ViewStorageController.php
+++ b/core/modules/views/lib/Drupal/views/ViewStorageController.php
@@ -35,72 +35,11 @@ public function load(array $ids = NULL) {
    */
   protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
     foreach ($queried_entities as $id => $entity) {
-      $this->mergeDefaultDisplaysOptions($entity);
+      $entity->mergeDefaultDisplaysOptions();
     }
 
     parent::attachLoad($queried_entities, $revision_id);
   }
 
-  /**
-   * Overrides Drupal\config\ConfigStorageController::postSave().
-   */
-  protected function postSave(EntityInterface $entity, $update) {
-    parent::postSave($entity, $update);
-    // Clear caches.
-    views_invalidate_cache();
-  }
-
-  /**
-   * Overrides Drupal\config\ConfigStorageController::create().
-   */
-  public function create(array $values) {
-    // If there is no information about displays available add at least the
-    // default display.
-    $values += array(
-      'display' => array(
-        'default' => array(
-          'display_plugin' => 'default',
-          'id' => 'default',
-          'display_title' => 'Master',
-          'position' => 0,
-          'display_options' => array(),
-        ),
-      )
-    );
-
-    $entity = parent::create($values);
-
-    $this->mergeDefaultDisplaysOptions($entity);
-    return $entity;
-  }
-
-  /**
-   * Add defaults to the display options.
-   *
-   * @param \Drupal\Core\Entity\EntityInterface $entity
-   *   The view entity to attach default displays options.
-   */
-  protected function mergeDefaultDisplaysOptions(EntityInterface $entity) {
-    $displays = array();
-    foreach ($entity->get('display') as $key => $options) {
-      $options += array(
-        'display_options' => array(),
-        'display_plugin' => NULL,
-        'id' => NULL,
-        'display_title' => '',
-        'position' => NULL,
-      );
-      // Add the defaults for the display.
-      $displays[$key] = $options;
-    }
-    // Sort the displays.
-    uasort($displays, function ($display1, $display2) {
-      if ($display1['position'] != $display2['position']) {
-        return $display1['position'] < $display2['position'] ? -1 : 1;
-      }
-      return 0;
-    });
-    $entity->set('display', $displays);
-  }
 
 }
diff --git a/core/modules/views/lib/Drupal/views/ViewStorageInterface.php b/core/modules/views/lib/Drupal/views/ViewStorageInterface.php
index b09acb2..7009bae 100644
--- a/core/modules/views/lib/Drupal/views/ViewStorageInterface.php
+++ b/core/modules/views/lib/Drupal/views/ViewStorageInterface.php
@@ -25,4 +25,8 @@
    */
   public function &getDisplay($display_id);
 
+  /**
+   * Add defaults to the display options.
+   */
+  public function mergeDefaultDisplaysOptions();
 }
diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php
index 5877f28..0c02b6f 100644
--- a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php
+++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php
@@ -8,6 +8,7 @@
 namespace Drupal\views_ui;
 
 use Drupal\views\Views;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
 use Drupal\views\ViewExecutable;
 use Drupal\Core\Database\Database;
 use Drupal\Core\TypedData\TypedDataInterface;
@@ -1134,4 +1135,36 @@ public function setContext($name = NULL, TypedDataInterface $parent = NULL) {
   public function onChange($property_name) {
     $this->storage->onChange($property_name);
   }
+
+  public function preSave(EntityStorageControllerInterface $storage_controller) {
+    $this->storage->presave($storage_controller);
+  }
+
+  public function postSave(EntityStorageControllerInterface $storage_controller, $update = TRUE) {
+    $this->storage->postSave($storage_controller, $update);
+  }
+
+  public static function preCreate(EntityStorageControllerInterface $storage_controller, &$values) {
+  }
+
+  public function postCreate(EntityStorageControllerInterface $storage_controller) {
+    $this->storage->postCreate($storage_controller);
+  }
+
+  public static function preDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+  }
+
+  public static function postDelete(EntityStorageControllerInterface $storage_controller, $entities) {
+  }
+
+  public static function postLoad(EntityStorageControllerInterface $storage_controller, $entities) {
+  }
+
+  public function preSaveRevision(EntityStorageControllerInterface $storage_controller, \stdClass $record) {
+    $this->storage->preSaveRevision($storage_controller, $record);
+  }
+
+  public function mergeDefaultDisplaysOptions() {
+    $this->storage->mergeDefaultDisplaysOptions();
+  }
 }
