diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 6b62bf8..48f4382 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -2879,7 +2879,7 @@ function menu_load_links($menu_name) {
  */
 function menu_delete_links($menu_name) {
   $links = menu_load_links($menu_name);
-  menu_link_delete_multiple(array_keys($links), FALSE, TRUE);
+  menu_link_delete_multiple(array_keys($links), FALSE, FALSE);
 }
 
 /**
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
index 5487e04..3cb7c2e 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigStorageController.php
@@ -572,4 +572,16 @@ public function importDelete($name, Config $new_config, Config $old_config) {
     return TRUE;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function purge(EntityInterface $entity, $field, $instance) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBackendType() {
+    return 'config';
+  }
 }
diff --git a/core/lib/Drupal/Core/Database/Statement.php b/core/lib/Drupal/Core/Database/Statement.php
index b7c9fdd..8d7a6f3 100644
--- a/core/lib/Drupal/Core/Database/Statement.php
+++ b/core/lib/Drupal/Core/Database/Statement.php
@@ -54,6 +54,11 @@ public function execute($args = array(), $options = array()) {
       $query_start = microtime(TRUE);
     }
 
+    foreach ($args as $arg) {
+      if (!is_scalar($arg)) {
+        file_put_contents('/tmp/log', print_r(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), TRUE));
+      }
+    }
     $return = parent::execute($args);
 
     if (!empty($logger)) {
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
index 25ebce2..7e4ad33 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageController.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\Core\Entity;
 
+use Drupal\field\Plugin\Core\Entity\Field;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
 use PDO;
 use Drupal\Core\Entity\Query\QueryInterface;
 use Drupal\Component\Uuid\Uuid;
@@ -120,6 +122,8 @@ class DatabaseStorageController implements EntityStorageControllerInterface {
    *   The entity type for which the instance is created.
    */
   public function __construct($entityType) {
+    // Let's hope this or NG becomes a service...
+    $this->eventDispatcher = \Drupal::service('event_dispatcher');
     $this->entityType = $entityType;
     $this->entityInfo = entity_get_info($entityType);
     $this->entityCache = array();
@@ -173,27 +177,66 @@ public function resetCache(array $ids = NULL) {
    */
   public function load(array $ids = NULL) {
     $entities = array();
-
-    // Create a new variable which is either a prepared version of the $ids
-    // array for later comparison with the entity cache, or FALSE if no $ids
-    // were passed. The $ids array is reduced as items are loaded from cache,
-    // and we need to know if it's empty for this reason to avoid querying the
-    // database when all requested entities are loaded from cache.
-    $passed_ids = !empty($ids) ? array_flip($ids) : FALSE;
-    // Try to load entities from the static cache, if the entity type supports
-    // static caching.
-    if ($this->cache && $ids) {
-      $entities += $this->cacheGet($ids);
-      // If any entities were loaded, remove them from the ids still to load.
-      if ($passed_ids) {
-        $ids = array_keys(array_diff_key($passed_ids, $entities));
-      }
+    // If there are no ids, there are no keys to run any kind of cache get
+    // against.
+    if ($ids) {
+      $pre_load_event = new PreLoadEvent($this->entityType, $ids, $this->getBackendType());
+      // Preload entities from this class.
+      $this->preLoad($pre_load_event);
+      // Then allow subscribers to add more entities.
+      $this->dispatch('pre_load', $pre_load_event);
+      // Store the preloaded entities.
+      $entities += $pre_load_event->getEntities();
+      $ids_to_load = $pre_load_event->getIds();
+    }
+    else {
+      $ids_to_load = $ids;
     }
 
+    // Do the actual loading.
+    $queried_entities = $this->doLoad($ids_to_load);
+    // The entity objects themselves won't change beyond this point.
+    $entities += $this->postLoad($queried_entities);
+    // Allow subscribers to add more properties/fields to the queried
+    // entities.
+    $this->dispatch('post_load', new EventMultiple($this->entityType, $queried_entities, $this->getBackendType()));
+
+    // Ensure that the returned array is ordered the same as the original
+    // $ids array if this was passed in and remove any invalid ids.
+    if ($ids) {
+      $new_entities = array_flip($ids);
+      // Remove any ids failed to load from the array.
+      $new_entities = array_intersect_key($new_entities, $entities);
+      foreach ($entities as $entity) {
+        $new_entities[$entity->id()] = $entity;
+      }
+      $entities = $new_entities;
+    }
+
+    return $entities;
+  }
+
+  /**
+   * @param $ids
+   *   A list of entity IDs to load.
+   * @param $passed_ids
+   *
+   * @return array
+   */
+  protected function preLoad(PreLoadEvent $event) {
+    // Try to load entities from the static cache, if the entity type supports
+    // static caching.
+    if ($this->cache && ($ids = $event->getIds())) {
+      $event->addEntities($this->cacheGet($ids));
+    }
+  }
+
+  protected function doLoad($ids, $revision_id = FALSE) {
     // Load any remaining entities from the database. This is the case if $ids
     // is set to NULL (so we load all entities) or if there are any ids left to
     // load.
-    if ($ids === NULL || $ids) {
+    $queried_entities = array();
+    if ($ids === FALSE || $ids) {
       // Build and execute the query.
       $query_result = $this->buildQuery($ids)->execute();
 
@@ -205,34 +248,33 @@ public function load(array $ids = NULL) {
       }
       $queried_entities = $query_result->fetchAllAssoc($this->idKey);
     }
+    return $this->mapFromStorageRecords($queried_entities);
+  }
 
+  protected function mapFromStorageRecords(array $records, $load_revision = FALSE) {
+    return $records;
+  }
+
+  /**
+   * @param $queried_entities
+   *   An array of entities. The objects themselves might change (for example
+   *   DatabaseStorageControllerNG does this) so this is passed around by
+   *   reference.
+   * @param bool $load_revision
+   * @return
+   *   Array of loaded entities.
+   */
+  protected function postLoad($queried_entities, $load_revision = FALSE) {
     // Pass all entities loaded from the database through $this->attachLoad(),
     // which attaches fields (if supported by the entity type) and calls the
     // entity type specific load callback, for example hook_node_load().
-    if (!empty($queried_entities)) {
-      $this->attachLoad($queried_entities);
-      $entities += $queried_entities;
-    }
-
     if ($this->cache) {
       // Add entities to the cache.
       if (!empty($queried_entities)) {
         $this->cacheSet($queried_entities);
       }
     }
-
-    // Ensure that the returned array is ordered the same as the original
-    // $ids array if this was passed in and remove any invalid ids.
-    if ($passed_ids) {
-      // Remove any invalid ids from the array.
-      $passed_ids = array_intersect_key($passed_ids, $entities);
-      foreach ($entities as $entity) {
-        $passed_ids[$entity->id()] = $entity;
-      }
-      $entities = $passed_ids;
-    }
-
-    return $entities;
+    return $queried_entities;
   }
 
   /**
@@ -248,23 +290,12 @@ public function loadUnchanged($id) {
    * Implements \Drupal\Core\Entity\EntityStorageControllerInterface::loadRevision().
    */
   public function loadRevision($revision_id) {
-    // Build and execute the query.
-    $query_result = $this->buildQuery(array(), $revision_id)->execute();
-
-    if (!empty($this->entityInfo['class'])) {
-      // We provide the necessary arguments for PDO to create objects of the
-      // specified entity class.
-      // @see Drupal\Core\Entity\EntityInterface::__construct()
-      $query_result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['class'], array(array(), $this->entityType));
-    }
-    $queried_entities = $query_result->fetchAllAssoc($this->idKey);
-
-    // Pass the loaded entities from the database through $this->attachLoad(),
-    // which attaches fields (if supported by the entity type) and calls the
-    // entity type specific load callback, for example hook_node_load().
-    if (!empty($queried_entities)) {
-      $this->attachLoad($queried_entities, TRUE);
-    }
+    $queried_entities = $this->doLoad(array(), $revision_id);
+    // Process them.
+    $this->postLoad($queried_entities);
+    // Allow subscribers to add more properties/fields to the queried
+    // entities.
+    $this->dispatch('revision_load', new EventMultiple($this->entityType, $queried_entities, $this->getBackendType()));
     return reset($queried_entities);
   }
 
@@ -281,7 +312,7 @@ public function deleteRevision($revision_id) {
       db_delete($this->revisionTable)
         ->condition($this->revisionKey, $revision->getRevisionId())
         ->execute();
-      $this->invokeHook('revision_delete', $revision);
+      $this->notify('revision_delete', $revision);
     }
   }
 
@@ -344,11 +375,11 @@ protected function buildQuery($ids, $revision_id = FALSE) {
     }
 
     // Add fields from the {entity} table.
-    $entity_fields = $this->entityInfo['schema_fields_sql']['base_table'];
+    $entity_fields = drupal_schema_fields_sql($this->entityInfo['base_table']);
 
     if ($this->revisionKey) {
       // Add all fields from the {entity_revision} table.
-      $entity_revision_fields = drupal_map_assoc($this->entityInfo['schema_fields_sql']['revision_table']);
+      $entity_revision_fields = drupal_map_assoc(drupal_schema_fields_sql($this->entityInfo['revision_table']));
       // The id field is provided by entity, so remove it.
       unset($entity_revision_fields[$this->idKey]);
 
@@ -377,47 +408,6 @@ protected function buildQuery($ids, $revision_id = FALSE) {
   }
 
   /**
-   * Attaches data to entities upon loading.
-   *
-   * This will attach fields, if the entity is fieldable. It calls
-   * hook_entity_load() for modules which need to add data to all entities.
-   * It also calls hook_TYPE_load() on the loaded entities. For example
-   * hook_node_load() or hook_user_load(). If your hook_TYPE_load()
-   * expects special parameters apart from the queried entities, you can set
-   * $this->hookLoadArguments prior to calling the method.
-   * See Drupal\node\NodeStorageController::attachLoad() for an example.
-   *
-   * @param $queried_entities
-   *   Associative array of query results, keyed on the entity ID.
-   * @param $load_revision
-   *   (optional) TRUE if the revision should be loaded, defaults to FALSE.
-   */
-  protected function attachLoad(&$queried_entities, $load_revision = FALSE) {
-    // Attach fields.
-    if ($this->entityInfo['fieldable']) {
-      if ($load_revision) {
-        field_attach_load_revision($this->entityType, $queried_entities);
-      }
-      else {
-        field_attach_load($this->entityType, $queried_entities);
-      }
-    }
-
-    // Call hook_entity_load().
-    foreach (module_implements('entity_load') as $module) {
-      $function = $module . '_entity_load';
-      $function($queried_entities, $this->entityType);
-    }
-    // Call hook_TYPE_load(). The first argument for hook_TYPE_load() are
-    // always the queried entities, followed by additional arguments set in
-    // $this->hookLoadArguments.
-    $args = array_merge(array($queried_entities), $this->hookLoadArguments);
-    foreach (module_implements($this->entityType . '_load') as $module) {
-      call_user_func_array($module . '_' . $this->entityType . '_load', $args);
-    }
-  }
-
-  /**
    * Gets entities from the static cache.
    *
    * @param $ids
@@ -461,7 +451,7 @@ public function create(array $values) {
 
     // Modules might need to add or change the data initially held by the new
     // entity object, for instance to fill-in default values.
-    $this->invokeHook('create', $entity);
+    $this->notify('create', $entity);
 
     return $entity;
   }
@@ -477,10 +467,8 @@ public function delete(array $entities) {
     $transaction = db_transaction();
 
     try {
-      $this->preDelete($entities);
-      foreach ($entities as $id => $entity) {
-        $this->invokeHook('predelete', $entity);
-      }
+      $event = new EventMultiple($this->entityType, $entities, $this->getBackendType());
+      $this->dispatch('pre_delete', $event);
       $ids = array_keys($entities);
 
       db_delete($this->entityInfo['base_table'])
@@ -496,10 +484,7 @@ public function delete(array $entities) {
       // Reset the cache as soon as the changes have been applied.
       $this->resetCache($ids);
 
-      $this->postDelete($entities);
-      foreach ($entities as $id => $entity) {
-        $this->invokeHook('delete', $entity);
-      }
+      $this->dispatch('post_delete', $event);
       // Ignore slave server temporarily.
       db_ignore_slave();
     }
@@ -511,6 +496,14 @@ public function delete(array $entities) {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function purge(EntityInterface $entity, $field, $instance) {
+    $this->dispatch('purge', new PurgeEvent($entity, $field, $instance, $this->getBackendType()));
+  }
+
+
+  /**
    * Implements \Drupal\Core\Entity\EntityStorageControllerInterface::save().
    */
   public function save(EntityInterface $entity) {
@@ -521,8 +514,7 @@ public function save(EntityInterface $entity) {
         $entity->original = entity_load_unchanged($this->entityType, $entity->id());
       }
 
-      $this->preSave($entity);
-      $this->invokeHook('presave', $entity);
+      $this->notify('pre_save', $entity);
 
       if (!$entity->isNew()) {
         if ($entity->isDefaultRevision()) {
@@ -537,8 +529,7 @@ public function save(EntityInterface $entity) {
           $this->saveRevision($entity);
         }
         $this->resetCache(array($entity->id()));
-        $this->postSave($entity, TRUE);
-        $this->invokeHook('update', $entity);
+        $this->notify('update', $entity);
       }
       else {
         $return = drupal_write_record($this->entityInfo['base_table'], $entity);
@@ -549,8 +540,7 @@ public function save(EntityInterface $entity) {
         $this->resetCache(array());
 
         $entity->enforceIsNew(FALSE);
-        $this->postSave($entity, FALSE);
-        $this->invokeHook('insert', $entity);
+        $this->notify('insert', $entity);
       }
 
       // Ignore slave server temporarily.
@@ -586,7 +576,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);
+    $this->dispatch('pre_save_revision', new PreSaveRevisionEvent($record, $entity, $this->getBackendType()));
     $record = (array) $record;
 
     if ($entity->isNewRevision()) {
@@ -607,73 +597,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
-   *   One of 'presave', 'insert', 'update', 'predelete', or 'delete'.
-   * @param $entity
-   *   The entity object.
-   */
-  protected function invokeHook($hook, EntityInterface $entity) {
-    $function = 'field_attach_' . $hook;
-    // @todo: field_attach_delete_revision() is named the wrong way round,
-    // consider renaming it.
-    if ($function == 'field_attach_revision_delete') {
-      $function = 'field_attach_delete_revision';
-    }
-    if (!empty($this->entityInfo['fieldable']) && function_exists($function)) {
-      $function($entity);
-    }
-    // Invoke the hook.
-    module_invoke_all($this->entityType . '_' . $hook, $entity);
-    // Invoke the respective entity-level hook.
-    module_invoke_all('entity_' . $hook, $entity, $this->entityType);
-  }
-
-  /**
    * Implements \Drupal\Core\Entity\EntityStorageControllerInterface::getFieldDefinitions().
    */
   public function getFieldDefinitions(array $constraints) {
@@ -728,10 +651,39 @@ public function baseFieldDefinitions() {
     return array();
   }
 
+  public function updateField(Field $field, Field $original, $has_data) {
+    $this->dispatch('update_field', new UpdateFieldEvent($field, $original, $has_data));
+  }
+
+  public function createInstance(FieldInstance $instance) {
+    $this->dispatch('create_instance', new InstanceEvent($instance));
+  }
+
+  public function deleteInstance(FieldInstance $instance) {
+    $this->dispatch('delete_instance', new InstanceEvent($instance));
+  }
+
   /**
    * Implements \Drupal\Core\Entity\EntityStorageControllerInterface::getQueryServiceName().
    */
   public function getQueryServiceName() {
     return 'entity.query.field_sql_storage';
   }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getBackendType() {
+    return 'sql';
+  }
+
+  protected function notify($event_name, $entity) {
+    $this->dispatch($event_name, new Event($entity, $this->getBackendType()));
+  }
+
+  protected function dispatch($event_name, $event) {
+    $this->eventDispatcher->dispatch(constant('Drupal\Core\Entity\Events::' . strtoupper($event_name)), $event);
+    $this->eventDispatcher->dispatch("$this->entityType.entity." . strtolower($event_name), $event);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
index 1048ca1..1fbb4ef 100644
--- a/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
+++ b/core/lib/Drupal/Core/Entity/DatabaseStorageControllerNG.php
@@ -148,45 +148,6 @@ protected function buildPropertyQuery(QueryInterface $entity_query, array $value
   }
 
   /**
-   * Overrides DatabaseStorageController::attachLoad().
-   *
-   * Added mapping from storage records to entities.
-   */
-  protected function attachLoad(&$queried_entities, $load_revision = FALSE) {
-    // Map the loaded stdclass records into entity objects and according fields.
-    $queried_entities = $this->mapFromStorageRecords($queried_entities, $load_revision);
-
-    // Activate backward-compatibility mode to attach fields.
-    if ($this->entityInfo['fieldable']) {
-      // Prepare BC compatible entities before passing them to the field API.
-      $bc_entities = array();
-      foreach ($queried_entities as $key => $entity) {
-        $bc_entities[$key] = $entity->getBCEntity();
-      }
-
-      if ($load_revision) {
-        field_attach_load_revision($this->entityType, $bc_entities);
-      }
-      else {
-        field_attach_load($this->entityType, $bc_entities);
-      }
-    }
-
-    // Call hook_entity_load().
-    foreach (module_implements('entity_load') as $module) {
-      $function = $module . '_entity_load';
-      $function($queried_entities, $this->entityType);
-    }
-    // Call hook_TYPE_load(). The first argument for hook_TYPE_load() are
-    // always the queried entities, followed by additional arguments set in
-    // $this->hookLoadArguments.
-    $args = array_merge(array($queried_entities), $this->hookLoadArguments);
-    foreach (module_implements($this->entityType . '_load') as $module) {
-      call_user_func_array($module . '_' . $this->entityType . '_load', $args);
-    }
-  }
-
-  /**
    * Maps from storage records to entity objects.
    *
    * @param array $records
@@ -241,7 +202,7 @@ protected function attachPropertyData(array &$entities, $load_revision = FALSE)
 
       // Fetch the field definitions to check which field is translatable.
       $field_definition = $this->getFieldDefinitions(array());
-      $data_fields = array_flip($this->entityInfo['schema_fields_sql']['data_table']);
+      $data_fields = array_flip(drupal_schema_fields_sql($this->entityInfo['data_table']));
 
       foreach ($data as $values) {
         $id = $values[$this->idKey];
@@ -286,8 +247,7 @@ public function save(EntityInterface $entity) {
         $entity->original = entity_load_unchanged($this->entityType, $entity->id());
       }
 
-      $this->preSave($entity);
-      $this->invokeHook('presave', $entity);
+      $this->notify('pre_save', $entity);
 
       // Create the storage record to be saved.
       $record = $this->mapToStorageRecord($entity);
@@ -308,8 +268,7 @@ public function save(EntityInterface $entity) {
           $this->savePropertyData($entity);
         }
         $this->resetCache(array($entity->id()));
-        $this->postSave($entity, TRUE);
-        $this->invokeHook('update', $entity);
+        $this->notify('update', $entity);
       }
       else {
         $return = drupal_write_record($this->entityInfo['base_table'], $record);
@@ -326,8 +285,7 @@ public function save(EntityInterface $entity) {
         $this->resetCache(array());
 
         $entity->enforceIsNew(FALSE);
-        $this->postSave($entity, FALSE);
-        $this->invokeHook('insert', $entity);
+        $this->notify('insert', $entity);
       }
 
       // Ignore slave server temporarily.
@@ -361,7 +319,7 @@ protected function saveRevision(EntityInterface $entity) {
       $record->{$this->revisionKey} = NULL;
     }
 
-    $this->preSaveRevision($record, $entity);
+    $this->dispatch('pre_save_revision', new PreSaveRevisionEvent($record, $entity, $this->getBackendType()));
 
     if ($entity->isNewRevision()) {
       drupal_write_record($this->revisionTable, $record);
@@ -439,7 +397,7 @@ protected function invokeHook($hook, EntityInterface $entity) {
    */
   protected function mapToStorageRecord(EntityInterface $entity) {
     $record = new \stdClass();
-    foreach ($this->entityInfo['schema_fields_sql']['base_table'] as $name) {
+    foreach (drupal_schema_fields_sql($this->entityInfo['base_table']) as $name) {
       $record->$name = $entity->$name->value;
     }
     return $record;
@@ -456,7 +414,7 @@ protected function mapToStorageRecord(EntityInterface $entity) {
    */
   protected function mapToRevisionStorageRecord(EntityInterface $entity) {
     $record = new \stdClass();
-    foreach ($this->entityInfo['schema_fields_sql']['revision_table'] as $name) {
+    foreach (drupal_schema_fields_sql($this->entityInfo['revision_table']) as $name) {
       if (isset($entity->$name->value)) {
         $record->$name = $entity->$name->value;
       }
@@ -482,7 +440,7 @@ protected function mapToDataStorageRecord(EntityInterface $entity, $langcode) {
     $translation = $entity->getTranslation($langcode, FALSE);
 
     $record = new \stdClass();
-    foreach ($this->entityInfo['schema_fields_sql']['data_table'] as $name) {
+    foreach (drupal_schema_fields_sql($this->entityInfo['data_table']) as $name) {
       $record->$name = $translation->$name->value;
     }
     $record->langcode = $langcode;
@@ -507,9 +465,8 @@ public function delete(array $entities) {
         $entities[$id] = $entity->getNGEntity();
       }
 
-      $this->preDelete($entities);
       foreach ($entities as $id => $entity) {
-        $this->invokeHook('predelete', $entity);
+        $this->notify('pre_delete', $entity);
       }
       $ids = array_keys($entities);
 
@@ -532,17 +489,17 @@ public function delete(array $entities) {
       // Reset the cache as soon as the changes have been applied.
       $this->resetCache($ids);
 
-      $this->postDelete($entities);
-      foreach ($entities as $id => $entity) {
-        $this->invokeHook('delete', $entity);
-      }
+
+      $this->dispatch('post_delete', new EventMultiple($this->entityType, $entities, $this->getBackendType()));
+
       // Ignore slave server temporarily.
       db_ignore_slave();
     }
-    catch (Exception $e) {
+    catch (\Exception $e) {
       $transaction->rollback();
       watchdog_exception($this->entityType, $e);
       throw new EntityStorageException($e->getMessage, $e->getCode, $e);
     }
   }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/Entity.php b/core/lib/Drupal/Core/Entity/Entity.php
index 146edee..e08359c 100644
--- a/core/lib/Drupal/Core/Entity/Entity.php
+++ b/core/lib/Drupal/Core/Entity/Entity.php
@@ -344,6 +344,13 @@ public function delete() {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function purge($field, $instance) {
+    return drupal_container()->get('plugin.manager.entity')->getStorageController($this->entityType)->purge($this, $field, $instance);
+  }
+
+  /**
    * Implements \Drupal\Core\Entity\EntityInterface::createDuplicate().
    */
   public function createDuplicate() {
diff --git a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php
index 9c48753..d1c1fe5 100644
--- a/core/lib/Drupal/Core/Entity/EntityBCDecorator.php
+++ b/core/lib/Drupal/Core/Entity/EntityBCDecorator.php
@@ -468,4 +468,11 @@ public function isTranslatable() {
     return $this->decorated->isTranslatable();
   }
 
+  /**
+   * Forwards the call to the decorated entity.
+   */
+  public function purge($field, $instance) {
+    $this->decorated->purge($field, $instance);
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/EntityInterface.php b/core/lib/Drupal/Core/Entity/EntityInterface.php
index 4c02da7..1d0ea5d 100644
--- a/core/lib/Drupal/Core/Entity/EntityInterface.php
+++ b/core/lib/Drupal/Core/Entity/EntityInterface.php
@@ -156,6 +156,20 @@ public function save();
   public function delete();
 
   /**
+   * Purges the field data for a single field on a single entity.
+   *
+   * This is basically the same as field_attach_delete() except it only applies to
+   * a single field. The entity itself is not being deleted, and it is quite
+   * possible that other field data will remain attached to it.
+   *
+   * @param $field
+   *   The (possibly deleted) field whose data is being purged.
+   * @param $instance
+   *   The deleted field instance whose data is being purged.
+   */
+  public function purge($field, $instance);
+
+  /**
    * Creates a duplicate of the entity.
    *
    * @return \Drupal\Core\Entity\EntityInterface
diff --git a/core/lib/Drupal/Core/Entity/EntityManager.php b/core/lib/Drupal/Core/Entity/EntityManager.php
index 6adeb96..ef7450f 100644
--- a/core/lib/Drupal/Core/Entity/EntityManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityManager.php
@@ -9,7 +9,6 @@
 
 use Drupal\Component\Plugin\PluginManagerBase;
 use Drupal\Component\Plugin\Factory\DefaultFactory;
-use Drupal\Component\Plugin\Discovery\ProcessDecorator;
 use Drupal\Core\Plugin\Discovery\AlterDecorator;
 use Drupal\Core\Plugin\Discovery\CacheDecorator;
 use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
@@ -52,7 +51,6 @@ public function __construct(array $namespaces) {
     );
     $this->discovery = new AnnotatedClassDiscovery('Core', 'Entity', $namespaces, $annotation_namespaces, 'Drupal\Core\Entity\Annotation\EntityType');
     $this->discovery = new InfoHookDecorator($this->discovery, 'entity_info');
-    $this->discovery = new ProcessDecorator($this->discovery, array($this, 'processDefinition'));
     $this->discovery = new AlterDecorator($this->discovery, 'entity_info');
     $this->discovery = new CacheDecorator($this->discovery, 'entity_info:' . language(LANGUAGE_TYPE_INTERFACE)->langcode, 'cache', CacheBackendInterface::CACHE_PERMANENT, array('entity_info' => TRUE));
 
@@ -60,25 +58,6 @@ public function __construct(array $namespaces) {
   }
 
   /**
-   * Overrides Drupal\Component\Plugin\PluginManagerBase::processDefinition().
-   */
-  public function processDefinition(&$definition, $plugin_id) {
-    parent::processDefinition($definition, $plugin_id);
-
-    // Prepare entity schema fields SQL info for
-    // Drupal\Core\Entity\DatabaseStorageControllerInterface::buildQuery().
-    if (isset($definition['base_table'])) {
-      $definition['schema_fields_sql']['base_table'] = drupal_schema_fields_sql($definition['base_table']);
-      if (isset($definition['data_table'])) {
-        $definition['schema_fields_sql']['data_table'] = drupal_schema_fields_sql($definition['data_table']);
-      }
-      if (isset($definition['revision_table'])) {
-        $definition['schema_fields_sql']['revision_table'] = drupal_schema_fields_sql($definition['revision_table']);
-      }
-    }
-  }
-
-  /**
    * Checks whether a certain entity type has a certain controller.
    *
    * @param string $entity_type
diff --git a/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php b/core/lib/Drupal/Core/Entity/EntityStorageControllerInterface.php
index 4ac5d9a..e707e62 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.
@@ -111,6 +111,22 @@ public function create(array $values);
   public function delete(array $entities);
 
   /**
+   * Purges the field data for a single field on a single pseudo-entity.
+   *
+   * This is basically the same as field_attach_delete() except it only applies to
+   * a single field. The entity itself is not being deleted, and it is quite
+   * possible that other field data will remain attached to it.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The pseudo-entity whose field data is being purged.
+   * @param $field
+   *   The (possibly deleted) field whose data is being purged.
+   * @param $instance
+   *   The deleted field instance whose data is being purged.
+   */
+  public function purge(EntityInterface $entity, $field, $instance);
+
+  /**
    * Saves the entity permanently.
    *
    * @param \Drupal\Core\Entity\EntityInterface $entity
@@ -166,4 +182,9 @@ public function getFieldDefinitions(array $constraints);
    */
   public function getQueryServicename();
 
+  /**
+   * Return the backend type, for example 'sql' or 'couchdb'.
+   */
+  public function getBackendType();
+
 }
diff --git a/core/lib/Drupal/Core/Entity/Event.php b/core/lib/Drupal/Core/Entity/Event.php
new file mode 100644
index 0000000..7ef8223
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Event.php
@@ -0,0 +1,55 @@
+<?php
+/**
+ * Contains \Drupal\Core\Entity\Event.
+ */
+
+namespace Drupal\Core\Entity;
+
+use Symfony\Component\EventDispatcher\Event as BaseEvent;
+
+/**
+ * An entity storage event.
+ */
+class Event extends BaseEvent {
+
+  /**
+   * Entity object.
+   *
+   * @var \Drupal\Core\Entity\Entity
+   */
+  protected $entity;
+
+  /**
+   * @var string
+   */
+  protected $backendType;
+
+  /**
+   * Constructs an entity event object.
+   */
+  public function __construct(EntityInterface $entity, $backend_type) {
+    $this->entity = $entity;
+    $this->backendType = $backend_type;
+  }
+
+  /**
+   * Get entity.
+   */
+  public function getEntity() {
+    return $this->entity;
+  }
+
+  /**
+   * @return $this
+   */
+  public function getBCEntity() {
+    return $this->entity->getBCEntity();
+  }
+
+  /**
+   * @return mixed
+   */
+  public function getBackendType() {
+    return $this->backendType;
+  }
+}
diff --git a/core/lib/Drupal/Core/Entity/EventMultiple.php b/core/lib/Drupal/Core/Entity/EventMultiple.php
new file mode 100644
index 0000000..88afb62
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/EventMultiple.php
@@ -0,0 +1,101 @@
+<?php
+
+/**
+ * Contains \Drupal\Core\Entity\LoadEvent.
+ */
+
+namespace Drupal\Core\Entity;
+
+use Symfony\Component\EventDispatcher\Event as BaseEvent;
+
+/**
+ * This event is used to add additional data to freshly loaded entities.
+ *
+ * No additional entities can be added during the event. Cached entities
+ * loaded during PreLoadEvent are not passed through this event.
+ */
+class EventMultiple extends BaseEvent {
+
+  /**
+   * Entity objects.
+   *
+   * @var array
+   */
+  protected $entities;
+
+  /**
+   * The entity type.
+   *
+   * @var string
+   */
+  protected $entityType;
+
+  /**
+   * @var string
+   */
+  protected $backendType;
+
+  /**
+   * Constructs a load event object.
+   */
+  public function __construct($entity_type, $entities, $backend_type) {
+    $this->entityType = $entity_type;
+    $this->entities = $entities;
+    $this->backendType = $backend_type;
+  }
+
+  /**
+   * Get entities.
+   */
+  public function getEntities() {
+    return $this->entities;
+  }
+
+  /**
+   * @return array
+   */
+  public function getBCEntities() {
+    $bc_entities = array();
+    foreach ($this->entities as $key => $entity) {
+      $bc_entities[$key] = $entity->getBCEntity();
+    }
+    return $bc_entities;
+  }
+
+  /**
+   * @return mixed
+   */
+  public function getEntityType() {
+    return $this->entityType;
+  }
+
+  /**
+   * @return array
+   */
+  public function getIDs() {
+    $ids = array();
+    foreach ($this->entities as $entity) {
+      $ids[$entity->id()] = $entity->id();
+    }
+    return $ids;
+  }
+
+  /**
+   * @return array
+   */
+  public function getRevisionIDs() {
+    $ids = array();
+    foreach ($this->entities as $entity) {
+      $ids[$entity->getRevisionId()] = $entity->getRevisionId();
+    }
+    return $ids;
+  }
+
+  /**
+   * @return string
+   */
+  public function getBackendType() {
+    return $this->backendType;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/Events.php b/core/lib/Drupal/Core/Entity/Events.php
new file mode 100644
index 0000000..e208c7a
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/Events.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * Contains \Drupal\Core\Entity\Events.
+ */
+
+namespace Drupal\Core\Entity;
+
+
+/**
+ * Class Events
+ * @package Drupal\Core\Entity
+ */
+final class Events {
+
+  const CREATE = 'entity.create';
+
+  const CREATE_INSTANCE = 'entity.create_instance';
+
+  const DELETE_INSTANCE = 'entity.delete_instance';
+
+  const POST_LOAD = 'entity.post_load';
+
+  const PRE_LOAD = 'entity.pre_load';
+
+  const INSERT = 'entity.insert';
+
+  const PRE_DELETE = 'entity.pre_delete';
+
+  const PRE_SAVE = 'entity.pre_save';
+
+  const PRE_SAVE_REVISION = 'entity.pre_save_revision';
+
+  const POST_DELETE = 'entity.post_delete';
+
+  const POST_DELETE_REVISION = 'entity.post_revision_delete';
+
+  const PURGE = 'entity.purge';
+
+  const REVISION_LOAD = 'entity.revision_load';
+
+  const UPDATE = 'entity.update';
+
+  const UPDATE_FIELD = 'entity.update_field';
+
+}
diff --git a/core/lib/Drupal/Core/Entity/InstanceEvent.php b/core/lib/Drupal/Core/Entity/InstanceEvent.php
new file mode 100644
index 0000000..9c79110
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/InstanceEvent.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * Contains \Drupal\Core\Entity\InstanceEvent.
+ */
+
+namespace Drupal\Core\Entity;
+
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
+use Symfony\Component\EventDispatcher\Event as BaseEvent;
+
+/**
+ *
+ */
+class InstanceEvent extends BaseEvent {
+
+  /**
+   * @var \Drupal\field\Plugin\Core\Entity\FieldInstance;
+   */
+  protected $instance;
+
+  /**
+   * @param \Drupal\field\Plugin\Core\Entity\FieldInstance $instance
+   */
+  function __construct($instance) {
+    $this->instance = $instance;
+  }
+
+  /**
+   * @return \Drupal\field\Plugin\Core\Entity\FieldInstance
+   */
+  public function getInstance() {
+    return $this->instance;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/PreLoadEvent.php b/core/lib/Drupal/Core/Entity/PreLoadEvent.php
new file mode 100644
index 0000000..d834dec
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/PreLoadEvent.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * Contains \Drupal\Core\Entity\PreLoadEvent.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * This event is used to load additional full entities for eg. from cache.
+ *
+ * These entities will not be changed any further during load.
+ */
+class PreLoadEvent extends EventMultiple {
+
+  /**
+   * @var array
+   */
+  protected $ids;
+
+  /**
+   * @param string $entity_type
+   * @param array $ids
+   * @param string $backend_type
+   */
+  public function __construct($entity_type, array $ids, $backend_type) {
+    parent::__construct($entity_type, array(), $backend_type);
+    $this->ids = array_combine($ids, $ids);
+  }
+
+  public function getIds() {
+    return $this->ids;
+  }
+
+  /**
+   * @param array $entities
+   */
+  public function addEntities(array $entities) {
+    $this->ids = array_diff_key($this->ids, $this->entities);
+    $this->entities += $entities;
+  }
+
+  /**
+   * Adds an entity to the existing entities.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   */
+  public function addEntity(EntityInterface $entity) {
+    $id = $entity->id();
+    unset($this->ids[$id]);
+    $this->entities[$id] = $entity;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/PreSaveRevisionEvent.php b/core/lib/Drupal/Core/Entity/PreSaveRevisionEvent.php
new file mode 100644
index 0000000..a86b464
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/PreSaveRevisionEvent.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * Contains \Drupal\Core\Entity\Events.
+ */
+
+namespace Drupal\Core\Entity;
+
+/**
+ * Event firing before a revision save.
+ */
+class PreSaveRevisionEvent extends Event {
+
+  /**
+   * @var \stdClass
+   */
+  protected $record;
+
+  /**
+   * @param \stdClass $record
+   * @param EntityInterface $entity
+   * @param $backend_type
+   */
+  function __construct(\stdClass $record, EntityInterface $entity, $backend_type) {
+    parent::__construct($entity, $backend_type);
+    $this->record = $record;
+  }
+
+  /**
+   * @return \stdClass
+   */
+  function getRecord() {
+    return $this->record;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/PurgeEvent.php b/core/lib/Drupal/Core/Entity/PurgeEvent.php
new file mode 100644
index 0000000..0694fc9
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/PurgeEvent.php
@@ -0,0 +1,42 @@
+<?php
+
+
+namespace Drupal\Core\Entity;
+
+
+use Drupal\field\Plugin\Core\Entity\Field;
+use Drupal\field\Plugin\Core\Entity\FieldInstance;
+
+class PurgeEvent extends Event {
+
+  /**
+   * @var \Drupal\field\Plugin\Core\Entity\Field
+   */
+  protected $field;
+
+  /**
+   * @var \Drupal\field\Plugin\Core\Entity\FieldInstance
+   */
+  protected $instance;
+
+  function __construct($entity, Field $field, FieldInstance $instance, $backend_type) {
+    parent::__construct($entity, $backend_type);
+    $this->field = $field;
+    $this->instance = $instance;
+  }
+
+  /**
+   * @return \Drupal\field\Plugin\Core\Entity\Field
+   */
+  public function getField() {
+    return $this->field;
+  }
+
+  /**
+   * @return \Drupal\field\Plugin\Core\Entity\FieldInstance
+   */
+  public function getInstance() {
+    return $this->instance;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Entity/UpdateFieldEvent.php b/core/lib/Drupal/Core/Entity/UpdateFieldEvent.php
new file mode 100644
index 0000000..254adf6
--- /dev/null
+++ b/core/lib/Drupal/Core/Entity/UpdateFieldEvent.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * Contains \Drupal\Core\Entity\UpdateFieldEvent.
+ */
+
+namespace Drupal\Core\Entity;
+
+use Drupal\field\Plugin\Core\Entity\Field;
+use Symfony\Component\EventDispatcher\Event as BaseEvent;
+
+/**
+ *
+ */
+class UpdateFieldEvent extends BaseEvent {
+
+  /**
+   * @var \Drupal\Core\Entity\Field\Type\Field
+   */
+  protected $field;
+
+  /**
+   * @var \Drupal\Core\Entity\Field\Type\Field
+   */
+  protected $original;
+
+  /**
+   * @var bool
+   */
+  protected $hasData;
+
+  /**
+   * @param \Drupal\Core\Entity\Field\Type\Field $field
+   * @param \Drupal\Core\Entity\Field\Type\Field $original
+   * @param bool $has_data
+   */
+  function __construct(Field $field, Field $original, $has_data) {
+    $this->field = $field;
+    $this->original = $original;
+    $this->hasData = $has_data;
+  }
+
+  /**
+   * @return \Drupal\Core\Entity\Field\Type\Field
+   */
+  public function getField() {
+    return $this->field;
+  }
+
+  /**
+   * @return \Drupal\Core\Entity\Field\Type\Field
+   */
+  public function getOriginal() {
+    return $this->original;
+  }
+
+  /**
+   * @return boolean
+   */
+  public function hasData() {
+    return $this->hasData;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Utility/SchemaCache.php b/core/lib/Drupal/Core/Utility/SchemaCache.php
index 3c252b1..c0c2ec6 100644
--- a/core/lib/Drupal/Core/Utility/SchemaCache.php
+++ b/core/lib/Drupal/Core/Utility/SchemaCache.php
@@ -32,4 +32,5 @@ protected function resolveCacheMiss($offset) {
     $this->persist($offset);
     return $value;
   }
+
 }
diff --git a/core/modules/aggregator/aggregator.services.yml b/core/modules/aggregator/aggregator.services.yml
index 1cedc0f..fe2ccd8 100644
--- a/core/modules/aggregator/aggregator.services.yml
+++ b/core/modules/aggregator/aggregator.services.yml
@@ -8,3 +8,10 @@ services:
   plugin.manager.aggregator.processor:
     class: Drupal\aggregator\Plugin\AggregatorPluginManager
     arguments: [processor, '%container.namespaces%']
+  aggregator.storage_subscriber:
+    class: Drupal\aggregator\StorageSubscriber
+    arguments: ['@plugin.manager.entity', '@plugin.manager.aggregator.processor', '@config.storage', '@config.factory']
+    calls:
+      - [setContainer, ['@service_container']]
+    tags:
+      - { name: event_subscriber }
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/FeedStorageController.php b/core/modules/aggregator/lib/Drupal/aggregator/FeedStorageController.php
index 82a5643..40917d5 100644
--- a/core/modules/aggregator/lib/Drupal/aggregator/FeedStorageController.php
+++ b/core/modules/aggregator/lib/Drupal/aggregator/FeedStorageController.php
@@ -30,88 +30,31 @@ public function create(array $values) {
     return parent::create($values);
   }
 
-  /**
-   * 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();
+  public function loadCategories($entities) {
+    foreach ($entities as $entity) {
+      $entity->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' => $entity->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);
+  public function sasaveCategories($feed_id, array $categories) {
+    foreach ($categories as $cid => $value) {
+      if ($value) {
+        db_insert('aggregator_category_feed')
+          ->fields(array(
+            'fid' => $feed_id,
+            'cid' => $cid,
+          ))
+          ->execute();
       }
     }
   }
 
-  /**
-   * 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.
+  public function deleteCategories($feed_id) {
     db_delete('aggregator_category_feed')
-      ->condition('fid', $entity->id())
+      ->condition('fid', $feed_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();
-        }
-      }
-    }
-  }
 
   /**
    * Implements Drupal\Core\Entity\DataBaseStorageControllerNG::baseFieldDefinitions().
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/StorageSubscriber.php b/core/modules/aggregator/lib/Drupal/aggregator/StorageSubscriber.php
new file mode 100644
index 0000000..98046ee
--- /dev/null
+++ b/core/modules/aggregator/lib/Drupal/aggregator/StorageSubscriber.php
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\aggregator\FeedStorageController.
+ */
+
+namespace Drupal\aggregator;
+
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Config\StorageInterface;
+use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\Event;
+use Drupal\Core\Entity\EventMultiple;
+use Drupal\aggregator\Plugin\AggregatorPluginManager;
+use Symfony\Component\DependencyInjection\Container;
+use Symfony\Component\DependencyInjection\ContainerAware;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class StorageSubscriber extends ContainerAware implements EventSubscriberInterface {
+
+  public function __construct(EntityManager $entity_manager, AggregatorPluginManager $aggregator_processor_manager, StorageInterface $config_storage, ConfigFactory $config_factory) {
+    $this->entityManager = $entity_manager;
+    $this->aggregatorProcessorManager = $aggregator_processor_manager;
+    $this->configStorage = $config_storage;
+    $this->configFactory = $config_factory;
+  }
+
+  /**
+   * Invalidate the block cache to update aggregator feed-based derivatives.
+   */
+  protected function clearBlockCacheDefinitions() {
+    if ($block_manager = $this->container->get('plugin.manager.block', Container::NULL_ON_INVALID_REFERENCE)) {
+      $block_manager->clearCachedDefinitions();
+    }
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\DataBaseStorageController::preDelete().
+   */
+  protected function preDelete($entities) {
+    $this->clearBlockCacheDefinitions();
+    foreach ($entities as $entity) {
+      // Notify processors to remove stored items.
+      foreach ($this->aggregatorProcessorManager->getDefinitions() as $id => $definition) {
+        $this->aggregatorProcessorManager->createInstance($id)->remove($entity);
+      }
+    }
+  }
+
+  public function postLoad(EventMultiple $event) {
+    $this->entityManager->getStorageController('aggregator_feed')->loadCategories($event->getEntities());
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\DataBaseStorageController::postDelete().
+   */
+  public function postDelete($entities) {
+    foreach ($entities as $entity) {
+      // Make sure there is no active block for this feed.
+      $block_configs = $this->configStorage->listAll('plugin.core.block');
+      foreach ($block_configs as $config_id) {
+        $config = $this->configFactory->get($config_id);
+        if ($config->get('id') == 'aggregator_feed_block:' . $entity->id()) {
+          $config->delete();
+        }
+      }
+    }
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\DataBaseStorageController::preSave().
+   */
+  public function preSave(Event $event) {
+    $this->clearBlockCacheDefinitions();
+    // An existing feed is being modified, delete the category listings.
+    $this->entityManager->getStorageController('aggregator_feed')->deleteCategories($event->getEntity()->id());
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\DataBaseStorageController::postSave().
+   */
+  public function postSave(Event $event) {
+    $entity = $event->getEntity();
+    if (!empty($entity->categories)) {
+      $this->entityManager->getStorageController('aggregator_feed')->saveCategories($entity->id(), $entity->categories);
+    }
+  }
+
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events['aggregator_feed.entity.insert'] = 'postSave';
+    $events['aggregator_feed.entity.post_delete'] = 'postDelete';
+    $events['aggregator_feed.entity.post_load'] = 'postLoad';
+    $events['aggregator_feed.entity.pre_delete'] = 'preDelete';
+    $events['aggregator_feed.entity.pre_save'] = 'preSave';
+    $events['aggregator_feed.entity.post_load'] = 'postLoad';
+    $events['aggregator_feed.entity.update'] = 'postSave';
+  }
+}
diff --git a/core/modules/comment/comment.module b/core/modules/comment/comment.module
index 5690c72..02cc29b 100644
--- a/core/modules/comment/comment.module
+++ b/core/modules/comment/comment.module
@@ -1154,41 +1154,6 @@ function comment_form_node_form_alter(&$form, $form_state) {
 }
 
 /**
- * Implements hook_node_load().
- */
-function comment_node_load($nodes, $types) {
-  $comments_enabled = array();
-
-  // Check if comments are enabled for each node. If comments are disabled,
-  // assign values without hitting the database.
-  foreach ($nodes as $node) {
-    // Store whether comments are enabled for this node.
-    if ($node->comment != COMMENT_NODE_HIDDEN) {
-      $comments_enabled[] = $node->nid;
-    }
-    else {
-      $node->cid = 0;
-      $node->last_comment_timestamp = $node->created;
-      $node->last_comment_name = '';
-      $node->last_comment_uid = $node->uid;
-      $node->comment_count = 0;
-    }
-  }
-
-  // For nodes with comments enabled, fetch information from the database.
-  if (!empty($comments_enabled)) {
-    $result = db_query('SELECT nid, cid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count FROM {node_comment_statistics} WHERE nid IN (:comments_enabled)', array(':comments_enabled' => $comments_enabled));
-    foreach ($result as $record) {
-      $nodes[$record->nid]->cid = $record->cid;
-      $nodes[$record->nid]->last_comment_timestamp = $record->last_comment_timestamp;
-      $nodes[$record->nid]->last_comment_name = $record->last_comment_name;
-      $nodes[$record->nid]->last_comment_uid = $record->last_comment_uid;
-      $nodes[$record->nid]->comment_count = $record->comment_count;
-    }
-  }
-}
-
-/**
  * Implements hook_node_prepare().
  */
 function comment_node_prepare(EntityInterface $node) {
@@ -1198,37 +1163,6 @@ function comment_node_prepare(EntityInterface $node) {
 }
 
 /**
- * Implements hook_node_insert().
- */
-function comment_node_insert(EntityInterface $node) {
-  // Allow bulk updates and inserts to temporarily disable the
-  // maintenance of the {node_comment_statistics} table.
-  if (variable_get('comment_maintain_node_statistics', TRUE)) {
-    db_insert('node_comment_statistics')
-      ->fields(array(
-        'nid' => $node->nid,
-        'cid' => 0,
-        'last_comment_timestamp' => $node->changed,
-        'last_comment_name' => NULL,
-        'last_comment_uid' => $node->uid,
-        'comment_count' => 0,
-      ))
-      ->execute();
-  }
-}
-
-/**
- * Implements hook_node_predelete().
- */
-function comment_node_predelete(EntityInterface $node) {
-  $cids = db_query('SELECT cid FROM {comment} WHERE nid = :nid', array(':nid' => $node->nid))->fetchCol();
-  comment_delete_multiple($cids);
-  db_delete('node_comment_statistics')
-    ->condition('nid', $node->nid)
-    ->execute();
-}
-
-/**
  * Implements hook_node_update_index().
  */
 function comment_node_update_index(EntityInterface $node, $langcode) {
@@ -1316,14 +1250,6 @@ function comment_user_cancel($edit, $account, $method) {
 }
 
 /**
- * Implements hook_user_predelete().
- */
-function comment_user_predelete($account) {
-  $cids = db_query('SELECT c.cid FROM {comment} c WHERE uid = :uid', array(':uid' => $account->uid))->fetchCol();
-  comment_delete_multiple($cids);
-}
-
-/**
  * Accepts a submission of new or changed comment content.
  *
  * @param Drupal\comment\Comment $comment
diff --git a/core/modules/comment/comment.services.yml b/core/modules/comment/comment.services.yml
new file mode 100644
index 0000000..3495dd7
--- /dev/null
+++ b/core/modules/comment/comment.services.yml
@@ -0,0 +1,10 @@
+services:
+  comment.storage_subscriber:
+    class: Drupal\comment\StorageSubscriber
+    arguments: ['@plugin.manager.entity', '@lock', '@module_handler']
+    tags:
+      - { name: event_subscriber }
+  comment.entity_subscriber:
+    class: Drupal\comment\EventSubscriber\EntitySubscriber
+    tags:
+      - { name: event_subscriber }
diff --git a/core/modules/comment/lib/Drupal/comment/CommentStorageController.php b/core/modules/comment/lib/Drupal/comment/CommentStorageController.php
index c005cb0..faa6630 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.
    */
@@ -39,19 +39,6 @@ protected function buildQuery($ids, $revision_id = FALSE) {
   }
 
   /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::attachLoad().
-   */
-  protected function attachLoad(&$records, $load_revision = FALSE) {
-    // Prepare standard comment fields.
-    foreach ($records as $key => $record) {
-      $record->name = $record->uid ? $record->registered_name : $record->name;
-      $record->node_type = 'comment_node_' . $record->node_type;
-      $records[$key] = $record;
-    }
-    parent::attachLoad($records, $load_revision);
-  }
-
-  /**
    * Overrides Drupal\Core\Entity\DatabaseStorageControllerNG::create().
    */
   public function create(array $values) {
@@ -63,145 +50,36 @@ public function create(array $values) {
   }
 
   /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::preSave().
-   *
-   * @see comment_int_to_alphadecimal()
-   * @see comment_alphadecimal_to_int()
+   * {@inheritdoc}
    */
-  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();
-    }
+  public function getMaxThread(EntityInterface $comment) {
+    return db_query('SELECT MAX(thread) FROM {comment} WHERE nid = :nid', array(':nid' => $comment->nid->target_id))->fetchField();
   }
 
   /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave().
+   * {@inheritdoc}
    */
-  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);
-    }
+  public function getMaxThreadPerThread(EntityInterface $comment) {
+    return db_query("SELECT MAX(thread) FROM {comment} WHERE thread LIKE :thread AND nid = :nid", array(
+      ':thread' => $comment->pid->entity->thread->value . '.%',
+      ':nid' => $comment->nid->target_id,
+    ))->fetchField();
   }
 
   /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::postDelete().
+   * {@inheritdoc}
    */
-  protected function postDelete($comments) {
-    // Delete the comments' replies.
+  public function getChildCids(array $comments) {
     $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.
+   * {@inheritdoc}
    */
-  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,13 +125,18 @@ protected function updateNodeStatistics($nid) {
   }
 
   /**
-   * Release the lock acquired for the thread in preSave().
+   * {@inheritdoc}
    */
-  protected function releaseThreadLock() {
-    if ($this->threadLock) {
-      lock()->release($this->threadLock);
-      $this->threadLock = '';
+  public function mapFromStorageRecords(array $records, $load_revision = FALSE) {
+    // Prepare standard comment fields. Comment module is a weirdo and changes
+    // the bundle after load -- there is no method in EntityNG to change the
+    // bundle of an entity so the postLoad subscriber acting on NG entities
+    // can not change the bundle.
+    foreach ($records as $comment) {
+      $comment->name = $comment->uid ? $comment->registered_name : $comment->name;
+      $comment->node_type = 'comment_node_' . $comment->node_type;
     }
+    return parent::mapFromStorageRecords($records, $load_revision);
   }
 
   /**
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/EventSubscriber/EntitySubscriber.php b/core/modules/comment/lib/Drupal/comment/EventSubscriber/EntitySubscriber.php
new file mode 100644
index 0000000..a09388d
--- /dev/null
+++ b/core/modules/comment/lib/Drupal/comment/EventSubscriber/EntitySubscriber.php
@@ -0,0 +1,94 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\comment\EventSubscriber\EntitySubscriber.
+ */
+
+namespace Drupal\comment\EventSubscriber;
+
+use Drupal\Core\Entity\Event;
+use Drupal\Core\Entity\EventMultiple;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class EntitySubscriber implements EventSubscriberInterface {
+
+  public function load(EventMultiple $event) {
+    $comments_enabled = array();
+    $nodes = $event->getBCEntities();
+
+    // Check if comments are enabled for each node. If comments are disabled,
+    // assign values without hitting the database.
+    foreach ($nodes as $node) {
+      // Store whether comments are enabled for this node.
+      if ($node->comment != COMMENT_NODE_HIDDEN) {
+        $comments_enabled[] = $node->nid;
+      }
+      else {
+        $node->cid = 0;
+        $node->last_comment_timestamp = $node->created;
+        $node->last_comment_name = '';
+        $node->last_comment_uid = $node->uid;
+        $node->comment_count = 0;
+      }
+    }
+
+    // For nodes with comments enabled, fetch information from the database.
+    if (!empty($comments_enabled)) {
+      $result = db_query('SELECT nid, cid, last_comment_timestamp, last_comment_name, last_comment_uid, comment_count FROM {node_comment_statistics} WHERE nid IN (:comments_enabled)', array(':comments_enabled' => $comments_enabled));
+      foreach ($result as $record) {
+        $nodes[$record->nid]->cid = $record->cid;
+        $nodes[$record->nid]->last_comment_timestamp = $record->last_comment_timestamp;
+        $nodes[$record->nid]->last_comment_name = $record->last_comment_name;
+        $nodes[$record->nid]->last_comment_uid = $record->last_comment_uid;
+        $nodes[$record->nid]->comment_count = $record->comment_count;
+      }
+    }
+  }
+
+  /**
+   * Implements hook_node_insert().
+   */
+  function insert(Event $event) {
+    $node = $event->getBCEntity();
+    // Allow bulk updates and inserts to temporarily disable the
+    // maintenance of the {node_comment_statistics} table.
+    if (variable_get('comment_maintain_node_statistics', TRUE)) {
+      db_insert('node_comment_statistics')
+        ->fields(array(
+          'nid' => $node->nid,
+          'cid' => 0,
+          'last_comment_timestamp' => $node->changed,
+          'last_comment_name' => NULL,
+          'last_comment_uid' => $node->uid,
+          'comment_count' => 0,
+        ))
+        ->execute();
+    }
+  }
+
+  public function nodePreDelete(EventMultiple $event) {
+    $nids = array_keys($event->getEntities());
+    $cids = db_query('SELECT cid FROM {comment} WHERE nid IN (:nids)', array(':nids' => $nids))->fetchCol();
+    comment_delete_multiple($cids);
+    db_delete('node_comment_statistics')
+      ->condition('nid', $nids)
+      ->execute();
+  }
+
+  public function userPreDelete(EventMultiple $event) {
+    $cids = db_query('SELECT c.cid FROM {comment} c WHERE uid IN (:uids)', array(':uids' => array_keys($event->getEntities())))->fetchCol();
+    comment_delete_multiple($cids);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events['node.entity.post_load'] = 'load';
+    $events['node.entity.insert'] = 'insert';
+    $events['node.entity.pre_delete'] = 'nodePreDelete';
+    $events['user.entity.pre_delete'] = 'userPreDelete';
+    return $events;
+  }
+
+}
diff --git a/core/modules/comment/lib/Drupal/comment/StorageSubscriber.php b/core/modules/comment/lib/Drupal/comment/StorageSubscriber.php
new file mode 100644
index 0000000..28b5a1e
--- /dev/null
+++ b/core/modules/comment/lib/Drupal/comment/StorageSubscriber.php
@@ -0,0 +1,183 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\comment\StorageSubscriber.
+ */
+
+namespace Drupal\comment;
+
+use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\Event;
+use Drupal\Core\Entity\EventMultiple;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Lock\LockBackendInterface;
+use Drupal\comment\Plugin\Core\Entity\Comment;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class StorageSubscriber implements EventSubscriberInterface {
+
+  /**
+   * @var \Drupal\Core\Entity\EntityManager
+   */
+  protected $entityManager;
+
+  /**
+   * @var \Drupal\Core\Lock\LockBackendInterface
+   */
+  protected $lockService;
+
+  /**
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The thread for which a lock was acquired.
+   */
+  protected $threadLock;
+
+  public function __construct(EntityManager $entity_manager, LockBackendInterface $lock_service, ModuleHandlerInterface $module_handler) {
+    // @todo use node access service instead http://drupal.org/node/1921426
+    $this->entityManager = $entity_manager;
+    $this->lockService = $lock_service;
+    $this->moduleHandler = $module_handler;
+  }
+
+  public function postDelete(EventMultiple $event) {
+    $comments = $event->getEntities();
+    // Delete the comments' replies.
+    $cids = $this->storageController()->getChildCids($comments);
+    $children = $this->storageController()->load($cids);
+    $this->storageController()->delete($children);
+
+    foreach ($comments as $comment) {
+      $this->storageController()->updateNodeStatistics($comment->nid->target_id);
+    }
+  }
+
+  public function preSave(Event $event) {
+    global $user;
+    $comment = $event->getEntity();
+
+    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 = $this->storageController()->getMaxThread($comment);
+          // 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 = $this->getController->getMaxThreadPerThread($comment);
+
+          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 (!$this->lockService->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;
+    }
+  }
+
+  public function write(Comment $comment) {
+    $this->releaseThreadLock();
+    // Update the {node_comment_statistics} table prior to executing the hook.
+    $this->storageController()->updateNodeStatistics($comment->nid->target_id);
+    if ($comment->status->value == COMMENT_PUBLISHED) {
+      $this->moduleHandler->invokeAll('comment_publish', array($comment));
+    }
+  }
+
+  public function insert(Event $event) {
+    $this->write($event->getEntity());
+  }
+
+  public function update(Event $event) {
+    $this->write($event->getEntity());
+  }
+
+  /**
+   * Release the lock acquired for the thread in preSave().
+   */
+  protected function releaseThreadLock() {
+    if ($this->threadLock) {
+      $this->lockService->release($this->threadLock);
+      $this->threadLock = '';
+    }
+  }
+
+  /**
+   * @return \Drupal\comment\CommentStorageControllerInterface
+   */
+  protected function storageController() {
+    return $this->entityManager->getStorageController('comment');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events['comment.entity.insert'] = 'insert';
+    $events['comment.entity.post_delete'] = 'postDelete';
+    $events['comment.entity.pre_save'] = 'preSave';
+    $events['comment.entity.update'] = 'update';
+    return $events;
+  }
+
+}
diff --git a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/entity_reference/selection/SelectionBase.php b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/entity_reference/selection/SelectionBase.php
index cba9a82..ab60905 100644
--- a/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/entity_reference/selection/SelectionBase.php
+++ b/core/modules/entity_reference/lib/Drupal/entity_reference/Plugin/entity_reference/selection/SelectionBase.php
@@ -113,7 +113,7 @@ public static function settingsForm(&$field, &$instance) {
 
     // @todo Use Entity::getPropertyDefinitions() when all entity types are
     // converted to the new Field API.
-    $fields = drupal_map_assoc($entity_info['schema_fields_sql']['base_table']);
+    $fields = drupal_map_assoc(drupal_schema_fields_sql($entity_info['base_table']));
     foreach (field_info_instances($field['settings']['target_type']) as $bundle_instances) {
       foreach ($bundle_instances as $instance_name => $instance_info) {
         $field_info = field_info_field($instance_name);
diff --git a/core/modules/field/field.attach.inc b/core/modules/field/field.attach.inc
index 51d2d57..a3a6489 100644
--- a/core/modules/field/field.attach.inc
+++ b/core/modules/field/field.attach.inc
@@ -841,149 +841,6 @@ function field_attach_form(EntityInterface $entity, &$form, &$form_state, $langc
   }
 }
 
-/**
- * Loads fields for the current revisions of a group of entities.
- *
- * Loads all fields for each entity object in a group of a single entity type.
- * The loaded field values are added directly to the entity objects.
- *
- * field_attach_load() is automatically called by the default entity controller
- * class, and thus, in most cases, doesn't need to be explicitly called by the
- * entity type module.
- *
- * @param $entity_type
- *   The type of entities in $entities; e.g., 'node' or 'user'.
- * @param $entities
- *   An array of entities for which to load fields, keyed by entity ID. Each
- *   entity needs to have its 'bundle', 'id' and (if applicable) 'revision' keys
- *   filled in. The function adds the loaded field data directly in the entity
- *   objects of the $entities array.
- * @param $age
- *   FIELD_LOAD_CURRENT to load the most recent revision for all fields, or
- *   FIELD_LOAD_REVISION to load the version indicated by each entity. Defaults
- *   to FIELD_LOAD_CURRENT; use field_attach_load_revision() instead of passing
- *   FIELD_LOAD_REVISION.
- * @param $options
- *   An associative array of additional options, with the following keys:
- *   - field_id: The field ID that should be loaded, instead of loading all
- *     fields, for each entity. Note that returned entities may contain data for
- *     other fields, for example if they are read from a cache.
- *   - deleted: If TRUE, the function will operate on deleted fields as well as
- *     non-deleted fields. If unset or FALSE, only non-deleted fields are
- *     operated on.
- */
-function field_attach_load($entity_type, $entities, $age = FIELD_LOAD_CURRENT, $options = array()) {
-  $load_current = $age == FIELD_LOAD_CURRENT;
-
-  // Merge default options.
-  $default_options = array(
-    'deleted' => FALSE,
-  );
-  $options += $default_options;
-
-  $info = entity_get_info($entity_type);
-  // Only the most current revision of non-deleted fields for cacheable entity
-  // types can be cached.
-  $cache_read = $load_current && $info['field_cache'] && empty($options['deleted']);
-  // In addition, do not write to the cache when loading a single field.
-  $cache_write = $cache_read && !isset($options['field_id']);
-
-  if (empty($entities)) {
-    return;
-  }
-
-  // Assume all entities will need to be queried. Entities found in the cache
-  // will be removed from the list.
-  $queried_entities = $entities;
-
-  // Fetch available entities from cache, if applicable.
-  if ($cache_read) {
-    // Build the list of cache entries to retrieve.
-    $cids = array();
-    foreach ($entities as $id => $entity) {
-      $cids[] = "field:$entity_type:$id";
-    }
-    $cache = cache('field')->getMultiple($cids);
-    // Put the cached field values back into the entities and remove them from
-    // the list of entities to query.
-    foreach ($entities as $id => $entity) {
-      $cid = "field:$entity_type:$id";
-      if (isset($cache[$cid])) {
-        unset($queried_entities[$id]);
-        foreach ($cache[$cid]->data as $field_name => $values) {
-          $entity->$field_name = $values;
-        }
-      }
-    }
-  }
-
-  // Fetch other entities from their storage location.
-  if ($queried_entities) {
-    // The invoke order is:
-    // - hook_field_storage_pre_load()
-    // - storage backend's hook_field_storage_load()
-    // - field-type module's hook_field_load()
-    // - hook_field_attach_load()
-
-    // Invoke hook_field_storage_pre_load(): let any module load field
-    // data before the storage engine, accumulating along the way.
-    $skip_fields = array();
-    foreach (module_implements('field_storage_pre_load') as $module) {
-      $function = $module . '_field_storage_pre_load';
-      $function($entity_type, $queried_entities, $age, $skip_fields, $options);
-    }
-
-    $instances = array();
-
-    // Collect the storage backends used by the remaining fields in the entities.
-    $storages = array();
-    foreach ($queried_entities as $entity) {
-      $instances = _field_invoke_get_instances($entity_type, $entity->bundle(), $options);
-      $id = $entity->id();
-      $vid = $entity->getRevisionId();
-      foreach ($instances as $instance) {
-        $field_name = $instance['field_name'];
-        $field_id = $instance['field_id'];
-        // Make sure all fields are present at least as empty arrays.
-        if (!isset($queried_entities[$id]->{$field_name})) {
-          $queried_entities[$id]->{$field_name} = array();
-        }
-        // Collect the storage backend if the field has not been loaded yet.
-        if (!isset($skip_fields[$field_id])) {
-          $field = field_info_field_by_id($field_id);
-          $storages[$field['storage']['type']][$field_id][] = $load_current ? $id : $vid;
-        }
-      }
-    }
-
-    // Invoke hook_field_storage_load() on the relevant storage backends.
-    foreach ($storages as $storage => $fields) {
-      $storage_info = field_info_storage_types($storage);
-      module_invoke($storage_info['module'], 'field_storage_load', $entity_type, $queried_entities, $age, $fields, $options);
-    }
-
-    // Invoke field-type module's hook_field_load().
-    $null = NULL;
-    _field_invoke_multiple('load', $entity_type, $queried_entities, $age, $null, $options);
-
-    // Invoke hook_field_attach_load(): let other modules act on loading the
-    // entity.
-    module_invoke_all('field_attach_load', $entity_type, $queried_entities, $age, $options);
-
-    // Build cache data.
-    if ($cache_write) {
-      foreach ($queried_entities as $id => $entity) {
-        $data = array();
-        $instances = field_info_instances($entity_type, $entity->bundle());
-        foreach ($instances as $instance) {
-          $data[$instance['field_name']] = $queried_entities[$id]->{$instance['field_name']};
-        }
-        $cid = "field:$entity_type:$id";
-        cache('field')->set($cid, $data);
-      }
-    }
-  }
-}
 
 /**
  * Loads all fields for previous versions of a group of entities.
@@ -1142,138 +999,6 @@ function field_attach_presave($entity) {
 }
 
 /**
- * Save field data for a new entity.
- *
- * The passed-in entity must already contain its id and (if applicable)
- * revision id attributes.
- * Default values (if any) will be saved for fields not present in the
- * $entity.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity with fields to save.
- * @return
- *   Default values (if any) will be added to the $entity parameter for fields
- *   it leaves unspecified.
- */
-function field_attach_insert(EntityInterface $entity) {
-  _field_invoke('insert', $entity);
-
-  // Let any module insert field data before the storage engine, accumulating
-  // saved fields along the way.
-  $skip_fields = array();
-  foreach (module_implements('field_storage_pre_insert') as $module) {
-    $function = $module . '_field_storage_pre_insert';
-    $function($entity, $skip_fields);
-  }
-
-  // Collect the storage backends used by the remaining fields in the entities.
-  $storages = array();
-  foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
-    $field = field_info_field_by_id($instance['field_id']);
-    $field_id = $field['uuid'];
-    $field_name = $field['field_name'];
-    if (!empty($entity->$field_name)) {
-      // Collect the storage backend if the field has not been written yet.
-      if (!isset($skip_fields[$field_id])) {
-        $storages[$field['storage']['type']][$field_id] = $field_id;
-      }
-    }
-  }
-
-  // Field storage backends save any remaining unsaved fields.
-  foreach ($storages as $storage => $fields) {
-    $storage_info = field_info_storage_types($storage);
-    module_invoke($storage_info['module'], 'field_storage_write', $entity, FIELD_STORAGE_INSERT, $fields);
-  }
-
-  // Let other modules act on inserting the entity.
-  module_invoke_all('field_attach_insert', $entity);
-}
-
-/**
- * Saves field data for an existing entity.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity with fields to save.
- */
-function field_attach_update(EntityInterface $entity) {
-  _field_invoke('update', $entity);
-
-  // Let any module update field data before the storage engine, accumulating
-  // saved fields along the way.
-  $skip_fields = array();
-  foreach (module_implements('field_storage_pre_update') as $module) {
-    $function = $module . '_field_storage_pre_update';
-    $function($entity, $skip_fields);
-  }
-
-  // Collect the storage backends used by the remaining fields in the entities.
-  $storages = array();
-  foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
-    $field = field_info_field_by_id($instance['field_id']);
-    $field_id = $field['uuid'];
-    $field_name = $field['field_name'];
-    // Leave the field untouched if $entity comes with no $field_name property,
-    // but empty the field if it comes as a NULL value or an empty array.
-    // Function property_exists() is slower, so we catch the more frequent
-    // cases where it's an empty array with the faster isset().
-    if (isset($entity->$field_name) || property_exists($entity, $field_name)) {
-      // Collect the storage backend if the field has not been written yet.
-      if (!isset($skip_fields[$field_id])) {
-        $storages[$field['storage']['type']][$field_id] = $field_id;
-      }
-    }
-  }
-
-  // Field storage backends save any remaining unsaved fields.
-  foreach ($storages as $storage => $fields) {
-    $storage_info = field_info_storage_types($storage);
-    module_invoke($storage_info['module'], 'field_storage_write', $entity, FIELD_STORAGE_UPDATE, $fields);
-  }
-
-  // Let other modules act on updating the entity.
-  module_invoke_all('field_attach_update', $entity);
-
-  $entity_info = $entity->entityInfo();
-  if ($entity_info['field_cache']) {
-    cache('field')->delete('field:' . $entity->entityType() . ':' . $entity->id());
-  }
-}
-
-/**
- * Deletes field data for an existing entity. This deletes all revisions of
- * field data for the entity.
- *
- * @param \Drupal\Core\Entity\EntityInterface $entity
- *   The entity whose field data to delete.
- */
-function field_attach_delete(EntityInterface $entity) {
-  _field_invoke('delete', $entity);
-
-  // Collect the storage backends used by the fields in the entities.
-  $storages = array();
-  foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
-    $field = field_info_field_by_id($instance['field_id']);
-    $field_id = $field['uuid'];
-    $storages[$field['storage']['type']][$field_id] = $field_id;
-  }
-
-  // Field storage backends delete their data.
-  foreach ($storages as $storage => $fields) {
-    $storage_info = field_info_storage_types($storage);
-    module_invoke($storage_info['module'], 'field_storage_delete', $entity, $fields);
-  }
-
-  // Let other modules act on deleting the entity.
-  module_invoke_all('field_attach_delete', $entity);
-
-  $entity_info = $entity->entityInfo();
-  if ($entity_info['field_cache']) {
-    cache('field')->delete('field:' . $entity->entityType() . ':' . $entity->id());
-  }
-}
-
-/**
  * Delete field data for a single revision of an existing entity. The passed
  * entity must have a revision ID attribute.
  *
diff --git a/core/modules/field/field.crud.inc b/core/modules/field/field.crud.inc
index 14566f2..3824373 100644
--- a/core/modules/field/field.crud.inc
+++ b/core/modules/field/field.crud.inc
@@ -170,7 +170,6 @@ function field_read_fields($conditions = array(), $include_additional = array())
   // Translate "do not include inactive instances" into actual conditions.
   if (!$include_inactive) {
     $conditions['active'] = TRUE;
-    $conditions['storage.active'] = TRUE;
   }
 
   // Collect matching fields.
@@ -179,10 +178,6 @@ function field_read_fields($conditions = array(), $include_additional = array())
     foreach ($conditions as $key => $value) {
       // Extract the actual value against which the condition is checked.
       switch ($key) {
-        case 'storage.active':
-          $checked_value = $field->storage['active'];
-          break;
-
         case 'field_name';
           $checked_value = $field->id;
           break;
@@ -361,7 +356,6 @@ function field_read_instances($conditions = array(), $include_additional = array
   // Translate "do not include inactive fields" into actual conditions.
   if (!$include_inactive) {
     $conditions['field.active'] = TRUE;
-    $conditions['field.storage.active'] = TRUE;
   }
 
   // Collect matching instances.
@@ -387,10 +381,6 @@ function field_read_instances($conditions = array(), $include_additional = array
           $checked_value = $field->active;
           break;
 
-        case 'field.storage.active':
-          $checked_value = $field->storage['active'];
-          break;
-
         case 'field_id':
           $checked_value = $instance->field_uuid;
           break;
@@ -555,7 +545,6 @@ function field_purge_batch($batch_size) {
         $ids->entity_id = $entity_id;
         $entities[$entity_id] = _field_create_entity_from_ids($ids);
       }
-      field_attach_load($entity_type, $entities, FIELD_LOAD_CURRENT, array('field_id' => $field['uuid'], 'deleted' => 1));
       foreach ($entities as $entity) {
         // Purge the data for the entity.
         field_purge_data($entity, $field, $instance);
@@ -597,15 +586,7 @@ function field_purge_data(EntityInterface $entity, $field, $instance) {
   // field at a time, so we can use it as-is for purging.
   $options = array('field_id' => $instance['field_id'], 'deleted' => TRUE);
   _field_invoke('delete', $entity, $dummy, $dummy, $options);
-
-  // Tell the field storage system to purge the data.
-  module_invoke($field['storage']['module'], 'field_storage_purge', $entity, $field, $instance);
-
-  // Let other modules act on purging the data.
-  foreach (module_implements('field_attach_purge') as $module) {
-    $function = $module . '_field_attach_purge';
-    $function($entity, $field, $instance);
-  }
+  $entity->purge($field, $instance);
 }
 
 /**
diff --git a/core/modules/field/field.info.inc b/core/modules/field/field.info.inc
index d364a70..62357b7 100644
--- a/core/modules/field/field.info.inc
+++ b/core/modules/field/field.info.inc
@@ -563,21 +563,5 @@ function field_info_formatter_settings($type) {
 }
 
 /**
- * Returns a field storage type's default settings.
- *
- * @param $type
- *   A field storage type name.
- *
- * @return
- *   The storage type's default settings, as provided by
- *   hook_field_storage_info(), or an empty array if type or settings are
- *   undefined.
- */
-function field_info_storage_settings($type) {
-  $info = field_info_storage_types($type);
-  return isset($info['settings']) ? $info['settings'] : array();
-}
-
-/**
  * @} End of "defgroup field_info".
  */
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 1e31484..f16f9f1 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -429,12 +429,6 @@ function field_sync_field_status() {
           $field['active'] = TRUE;
           $changed[$uuid] = $field;
         }
-        // Associate storage backends.
-        if (isset($storage_types[$field['storage']['type']]) && ($field['storage']['module'] !== $module || !$field['storage']['active'])) {
-          $field['storage']['module'] = $module;
-          $field['storage']['active'] = TRUE;
-          $changed[$uuid] = $field;
-        }
       }
     }
   }
@@ -445,10 +439,6 @@ function field_sync_field_status() {
       $field['active'] = FALSE;
       $changed[$uuid] = $field;
     }
-    if (!isset($modules[$field['storage']['module']]) && $field['storage']['active']) {
-      $field['storage']['active'] = FALSE;
-      $changed[$uuid] = $field;
-    }
   }
 
   // Store the updated field definitions.
diff --git a/core/modules/field/field.services.yml b/core/modules/field/field.services.yml
index 2313254..91a1df5 100644
--- a/core/modules/field/field.services.yml
+++ b/core/modules/field/field.services.yml
@@ -15,3 +15,7 @@ services:
     factory_method: get
     factory_service: cache_factory
     arguments: [field]
+  field.entity_subscriber:
+    class: Drupal\field\EntitySubscriber
+    tags:
+      - { name: event_subscriber }
diff --git a/core/modules/field/field.views.inc b/core/modules/field/field.views.inc
index b01e882..09fcc84 100644
--- a/core/modules/field/field.views.inc
+++ b/core/modules/field/field.views.inc
@@ -8,6 +8,7 @@
  */
 
 use Drupal\Component\Utility\NestedArray;
+use Drupal\field_sql_storage\Entity\Storage;
 
 /**
  * Implements hook_views_data().
@@ -99,8 +100,8 @@ function field_views_field_default_views_data($field) {
 
   $data = array();
 
-  $current_table = _field_sql_storage_tablename($field);
-  $revision_table = _field_sql_storage_revision_tablename($field);
+  $current_table = Storage::tableName($field);
+  $revision_table = Storage::revisionTableName($field);
 
   // The list of entity:bundle that this field is used in.
   $bundles_names = array();
@@ -172,7 +173,7 @@ function field_views_field_default_views_data($field) {
 
   $add_fields = array('delta', 'langcode', 'bundle');
   foreach ($field['columns'] as $column_name => $attributes) {
-    $add_fields[] = _field_sql_storage_columnname($field['field_name'], $column_name);
+    $add_fields[] = Storage::columnName($field['field_name'], $column_name);
   }
 
   // Note: we don't have a label available here, because we are at the field
diff --git a/core/modules/field/lib/Drupal/field/EntitySubscriber.php b/core/modules/field/lib/Drupal/field/EntitySubscriber.php
new file mode 100644
index 0000000..cef74fd
--- /dev/null
+++ b/core/modules/field/lib/Drupal/field/EntitySubscriber.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\field\EntitySubscriber.
+ */
+
+namespace Drupal\field;
+
+
+use Drupal\Core\Entity\Event;
+use Drupal\Core\Entity\EventMultiple;
+use Drupal\Core\Entity\Events;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class EntitySubscriber implements EventSubscriberInterface {
+
+  function insert(Event $event) {
+    _field_invoke('insert', $event->getBCEntity());
+  }
+
+  function postDelete(EventMultiple $event) {
+    foreach ($event->getBCEntities() as $entity) {
+      _field_invoke('delete', $entity);
+    }
+  }
+
+  function postDeleteRevision(EventMultiple $event) {
+    foreach ($event->getBCEntities() as $entity) {
+      _field_invoke('delete_revision', $entity);
+    }
+  }
+
+  function postLoad(EventMultiple $event) {
+    _field_invoke_multiple('load', $event->getEntityType(), $event->getBCEntities());
+  }
+
+  function preSave(Event $event) {
+    _field_invoke('presave', $event->getBCEntity());
+  }
+
+  function update(Event $event) {
+    _field_invoke('update', $event->getBCEntity());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[Events::INSERT] = 'insert';
+    $events[Events::POST_LOAD] = 'postLoad';
+    $events[Events::POST_DELETE] = 'postDelete';
+    $events[Events::POST_DELETE_REVISION] = 'postDeleteRevision';
+    $events[Events::PRE_SAVE] = 'preSave';
+    $events[Events::UPDATE] = 'update';
+    return $events;
+  }
+
+}
diff --git a/core/modules/field/lib/Drupal/field/FieldInfo.php b/core/modules/field/lib/Drupal/field/FieldInfo.php
index 6d95788..dfb7428 100644
--- a/core/modules/field/lib/Drupal/field/FieldInfo.php
+++ b/core/modules/field/lib/Drupal/field/FieldInfo.php
@@ -180,7 +180,7 @@ public function getFieldMap() {
     // Get active fields.
     foreach (config_get_storage_names_with_prefix('field.field') as $config_id) {
       $field_config = $this->config->get($config_id)->get();
-      if ($field_config['active'] && $field_config['storage']['active']) {
+      if ($field_config['active']) {
         $fields[$field_config['uuid']] = $field_config;
       }
     }
@@ -392,7 +392,7 @@ public function getBundleInstances($entity_type, $bundle) {
     }
 
     // Read from the persistent cache.
-    if ($cached = $this->cacheBackend->get("field_info:bundle:$entity_type:$bundle")) {
+    if (FALSE && $cached = $this->cacheBackend->get("field_info:bundle:$entity_type:$bundle")) {
       $info = $cached->data;
 
       // Extract the field definitions and save them in the "static" cache.
@@ -533,7 +533,6 @@ public function getBundleExtraFields($entity_type, $bundle) {
   public function prepareField($field) {
     // Make sure all expected field settings are present.
     $field['settings'] += field_info_field_settings($field['type']);
-    $field['storage']['settings'] += field_info_storage_settings($field['storage']['type']);
 
     return $field;
   }
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php
index 14d02e8..41dcc9f 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/Field.php
@@ -141,25 +141,6 @@ class Field extends ConfigEntityBase implements \ArrayAccess, \Serializable {
   public $locked;
 
   /**
-   * The field storage definition.
-   *
-   * An array of key/value pairs identifying the storage backend to use for the
-   * field:
-   * - type: (string) The storage backend used by the field. Storage backends
-   *   are defined by modules that implement hook_field_storage_info().
-   * - settings: (array) A sub-array of key/value pairs of settings. The keys
-   *   and default values are defined by the storage backend in the 'settings'
-   *   entry of hook_field_storage_info().
-   * - module: (string, read-only) The name of the module that implements the
-   *   storage backend.
-   * - active: (integer, read-only) TRUE if the module that implements the
-   *   storage backend is currently enabled, FALSE otherwise.
-   *
-   * @var array
-   */
-  public $storage;
-
-  /**
    * The custom storage indexes for the field data storage.
    *
    * This set of indexes is merged with the "default" indexes specified by the
@@ -236,7 +217,6 @@ public function __construct(array $values, $entity_type = 'field_entity') {
       'entity_types' => array(),
       'locked' => FALSE,
       'deleted' => FALSE,
-      'storage' => array(),
       'indexes' => array(),
     );
     parent::__construct($values, $entity_type);
@@ -256,7 +236,6 @@ public function getExportProperties() {
       'module',
       'active',
       'entity_types',
-      'storage',
       'locked',
       'cardinality',
       'translatable',
@@ -274,7 +253,8 @@ public function getExportProperties() {
    */
   public function save() {
     $module_handler = \Drupal::moduleHandler();
-    $storage_controller = \Drupal::service('plugin.manager.entity')->getStorageController($this->entityType);
+    $entity_manager = \Drupal::service('plugin.manager.entity');
+    $storage_controller = $entity_manager->getStorageController($this->entityType);
 
     // Clear the derived data about the field.
     unset($this->schema, $this->storageDetails);
@@ -321,24 +301,6 @@ public function save() {
       // definition is passed to the various hooks and written to config.
       $this->settings += $field_type['settings'];
 
-      // Provide default storage.
-      $this->storage += array(
-        'type' => variable_get('field_storage_default', 'field_sql_storage'),
-        'settings' => array(),
-      );
-      // Check that the storage type is known.
-      $storage_type = field_info_storage_types($this->storage['type']);
-      if (!$storage_type) {
-        throw new FieldException(format_string('Attempt to create a field with unknown storage type %type.', array('%type' => $this->storage['type'])));
-      }
-      $this->storage['module'] = $storage_type['module'];
-      $this->storage['active'] = TRUE;
-      // Provide default storage settings.
-      $this->storage['settings'] += $storage_type['settings'];
-
-      // Invoke the storage backend's hook_field_storage_create_field().
-      $module_handler->invoke($this->storage['module'], 'field_storage_create_field', array($this));
-
       $hook = 'field_create_field';
       $hook_args = array($this);
     }
@@ -353,9 +315,6 @@ public function save() {
       if ($this->entity_types != $original->entity_types) {
         throw new FieldException("Cannot change an existing field's entity_types property.");
       }
-      if ($this->storage['type'] != $original->storage['type']) {
-        throw new FieldException("Cannot change an existing field's storage type.");
-      }
 
       // Make sure all settings are present, so that a complete field
       // definition is saved. This allows calling code to perform partial
@@ -367,12 +326,18 @@ public function save() {
       // See if any module forbids the update by throwing an exception. This
       // invokes hook_field_update_forbid().
       $module_handler->invokeAll('field_update_forbid', array($this, $original, $has_data));
-
-      // Tell the storage engine to update the field by invoking the
-      // hook_field_storage_update_field(). The storage engine can reject the
-      // definition update as invalid by raising an exception, which stops
-      // execution before the definition is written to config.
-      $module_handler->invoke($this->storage['module'], 'field_storage_update_field', array($this, $original, $has_data));
+      // Tell the storage engine to update the field by calling the
+      // updateField() mehtod on the entity storage controller this field
+      // belongs to. The storage engine can reject the definition update as
+      // invalid by raising an exception, which stops execution before the
+      // definition is written to config.
+      $entity_types = array_keys($this->getBundles());
+      if ($entity_types) {
+        $entity_type = reset($entity_types);
+        // Fields are stored in the same storage as the entity they are attached
+        // to.
+        $entity_manager->getStorageController($entity_type)->updateField($this, $original, $has_data);
+      }
 
       $hook = 'field_update_field';
       $hook_args = array($this, $original, $has_data);
@@ -414,10 +379,6 @@ public function delete() {
         $instance->delete(FALSE);
       }
 
-      // Mark field data for deletion by invoking
-      // hook_field_storage_delete_field().
-      $module_handler->invoke($this->storage['module'], 'field_storage_delete_field', array($this));
-
       // Delete the configuration of this field and save the field configuration
       // in the key_value table so we can use it later during
       // field_purge_batch(). This makes sure a new field can be created
@@ -515,7 +476,16 @@ public function getStorageDetails() {
       // Collect the storage details from the storage backend, and let other
       // modules alter it. This invokes hook_field_storage_details() and
       // hook_field_storage_details_alter().
-      $details = (array) $module_handler->invoke($this->storage['module'], 'field_storage_details', array($this));
+      $entity_types = array_keys($this->getBundles());
+      if ($entity_types) {
+        $entity_type = reset($entity_types);
+        // Fields are stored in the same storage as the entity they are attached
+        // to.
+        $details = \Drupal::service('plugin.manager.entity')->getStorageController($entity_type)->storageDetails($this);
+      }
+      else {
+        throw new FieldException(format_string('Field %field has no instances and no storage details', array('%field' => $this->id)));
+      }
       $module_handler->alter('field_storage_details', $details, $this);
 
       $this->storageDetails = $details;
diff --git a/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/FieldInstance.php b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/FieldInstance.php
index 363d54f..3fb9245 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/FieldInstance.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/Core/Entity/FieldInstance.php
@@ -246,6 +246,20 @@ public function __construct(array $values, $entity_type = 'field_instance') {
       throw new FieldException('Attempt to create an instance of an unspecified field.');
     }
 
+    // Instances must be on the same storage backend.
+    $map = field_info_field_map();
+    if (isset($map[$field['field_name']]['bundles'])) {
+      $bundles = $map[$field['field_name']]['bundles'];
+      unset($bundles[$values['entity_type']]);
+      if ($bundles) {
+        $other_entity_types = array_keys($bundles);
+        $other_entity_type = reset($other_entity_types);
+        if (!static::isSameStorage($other_entity_type, $values['entity_type'])) {
+          throw new FieldException('Attempt to create a cross-storage backend instance.');
+        }
+      }
+    }
+
     // At this point, we should have a 'field_uuid' and a Field. Ditch the
     // 'field_name' property if it was provided, and assign the $field property.
     unset($values['field_name']);
@@ -276,6 +290,19 @@ public function __construct(array $values, $entity_type = 'field_instance') {
   }
 
   /**
+   * @param $entity_type1
+   * @param $entity_type2
+   * @return bool
+   *  If the two entity types have the same storage.
+   */
+  static public function isSameStorage($entity_type1, $entity_type2) {
+    $entity_manager = \Drupal::service('plugin.manager.entity');
+    $entity_storage1 = $entity_manager->getStorageController($entity_type1);
+    $entity_storage2 = $entity_manager->getStorageController($entity_type2);
+    return $entity_storage1->getBackendType() == $entity_storage2->getBackendType();
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function id() {
@@ -330,6 +357,7 @@ public function save() {
       if ($prior_instance = current($instance_controller->load(array($this->id)))) {
         throw new FieldException(format_string('Attempt to create an instance of field @field_id on bundle @bundle that already has an instance of that field.', array('@field_id' => $this->field->id, '@bundle' => $this->bundle)));
       }
+      $entity_manager->getStorageController($this->entity_type)->createInstance($this);
 
       $hook = 'field_create_instance';
       $hook_args = array($this);
@@ -417,7 +445,7 @@ public function delete($field_cleanup = TRUE) {
 
       // Mark instance data for deletion by invoking
       // hook_field_storage_delete_instance().
-      $module_handler->invoke($this->field->storage['module'], 'field_storage_delete_instance', array($this));
+      \Drupal::service('plugin.manager.entity')->getStorageController($this->entity_type)->deleteInstance($this);
 
       // Let modules react to the deletion of the instance with
       // hook_field_delete_instance().
@@ -443,7 +471,7 @@ public function getField() {
   /**
    * Returns the Widget plugin for the instance.
    *
-   * @return Drupal\field\Plugin\Type\Widget\WidgetInterface
+   * @return \Drupal\field\Plugin\Type\Widget\WidgetInterface
    *   The Widget plugin to be used for the instance.
    */
   public function getWidget() {
diff --git a/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php b/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php
index c288750..5b75a0c 100644
--- a/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php
+++ b/core/modules/field/lib/Drupal/field/Plugin/views/field/Field.php
@@ -8,6 +8,7 @@
 namespace Drupal\field\Plugin\views\field;
 
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\field_sql_storage\Entity\Storage;
 use Drupal\views\ViewExecutable;
 use Drupal\views\Plugin\views\display\DisplayPluginBase;
 use Drupal\views\Plugin\views\field\FieldPluginBase;
@@ -247,7 +248,7 @@ function click_sort($order) {
     }
 
     $this->ensureMyTable();
-    $column = _field_sql_storage_columnname($this->definition['field_name'], $this->options['click_sort_column']);
+    $column = Storage::columnName($this->definition['field_name'], $this->options['click_sort_column']);
     if (!isset($this->aliases[$column])) {
       // Column is not in query; add a sort on it (without adding the column).
       $this->aliases[$column] = $this->tableAlias . '.' . $column;
diff --git a/core/modules/field_sql_storage/field_sql_storage.install b/core/modules/field_sql_storage/field_sql_storage.install
index 4bf79df..53772b7 100644
--- a/core/modules/field_sql_storage/field_sql_storage.install
+++ b/core/modules/field_sql_storage/field_sql_storage.install
@@ -6,6 +6,7 @@
  */
 
 use Drupal\field\Plugin\Core\Entity\Field;
+use Drupal\field_sql_storage\Entity\Storage;
 
 /**
  * Implements hook_schema().
@@ -22,11 +23,21 @@ function field_sql_storage_schema() {
   }
   $deleted_fields = Drupal::state()->get('field.field.deleted') ?: array();
   $fields = array_merge($fields, $deleted_fields);
+  $backends_by_field_name = array();
+  $entity_manager = Drupal::service('plugin.manager.entity');
+  foreach (config_get_storage_names_with_prefix('field.instance') as $name) {
+    // $prefix1 is field, $prefix2 is instance.
+    list($prefix1, $prefix2, $entity_type, $bundle, $field_name) = explode('.', $name);
+    if (!isset($backends_by_entity_type[$entity_type])) {
+      $backends_by_entity_type[$entity_type] = $entity_manager->getStorageController($entity_type)->getBackendType();
+    }
+    $backends_by_field_name[$field_name] = $backends_by_entity_type[$entity_type];
+  }
 
   foreach ($fields as $field) {
-    if ($field['storage']['type'] == 'field_sql_storage') {
+    if (isset($backends_by_field_name[$field['id']]) && $backends_by_field_name[$field['id']] == 'sql') {
       $field = new Field($field);
-      $schema += _field_sql_storage_schema($field);
+      $schema += Storage::schema($field);
     }
   }
 
@@ -158,8 +169,8 @@ function field_sql_storage_update_8000(&$sandbox) {
     );
 
     $table_info = array(
-      _field_sql_storage_tablename($field) => $primary_key_data,
-      _field_sql_storage_revision_tablename($field) => $primary_key_revision,
+      Storage::tableName($field) => $primary_key_data,
+      Storage::revisionTableName($field) => $primary_key_revision,
     );
     foreach ($table_info as $table => $primary_key) {
       // Do not update tables which already have the langcode column,
diff --git a/core/modules/field_sql_storage/field_sql_storage.module b/core/modules/field_sql_storage/field_sql_storage.module
index 3283b26..45baf5f 100644
--- a/core/modules/field_sql_storage/field_sql_storage.module
+++ b/core/modules/field_sql_storage/field_sql_storage.module
@@ -8,6 +8,7 @@
 use Drupal\Core\Database\Database;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\field\FieldUpdateForbiddenException;
+use Drupal\field_sql_storage\Entity\Storage;
 
 /**
  * Implements hook_help().
@@ -35,221 +36,6 @@ function field_sql_storage_field_storage_info() {
 }
 
 /**
- * Generates a table name for a field data table.
- *
- * When a field is a deleted, the table is renamed to
- * {field_deleted_data_FIELD_UUID}. To make sure we don't end up with table
- * names longer than 64 characters, we hash the uuid and return the first 10
- * characters so we end up with a short unique ID.
- *
- * @param $field
- *   The field structure.
- *
- * @return
- *   A string containing the generated name for the database table.
- */
-function _field_sql_storage_tablename($field) {
-  if ($field['deleted']) {
-    return "field_deleted_data_" . substr(hash('sha256', $field['uuid']), 0, 10);
-  }
-  else {
-    return "field_data_{$field['field_name']}";
-  }
-}
-
-/**
- * Generates a table name for a field revision archive table.
- *
- * When a field is a deleted, the table is renamed to
- * {field_deleted_revision_FIELD_UUID}. To make sure we don't end up with table
- * names longer than 64 characters, we hash the uuid and return the first
- * 10 characters so we end up with a short unique ID.
- *
- * @param $name
- *   The field structure.
- *
- * @return
- *   A string containing the generated name for the database table.
- */
-function _field_sql_storage_revision_tablename($field) {
-  if ($field['deleted']) {
-    return "field_deleted_revision_" . substr(hash('sha256', $field['uuid']), 0, 10);
-  }
-  else {
-    return "field_revision_{$field['field_name']}";
-  }
-}
-
-/**
- * Generates a column name for a field data table.
- *
- * @param $name
- *   The name of the field.
- * @param $column
- *   The name of the column.
- *
- * @return
- *   A string containing a generated column name for a field data table that is
- *   unique among all other fields.
- */
-function _field_sql_storage_columnname($name, $column) {
-  return in_array($column, field_reserved_columns()) ? $column : $name . '_' . $column;
-}
-
-/**
- * Generates an index name for a field data table.
- *
- * @param $name
- *   The name of the field.
- * @param $column
- *   The name of the index.
- *
- * @return
- *   A string containing a generated index name for a field data table that is
- *   unique among all other fields.
- */
-function _field_sql_storage_indexname($name, $index) {
-  return $name . '_' . $index;
-}
-
-/**
- * Returns the database schema for a field.
- *
- * This may contain one or more tables. Each table will contain the columns
- * relevant for the specified field. Leave the $field's 'columns' and 'indexes'
- * keys empty to get only the base schema.
- *
- * @param $field
- *   The field structure for which to generate a database schema.
- * @return
- *   One or more tables representing the schema for the field.
- */
-function _field_sql_storage_schema($field) {
-  $deleted = $field['deleted'] ? 'deleted ' : '';
-  $current = array(
-    'description' => "Data storage for {$deleted}field {$field['id']} ({$field['field_name']})",
-    'fields' => array(
-      'entity_type' => array(
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => 'The entity type this data is attached to',
-      ),
-      'bundle' => array(
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance',
-      ),
-      'deleted' => array(
-        'type' => 'int',
-        'size' => 'tiny',
-        'not null' => TRUE,
-        'default' => 0,
-        'description' => 'A boolean indicating whether this data item has been deleted'
-      ),
-      'entity_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'description' => 'The entity id this data is attached to',
-      ),
-      'revision_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => FALSE,
-        'description' => 'The entity revision id this data is attached to, or NULL if the entity type is not versioned',
-      ),
-      // @todo Consider storing langcode as integer.
-      'langcode' => array(
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => 'The language code for this data item.',
-      ),
-      'delta' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'description' => 'The sequence number for this data item, used for multi-value fields',
-      ),
-    ),
-    'primary key' => array('entity_type', 'entity_id', 'deleted', 'delta', 'langcode'),
-    'indexes' => array(
-      'entity_type' => array('entity_type'),
-      'bundle' => array('bundle'),
-      'deleted' => array('deleted'),
-      'entity_id' => array('entity_id'),
-      'revision_id' => array('revision_id'),
-      'langcode' => array('langcode'),
-    ),
-  );
-
-  $schema = $field->getSchema();
-
-  // Add field columns.
-  foreach ($schema['columns'] as $column_name => $attributes) {
-    $real_name = _field_sql_storage_columnname($field['field_name'], $column_name);
-    $current['fields'][$real_name] = $attributes;
-  }
-
-  // Add indexes.
-  foreach ($schema['indexes'] as $index_name => $columns) {
-    $real_name = _field_sql_storage_indexname($field['field_name'], $index_name);
-    foreach ($columns as $column_name) {
-      // Indexes can be specified as either a column name or an array with
-      // column name and length. Allow for either case.
-      if (is_array($column_name)) {
-        $current['indexes'][$real_name][] = array(
-          _field_sql_storage_columnname($field['field_name'], $column_name[0]),
-          $column_name[1],
-        );
-      }
-      else {
-        $current['indexes'][$real_name][] = _field_sql_storage_columnname($field['field_name'], $column_name);
-      }
-    }
-  }
-
-  // Add foreign keys.
-  foreach ($schema['foreign keys'] as $specifier => $specification) {
-    $real_name = _field_sql_storage_indexname($field['field_name'], $specifier);
-    $current['foreign keys'][$real_name]['table'] = $specification['table'];
-    foreach ($specification['columns'] as $column_name => $referenced) {
-      $sql_storage_column = _field_sql_storage_columnname($field['field_name'], $column_name);
-      $current['foreign keys'][$real_name]['columns'][$sql_storage_column] = $referenced;
-    }
-  }
-
-  // Construct the revision table.
-  $revision = $current;
-  $revision['description'] = "Revision archive storage for {$deleted}field {$field['id']} ({$field['field_name']})";
-  $revision['primary key'] = array('entity_type', 'entity_id', 'revision_id', 'deleted', 'delta', 'langcode');
-  $revision['fields']['revision_id']['not null'] = TRUE;
-  $revision['fields']['revision_id']['description'] = 'The entity revision id this data is attached to';
-
-  return array(
-    _field_sql_storage_tablename($field) => $current,
-    _field_sql_storage_revision_tablename($field) => $revision,
-  );
-}
-
-/**
- * Implements hook_field_storage_create_field().
- */
-function field_sql_storage_field_storage_create_field($field) {
-  $schema = _field_sql_storage_schema($field);
-  foreach ($schema as $name => $table) {
-    db_create_table($name, $table);
-  }
-  // Do not rebuild the schema right now, since the field definition has not
-  // been saved yet. This will be done in hook_field_create_field().
-}
-
-/**
  * Implements hook_field_create_field().
  */
 function field_sql_storage_field_create_field($field) {
@@ -274,84 +60,10 @@ function field_sql_storage_field_update_forbid($field, $prior_field, $has_data)
 /**
  * Implements hook_field_storage_update_field().
  */
+/**
+ * Implements hook_field_storage_update_field().
+ */
 function field_sql_storage_field_storage_update_field($field, $prior_field, $has_data) {
-  if (! $has_data) {
-    // There is no data. Re-create the tables completely.
-
-    if (Database::getConnection()->supportsTransactionalDDL()) {
-      // If the database supports transactional DDL, we can go ahead and rely
-      // on it. If not, we will have to rollback manually if something fails.
-      $transaction = db_transaction();
-    }
-
-    try {
-      $prior_schema = _field_sql_storage_schema($prior_field);
-      foreach ($prior_schema as $name => $table) {
-        db_drop_table($name, $table);
-      }
-      $schema = _field_sql_storage_schema($field);
-      foreach ($schema as $name => $table) {
-        db_create_table($name, $table);
-      }
-    }
-    catch (Exception $e) {
-      if (Database::getConnection()->supportsTransactionalDDL()) {
-        $transaction->rollback();
-      }
-      else {
-        // Recreate tables.
-        $prior_schema = _field_sql_storage_schema($prior_field);
-        foreach ($prior_schema as $name => $table) {
-          if (!db_table_exists($name)) {
-            db_create_table($name, $table);
-          }
-        }
-      }
-      throw $e;
-    }
-  }
-  else {
-    // There is data, so there are no column changes. Drop all the
-    // prior indexes and create all the new ones, except for all the
-    // priors that exist unchanged.
-    $table = _field_sql_storage_tablename($prior_field);
-    $revision_table = _field_sql_storage_revision_tablename($prior_field);
-
-    $schema = $field->getSchema();
-    $prior_schema = $prior_field->getSchema();
-
-    foreach ($prior_schema['indexes'] as $name => $columns) {
-      if (!isset($schema['indexes'][$name]) || $columns != $schema['indexes'][$name]) {
-        $real_name = _field_sql_storage_indexname($field['field_name'], $name);
-        db_drop_index($table, $real_name);
-        db_drop_index($revision_table, $real_name);
-      }
-    }
-    $table = _field_sql_storage_tablename($field);
-    $revision_table = _field_sql_storage_revision_tablename($field);
-    foreach ($schema['indexes'] as $name => $columns) {
-      if (!isset($prior_schema['indexes'][$name]) || $columns != $prior_schema['indexes'][$name]) {
-        $real_name = _field_sql_storage_indexname($field['field_name'], $name);
-        $real_columns = array();
-        foreach ($columns as $column_name) {
-          // Indexes can be specified as either a column name or an array with
-          // column name and length. Allow for either case.
-          if (is_array($column_name)) {
-            $real_columns[] = array(
-              _field_sql_storage_columnname($field['field_name'], $column_name[0]),
-              $column_name[1],
-            );
-          }
-          else {
-            $real_columns[] = _field_sql_storage_columnname($field['field_name'], $column_name);
-          }
-        }
-        db_add_index($table, $real_name, $real_columns);
-        db_add_index($revision_table, $real_name, $real_columns);
-      }
-    }
-  }
-  drupal_get_schema(NULL, TRUE);
 }
 
 /**
@@ -360,228 +72,30 @@ function field_sql_storage_field_storage_update_field($field, $prior_field, $has
 function field_sql_storage_field_storage_delete_field($field) {
   // Mark all data associated with the field for deletion.
   $field['deleted'] = FALSE;
-  $table = _field_sql_storage_tablename($field);
-  $revision_table = _field_sql_storage_revision_tablename($field);
+  $table = Storage::tableName($field);
+  $revision_table = Storage::revisionTableName($field);
   db_update($table)
     ->fields(array('deleted' => 1))
     ->execute();
 
   // Move the table to a unique name while the table contents are being deleted.
   $field['deleted'] = TRUE;
-  $new_table = _field_sql_storage_tablename($field);
-  $revision_new_table = _field_sql_storage_revision_tablename($field);
+  $new_table = Storage::tableName($field);
+  $revision_new_table = Storage::revisionTableName($field);
   db_rename_table($table, $new_table);
   db_rename_table($revision_table, $revision_new_table);
   drupal_get_schema(NULL, TRUE);
 }
 
 /**
- * Implements hook_field_storage_load().
- */
-function field_sql_storage_field_storage_load($entity_type, $entities, $age, $fields, $options) {
-  $load_current = $age == FIELD_LOAD_CURRENT;
-
-  foreach ($fields as $field_id => $ids) {
-    // By the time this hook runs, the relevant field definitions have been
-    // populated and cached in FieldInfo, so calling field_info_field_by_id()
-    // on each field individually is more efficient than loading all fields in
-    // memory upfront with field_info_field_by_ids().
-    $field = field_info_field_by_id($field_id);
-    $field_name = $field['field_name'];
-    $table = $load_current ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field);
-
-    $query = db_select($table, 't')
-      ->fields('t')
-      ->condition('entity_type', $entity_type)
-      ->condition($load_current ? 'entity_id' : 'revision_id', $ids, 'IN')
-      ->condition('langcode', field_available_languages($entity_type, $field), 'IN')
-      ->orderBy('delta');
-
-    if (empty($options['deleted'])) {
-      $query->condition('deleted', 0);
-    }
-
-    $results = $query->execute();
-
-    $delta_count = array();
-    foreach ($results as $row) {
-      if (!isset($delta_count[$row->entity_id][$row->langcode])) {
-        $delta_count[$row->entity_id][$row->langcode] = 0;
-      }
-
-      if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$row->langcode] < $field['cardinality']) {
-        $item = array();
-        // For each column declared by the field, populate the item
-        // from the prefixed database column.
-        foreach ($field['columns'] as $column => $attributes) {
-          $column_name = _field_sql_storage_columnname($field_name, $column);
-          $item[$column] = $row->$column_name;
-        }
-
-        // Add the item to the field values for the entity.
-        $entities[$row->entity_id]->{$field_name}[$row->langcode][] = $item;
-        $delta_count[$row->entity_id][$row->langcode]++;
-      }
-    }
-  }
-}
-
-/**
- * Implements hook_field_storage_write().
- */
-function field_sql_storage_field_storage_write(EntityInterface $entity, $op, $fields) {
-  $vid = $entity->getRevisionId();
-  $id = $entity->id();
-  $bundle = $entity->bundle();
-  $entity_type = $entity->entityType();
-  if (!isset($vid)) {
-    $vid = $id;
-  }
-
-  foreach ($fields as $field_id) {
-    $field = field_info_field_by_id($field_id);
-    $field_name = $field['field_name'];
-    $table_name = _field_sql_storage_tablename($field);
-    $revision_name = _field_sql_storage_revision_tablename($field);
-
-    $all_langcodes = field_available_languages($entity_type, $field);
-    $field_langcodes = array_intersect($all_langcodes, array_keys((array) $entity->$field_name));
-
-    // Delete and insert, rather than update, in case a value was added.
-    if ($op == FIELD_STORAGE_UPDATE) {
-      // Delete language codes present in the incoming $entity->$field_name.
-      // Delete all language codes if $entity->$field_name is empty.
-      $langcodes = !empty($entity->$field_name) ? $field_langcodes : $all_langcodes;
-      if ($langcodes) {
-        // Only overwrite the field's base table if saving the default revision
-        // of an entity.
-        if ($entity->isDefaultRevision()) {
-          db_delete($table_name)
-            ->condition('entity_type', $entity_type)
-            ->condition('entity_id', $id)
-            ->condition('langcode', $langcodes, 'IN')
-            ->execute();
-        }
-        db_delete($revision_name)
-          ->condition('entity_type', $entity_type)
-          ->condition('entity_id', $id)
-          ->condition('revision_id', $vid)
-          ->condition('langcode', $langcodes, 'IN')
-          ->execute();
-      }
-    }
-
-    // Prepare the multi-insert query.
-    $do_insert = FALSE;
-    $columns = array('entity_type', 'entity_id', 'revision_id', 'bundle', 'delta', 'langcode');
-    foreach ($field['columns'] as $column => $attributes) {
-      $columns[] = _field_sql_storage_columnname($field_name, $column);
-    }
-    $query = db_insert($table_name)->fields($columns);
-    $revision_query = db_insert($revision_name)->fields($columns);
-
-    foreach ($field_langcodes as $langcode) {
-      $items = (array) $entity->{$field_name}[$langcode];
-      $delta_count = 0;
-      foreach ($items as $delta => $item) {
-        // We now know we have someting to insert.
-        $do_insert = TRUE;
-        $record = array(
-          'entity_type' => $entity_type,
-          'entity_id' => $id,
-          'revision_id' => $vid,
-          'bundle' => $bundle,
-          'delta' => $delta,
-          'langcode' => $langcode,
-        );
-        foreach ($field['columns'] as $column => $attributes) {
-          $record[_field_sql_storage_columnname($field_name, $column)] = isset($item[$column]) ? $item[$column] : NULL;
-        }
-        $query->values($record);
-        if (isset($vid)) {
-          $revision_query->values($record);
-        }
-
-        if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) {
-          break;
-        }
-      }
-    }
-
-    // Execute the query if we have values to insert.
-    if ($do_insert) {
-      // Only overwrite the field's base table if saving the default revision
-      // of an entity.
-      if ($entity->isDefaultRevision()) {
-        $query->execute();
-      }
-      $revision_query->execute();
-    }
-  }
-}
-
-/**
- * Implements hook_field_storage_delete().
- *
- * This function deletes data for all fields for an entity from the database.
- */
-function field_sql_storage_field_storage_delete(EntityInterface $entity, $fields) {
-  foreach (field_info_instances($entity->entityType(), $entity->bundle()) as $instance) {
-    if (isset($fields[$instance['field_id']])) {
-      $field = field_info_field_by_id($instance['field_id']);
-      field_sql_storage_field_storage_purge($entity, $field, $instance);
-    }
-  }
-}
-
-/**
- * Implements hook_field_storage_purge().
- *
- * This function deletes data from the database for a single field on
- * an entity.
- */
-function field_sql_storage_field_storage_purge(EntityInterface $entity, $field, $instance) {
-  $table_name = _field_sql_storage_tablename($field);
-  $revision_name = _field_sql_storage_revision_tablename($field);
-  db_delete($table_name)
-    ->condition('entity_type', $entity->entityType())
-    ->condition('entity_id', $entity->id())
-    ->execute();
-  db_delete($revision_name)
-    ->condition('entity_type', $entity->entityType())
-    ->condition('entity_id', $entity->id())
-    ->execute();
-}
-
-/**
- * Implements hook_field_storage_delete_revision().
- *
- * This function actually deletes the data from the database.
- */
-function field_sql_storage_field_storage_delete_revision(EntityInterface $entity, $fields) {
-  $vid = $entity->getRevisionId();
-  if (isset($vid)) {
-    foreach ($fields as $field_id) {
-      $field = field_info_field_by_id($field_id);
-      $revision_name = _field_sql_storage_revision_tablename($field);
-      db_delete($revision_name)
-        ->condition('entity_type', $entity->entityType())
-        ->condition('entity_id', $entity->id())
-        ->condition('revision_id', $vid)
-        ->execute();
-    }
-  }
-}
-
-/**
  * Implements hook_field_storage_delete_instance().
  *
  * This function simply marks for deletion all data associated with the field.
  */
 function field_sql_storage_field_storage_delete_instance($instance) {
   $field = field_info_field($instance['field_name']);
-  $table_name = _field_sql_storage_tablename($field);
-  $revision_name = _field_sql_storage_revision_tablename($field);
+  $table_name = Storage::tableName($field);
+  $revision_name = Storage::revisionTableName($field);
   db_update($table_name)
     ->fields(array('deleted' => 1))
     ->condition('entity_type', $instance['entity_type'])
@@ -603,8 +117,8 @@ function field_sql_storage_entity_bundle_rename($entity_type, $bundle_old, $bund
   foreach ($instances as $instance) {
     $field = field_info_field_by_id($instance['field_id']);
     if ($field['storage']['type'] == 'field_sql_storage') {
-      $table_name = _field_sql_storage_tablename($field);
-      $revision_name = _field_sql_storage_revision_tablename($field);
+      $table_name = Storage::tableName($field);
+      $revision_name = Storage::revisionTableName($field);
       db_update($table_name)
         ->fields(array('bundle' => $bundle_new))
         ->condition('entity_type', $entity_type)
@@ -626,8 +140,8 @@ function field_sql_storage_entity_bundle_rename($entity_type, $bundle_old, $bund
  * left is to delete the table.
  */
 function field_sql_storage_field_storage_purge_field($field) {
-  $table_name = _field_sql_storage_tablename($field);
-  $revision_name = _field_sql_storage_revision_tablename($field);
+  $table_name = Storage::tableName($field);
+  $revision_name = Storage::revisionTableName($field);
   db_drop_table($table_name);
   db_drop_table($revision_name);
 }
@@ -640,16 +154,16 @@ function field_sql_storage_field_storage_details($field) {
   if (!empty($field['columns'])) {
      // Add field columns.
     foreach ($field['columns'] as $column_name => $attributes) {
-      $real_name = _field_sql_storage_columnname($field['field_name'], $column_name);
+      $real_name = Storage::columnName($field['field_name'], $column_name);
       $columns[$column_name] = $real_name;
     }
     return array(
       'sql' => array(
         FIELD_LOAD_CURRENT => array(
-          _field_sql_storage_tablename($field) => $columns,
+          Storage::tableName($field) => $columns,
         ),
         FIELD_LOAD_REVISION => array(
-          _field_sql_storage_revision_tablename($field) => $columns,
+          Storage::revisionTableName($field) => $columns,
         ),
       ),
     );
diff --git a/core/modules/field_sql_storage/field_sql_storage.services.yml b/core/modules/field_sql_storage/field_sql_storage.services.yml
index 42126e4..e80972e 100644
--- a/core/modules/field_sql_storage/field_sql_storage.services.yml
+++ b/core/modules/field_sql_storage/field_sql_storage.services.yml
@@ -2,3 +2,7 @@ services:
   entity.query.field_sql_storage:
     class: Drupal\field_sql_storage\Entity\QueryFactory
     arguments: ['@database']
+  entity.storage.backend.field_sql_storage:
+    class: Drupal\field_sql_storage\Entity\Storage
+    tags:
+      - { name: event_subscriber }
diff --git a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Storage.php b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Storage.php
new file mode 100644
index 0000000..9bee88a
--- /dev/null
+++ b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Storage.php
@@ -0,0 +1,585 @@
+<?php
+/**
+ * Contains \Drupal\field_sql_storage\Entity\Storage.
+ */
+
+namespace Drupal\field_sql_storage\Entity;
+
+
+use Drupal\Core\Database\Database;
+use Drupal\Core\Entity\InstanceEvent;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\Event;
+use Drupal\Core\Entity\Events;
+use Drupal\Core\Entity\EventMultiple;
+use Drupal\Core\Entity\PurgeEvent;
+use Drupal\Core\Entity\UpdateFieldEvent;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class Storage implements EventSubscriberInterface {
+
+  /**
+   * Generates a table name for a field data table.
+   *
+   * When a field is a deleted, the table is renamed to
+   * {field_deleted_data_FIELD_UUID}. To make sure we don't end up with table
+   * names longer than 64 characters, we hash the uuid and return the first 10
+   * characters so we end up with a short unique ID.
+   *
+   * @param $field
+   *   The field structure.
+   *
+   * @return
+   *   A string containing the generated name for the database table.
+   */
+  public static function tableName($field) {
+    if ($field['deleted']) {
+      return "field_deleted_data_" . substr(hash('sha256', $field['uuid']), 0, 10);
+    }
+    else {
+      return "field_data_{$field['field_name']}";
+    }
+  }
+
+  /**
+   * Generates a column name for a field data table.
+   *
+   * @param $name
+   *   The name of the field.
+   * @param $column
+   *   The name of the column.
+   *
+   * @return
+   *   A string containing a generated column name for a field data table that is
+   *   unique among all other fields.
+   */
+  public static function columnName($name, $column) {
+    return in_array($column, field_reserved_columns()) ? $column : $name . '_' . $column;
+  }
+
+  /**
+   * Generates an index name for a field data table.
+   *
+   * @param $name
+   *   The name of the field.
+   * @param $column
+   *   The name of the index.
+   *
+   * @return
+   *   A string containing a generated index name for a field data table that is
+   *   unique among all other fields.
+   */
+  public static function indexName($name, $index) {
+    return $name . '_' . $index;
+  }
+
+  /**
+   * Generates a table name for a field revision archive table.
+   *
+   * When a field is a deleted, the table is renamed to
+   * {field_deleted_revision_FIELD_UUID}. To make sure we don't end up with table
+   * names longer than 64 characters, we hash the uuid and return the first
+   * 10 characters so we end up with a short unique ID.
+   *
+   * @param $name
+   *   The field structure.
+   *
+   * @return
+   *   A string containing the generated name for the database table.
+   */
+  public static function revisionTableName($field) {
+    if ($field['deleted']) {
+      return "field_deleted_revision_" . substr(hash('sha256', $field['uuid']), 0, 10);
+    }
+    else {
+      return "field_revision_{$field['field_name']}";
+    }
+  }
+
+  /**
+   * Returns the database schema for a field.
+   *
+   * This may contain one or more tables. Each table will contain the columns
+   * relevant for the specified field. Leave the $field's 'columns' and 'indexes'
+   * keys empty to get only the base schema.
+   *
+   * @param $field
+   *   The field structure for which to generate a database schema.
+   * @return
+   *   One or more tables representing the schema for the field.
+   */
+  public static function schema($field) {
+    $deleted = $field['deleted'] ? 'deleted ' : '';
+    $current = array(
+      'description' => "Data storage for {$deleted}field {$field['id']} ({$field['field_name']})",
+      'fields' => array(
+        'entity_type' => array(
+          'type' => 'varchar',
+          'length' => 128,
+          'not null' => TRUE,
+          'default' => '',
+          'description' => 'The entity type this data is attached to',
+        ),
+        'bundle' => array(
+          'type' => 'varchar',
+          'length' => 128,
+          'not null' => TRUE,
+          'default' => '',
+          'description' => 'The field instance bundle to which this row belongs, used when deleting a field instance',
+        ),
+        'deleted' => array(
+          'type' => 'int',
+          'size' => 'tiny',
+          'not null' => TRUE,
+          'default' => 0,
+          'description' => 'A boolean indicating whether this data item has been deleted'
+        ),
+        'entity_id' => array(
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'description' => 'The entity id this data is attached to',
+        ),
+        'revision_id' => array(
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => FALSE,
+          'description' => 'The entity revision id this data is attached to, or NULL if the entity type is not versioned',
+        ),
+        // @todo Consider storing langcode as integer.
+        'langcode' => array(
+          'type' => 'varchar',
+          'length' => 32,
+          'not null' => TRUE,
+          'default' => '',
+          'description' => 'The language code for this data item.',
+        ),
+        'delta' => array(
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+          'description' => 'The sequence number for this data item, used for multi-value fields',
+        ),
+      ),
+      'primary key' => array('entity_type', 'entity_id', 'deleted', 'delta', 'langcode'),
+      'indexes' => array(
+        'entity_type' => array('entity_type'),
+        'bundle' => array('bundle'),
+        'deleted' => array('deleted'),
+        'entity_id' => array('entity_id'),
+        'revision_id' => array('revision_id'),
+        'langcode' => array('langcode'),
+      ),
+    );
+
+    $schema = $field->getSchema();
+
+    // Add field columns.
+    foreach ($schema['columns'] as $column_name => $attributes) {
+      $real_name = static::columnName($field['field_name'], $column_name);
+      $current['fields'][$real_name] = $attributes;
+    }
+
+    // Add indexes.
+    foreach ($schema['indexes'] as $index_name => $columns) {
+      $real_name = static::indexName($field['field_name'], $index_name);
+      foreach ($columns as $column_name) {
+        // Indexes can be specified as either a column name or an array with
+        // column name and length. Allow for either case.
+        if (is_array($column_name)) {
+          $current['indexes'][$real_name][] = array(
+            static::columnName($field['field_name'], $column_name[0]),
+            $column_name[1],
+          );
+        }
+        else {
+          $current['indexes'][$real_name][] = static::columnName($field['field_name'], $column_name);
+        }
+      }
+    }
+
+    // Add foreign keys.
+    foreach ($schema['foreign keys'] as $specifier => $specification) {
+      $real_name = static::indexName($field['field_name'], $specifier);
+      $current['foreign keys'][$real_name]['table'] = $specification['table'];
+      foreach ($specification['columns'] as $column_name => $referenced) {
+        $sql_storage_column = static::columnName($field['field_name'], $column_name);
+        $current['foreign keys'][$real_name]['columns'][$sql_storage_column] = $referenced;
+      }
+    }
+
+    // Construct the revision table.
+    $revision = $current;
+    $revision['description'] = "Revision archive storage for {$deleted}field {$field['id']} ({$field['field_name']})";
+    $revision['primary key'] = array('entity_type', 'entity_id', 'revision_id', 'deleted', 'delta', 'langcode');
+    $revision['fields']['revision_id']['not null'] = TRUE;
+    $revision['fields']['revision_id']['description'] = 'The entity revision id this data is attached to';
+
+    return array(
+      static::tableName($field) => $current,
+      static::revisionTableName($field) => $revision,
+    );
+  }
+
+  public function revisionLoad(EventMultiple $event) {
+    $this->doLoad($event, FIELD_LOAD_REVISION);
+  }
+
+  public function load(EventMultiple $event) {
+    $this->doLoad($event, FIELD_LOAD_CURRENT);
+  }
+
+  public function doLoad(EventMultiple $event, $age) {
+    if ($event->getBackendType() != 'sql') {
+      return;
+    }
+    $entity_type = $event->getEntityType();
+    $entities = $event->getBCEntities();
+    if (!$entities) {
+      return;
+    }
+
+    foreach (field_info_field_map() as $field_name => $data) {
+      if (!isset($data['bundles'][$entity_type])) {
+        continue;
+      }
+      // By the time this hook runs, the relevant field definitions have been
+      // populated and cached in FieldInfo, so calling field_info_field()
+      // on each field individually is more efficient than loading all fields in
+      // memory upfront with field_info_field_by_ids().
+      $field = field_info_field($field_name);
+      $field_name = $field['field_name'];
+      if ($age === FIELD_LOAD_CURRENT) {
+        $query = db_select(static::tableName($field), 't')
+          ->condition('entity_id', $event->getIDs(), 'IN');
+      }
+      else {
+        $query = db_select(static::revisionTableName($field), 't')
+          ->condition('revision_id', $event->getRevisionIDs(), 'IN');
+      }
+      $results = $query
+        ->fields('t')
+        ->condition('entity_type', $entity_type)
+        ->condition('langcode', field_available_languages($entity_type, $field), 'IN')
+        ->orderBy('delta')
+        ->condition('deleted', 0)
+        ->execute();
+
+      $delta_count = array();
+      foreach ($results as $row) {
+        if (!isset($delta_count[$row->entity_id][$row->langcode])) {
+          $delta_count[$row->entity_id][$row->langcode] = 0;
+        }
+
+        if ($field['cardinality'] == FIELD_CARDINALITY_UNLIMITED || $delta_count[$row->entity_id][$row->langcode] < $field['cardinality']) {
+          $item = array();
+          // For each column declared by the field, populate the item
+          // from the prefixed database column.
+          foreach ($field['columns'] as $column => $attributes) {
+            $column_name = static::columnName($field_name, $column);
+            $item[$column] = $row->$column_name;
+          }
+
+          // Add the item to the field values for the entity.
+          $entities[$row->entity_id]->{$field_name}[$row->langcode][] = $item;
+          $delta_count[$row->entity_id][$row->langcode]++;
+        }
+      }
+    }
+  }
+
+  public function insert(Event $event) {
+    if ($event->getBackendType() != 'sql') {
+      return;
+    }
+    $this->write($event->getBCEntity(), FIELD_STORAGE_INSERT);
+  }
+
+  public function update(Event $event) {
+    if ($event->getBackendType() != 'sql') {
+      return;
+    }
+    $this->write($event->getBCEntity(), FIELD_STORAGE_UPDATE);
+  }
+
+  public function write(EntityInterface $entity, $op) {
+    $vid = $entity->getRevisionId();
+    $id = $entity->id();
+    $bundle = $entity->bundle();
+    $entity_type = $entity->entityType();
+    if (!isset($vid)) {
+      $vid = $id;
+    }
+
+    foreach (field_info_field_map() as $field_name => $data) {
+      if (!isset($data['bundles'][$entity_type]) || !isset($entity->$field_name)) {
+        continue;
+      }
+      // By the time this hook runs, the relevant field definitions have been
+      // populated and cached in FieldInfo, so calling field_info_field()
+      // on each field individually is more efficient than loading all fields in
+      // memory upfront with field_info_field_by_ids().
+      $field = field_info_field($field_name);
+      $table_name = static::tableName($field);
+      $revision_name = static::revisionTableName($field);
+
+      $all_langcodes = field_available_languages($entity_type, $field);
+      $field_langcodes = array_intersect($all_langcodes, array_keys((array) $entity->$field_name));
+
+      // Delete and insert, rather than update, in case a value was added.
+      if ($op == FIELD_STORAGE_UPDATE) {
+        // Delete language codes present in the incoming $entity->$field_name.
+        // Delete all language codes if $entity->$field_name is empty.
+        $langcodes = !empty($entity->$field_name) ? $field_langcodes : $all_langcodes;
+        if ($langcodes) {
+          // Only overwrite the field's base table if saving the default revision
+          // of an entity.
+          if ($entity->isDefaultRevision()) {
+            db_delete($table_name)
+              ->condition('entity_type', $entity_type)
+              ->condition('entity_id', $id)
+              ->condition('langcode', $langcodes, 'IN')
+              ->execute();
+          }
+          db_delete($revision_name)
+            ->condition('entity_type', $entity_type)
+            ->condition('entity_id', $id)
+            ->condition('revision_id', $vid)
+            ->condition('langcode', $langcodes, 'IN')
+            ->execute();
+        }
+      }
+
+      // Prepare the multi-insert query.
+      $do_insert = FALSE;
+      $columns = array('entity_type', 'entity_id', 'revision_id', 'bundle', 'delta', 'langcode');
+      foreach ($field['columns'] as $column => $attributes) {
+        $columns[] = static::columnName($field_name, $column);
+      }
+      $query = db_insert($table_name)->fields($columns);
+      $revision_query = db_insert($revision_name)->fields($columns);
+
+      foreach ($field_langcodes as $langcode) {
+        $items = (array) $entity->{$field_name}[$langcode];
+        $delta_count = 0;
+        foreach ($items as $delta => $item) {
+          // We now know we have someting to insert.
+          $do_insert = TRUE;
+          $record = array(
+            'entity_type' => $entity_type,
+            'entity_id' => $id,
+            'revision_id' => $vid,
+            'bundle' => $bundle,
+            'delta' => $delta,
+            'langcode' => $langcode,
+          );
+          foreach ($field['columns'] as $column => $attributes) {
+            $record[static::columnName($field_name, $column)] = isset($item[$column]) ? $item[$column] : NULL;
+          }
+          $query->values($record);
+          if (isset($vid)) {
+            $revision_query->values($record);
+          }
+
+          if ($field['cardinality'] != FIELD_CARDINALITY_UNLIMITED && ++$delta_count == $field['cardinality']) {
+            break;
+          }
+        }
+      }
+
+      // Execute the query if we have values to insert.
+      if ($do_insert) {
+        // Only overwrite the field's base table if saving the default revision
+        // of an entity.
+        if ($entity->isDefaultRevision()) {
+          $query->execute();
+        }
+        $revision_query->execute();
+      }
+    }
+  }
+
+  public function delete(EventMultiple $event) {
+    if ($event->getBackendType() != 'sql') {
+      return;
+    }
+    foreach ($event->getBCEntities() as $entity) {
+      $this->doDelete($entity, FALSE);
+    }
+  }
+
+  /**
+   * Implements hook_field_storage_delete_revision().
+   *
+   * This function actually deletes the data from the database.
+   */
+  function revisionDelete(Event $event) {
+    if ($event->getBackendType() != 'sql') {
+      return;
+    }
+    $this->doDelete($event->getBCEntity(), TRUE);
+  }
+
+  function doDelete(EntityInterface $entity, $revision_only) {
+    $entity_type = $entity->entityType();
+    $vid = $entity->getRevisionId();
+    if (!$revision_only || isset($vid)) {
+      foreach (field_info_field_map() as $field_name => $data) {
+        if (!isset($data['bundles'][$entity_type])) {
+          continue;
+        }
+        $field = field_info_field($field_name);
+        if ($revision_only) {
+          $query = db_delete(static::revisionTableName($field))
+            ->condition('revision_id', $vid);
+        }
+        else {
+          $query = db_delete(static::tableName($field));
+        }
+        $query->condition('entity_type', $entity_type)
+          ->condition('entity_id', $entity->id())
+          ->execute();
+      }
+    }
+  }
+
+  function purge(PurgeEvent $event, $field, $instance) {
+    if ($event->getBackendType() != 'sql') {
+      return;
+    }
+    $entity = $event->getBCEntity();
+    $table_name = static::tableName($field);
+    $revision_name = static::revisionTableName($field);
+    db_delete($table_name)
+      ->condition('entity_type', $entity->entityType())
+      ->condition('entity_id', $entity->id())
+      ->execute();
+    db_delete($revision_name)
+      ->condition('entity_type', $entity->entityType())
+      ->condition('entity_id', $entity->id())
+      ->execute();
+  }
+
+  function createInstance(InstanceEvent $event) {
+    $instance = $event->getInstance();
+    $field = field_info_field($instance['field_name']);
+    $schema = Storage::schema($field);
+    foreach ($schema as $name => $table) {
+      if (!db_table_exists($name)) {
+        db_create_table($name, $table);
+      }
+    }
+  }
+
+  function deleteInstance(InstanceEvent $event) {
+    $instance = $event->getInstance();
+    $map = field_info_field_map();
+    if (empty($map[$instance['field_name']]['bundles'][$instance['entity_type']])) {
+      $schema = Storage::schema(field_info_field($instance['field_name']));
+      foreach ($schema as $name => $table) {
+        db_drop_table($name);
+      }
+    }
+  }
+
+  function updateField(UpdateFieldEvent $event) {
+    $field = $event->getField();
+    $original = $event->getOriginal();
+    if (!$event->hasData()) {
+      $transactional_ddl = Database::getConnection()->supportsTransactionalDDL();
+      // There is no data. Re-create the tables completely.
+      if ($transactional_ddl) {
+        // If the database supports transactional DDL, we can go ahead and rely
+        // on it. If not, we will have to rollback manually if something fails.
+        $transaction = db_transaction();
+      }
+
+      try {
+        $prior_schema = Storage::schema($original);
+        foreach ($prior_schema as $name => $table) {
+          db_drop_table($name, $table);
+        }
+        $schema = Storage::schema($field);
+        foreach ($schema as $name => $table) {
+          db_create_table($name, $table);
+        }
+      }
+      catch (\Exception $e) {
+        if ($transactional_ddl) {
+          $transaction->rollback();
+        }
+        else {
+          // Recreate tables.
+          $prior_schema = Storage::schema($original);
+          foreach ($prior_schema as $name => $table) {
+            if (!db_table_exists($name)) {
+              db_create_table($name, $table);
+            }
+          }
+        }
+        throw $e;
+      }
+    }
+    else {
+      // There is data, so there are no column changes. Drop all the
+      // prior indexes and create all the new ones, except for all the
+      // priors that exist unchanged.
+      $table = Storage::tablename($original);
+      $revision_table = Storage::revisionTableName($original);
+
+      $schema = $field->getSchema();
+      $prior_schema = $original->getSchema();
+
+      foreach ($prior_schema['indexes'] as $name => $columns) {
+        if (!isset($schema['indexes'][$name]) || $columns != $schema['indexes'][$name]) {
+          $real_name = Storage::indexName($field['field_name'], $name);
+          db_drop_index($table, $real_name);
+          db_drop_index($revision_table, $real_name);
+        }
+      }
+      $table = Storage::tablename($field);
+      $revision_table = Storage::revisionTableName($field);
+      foreach ($schema['indexes'] as $name => $columns) {
+        if (!isset($prior_schema['indexes'][$name]) || $columns != $prior_schema['indexes'][$name]) {
+          $real_name = Storage::indexName($field['field_name'], $name);
+          $real_columns = array();
+          foreach ($columns as $column_name) {
+            // Indexes can be specified as either a column name or an array with
+            // column name and length. Allow for either case.
+            if (is_array($column_name)) {
+              $real_columns[] = array(
+                Storage::columnName($field['field_name'], $column_name[0]),
+                $column_name[1],
+              );
+            }
+            else {
+              $real_columns[] = Storage::columnName($field['field_name'], $column_name);
+            }
+          }
+          db_add_index($table, $real_name, $real_columns);
+          db_add_index($revision_table, $real_name, $real_columns);
+        }
+      }
+    }
+    drupal_get_schema(NULL, TRUE);
+
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[Events::CREATE_INSTANCE] = 'createInstance';
+    $events[Events::POST_DELETE] = 'delete';
+    $events[Events::DELETE_INSTANCE] = 'deleteInstance';
+    $events[Events::INSERT] = 'insert';
+    $events[Events::POST_LOAD] = 'load';
+    $events[Events::PURGE] = 'purge';
+    $events[Events::POST_DELETE_REVISION] = 'revisionDelete';
+    $events[Events::REVISION_LOAD] = 'revisionLoad';
+    $events[Events::UPDATE] = 'update';
+    $events[Events::UPDATE_FIELD] = 'updateField';
+    return $events;
+  }
+
+}
diff --git a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php
index a387cf4..9fe6775 100644
--- a/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php
+++ b/core/modules/field_sql_storage/lib/Drupal/field_sql_storage/Entity/Tables.php
@@ -156,7 +156,7 @@ function addField($field, $type, $langcode) {
           $column = 'value';
         }
         $table = $this->ensureFieldTable($index_prefix, $field, $type, $langcode, $base_table, $entity_id_field, $field_id_field);
-        $sql_column = _field_sql_storage_columnname($field['field_name'], $column);
+        $sql_column = Storage::columnName($field['field_name'], $column);
       }
       // This is an entity property (non-configurable field).
       else {
@@ -184,7 +184,8 @@ function addField($field, $type, $langcode) {
           // If there are bundles, pick one. It does not matter which,
           // properties exist on all bundles.
           if (!empty($entity_info['entity keys']['bundle'])) {
-            $values[$entity_info['entity keys']['bundle']] = key(entity_get_bundles('node'));
+            $bundles = entity_get_bundles('node');
+            $values[$entity_info['entity keys']['bundle']] = key($bundles);
           }
           $entity = entity_create($entity_type, $values);
           $propertyDefinitions = $entity->$specifier->getPropertyDefinitions();
@@ -241,7 +242,7 @@ protected function ensureEntityTable($index_prefix, $property, $type, $langcode,
   protected function ensureFieldTable($index_prefix, &$field, $type, $langcode, $base_table, $entity_id_field, $field_id_field) {
     $field_name = $field['field_name'];
     if (!isset($this->fieldTables[$index_prefix . $field_name])) {
-      $table = $this->sqlQuery->getMetaData('age') == FIELD_LOAD_CURRENT ? _field_sql_storage_tablename($field) : _field_sql_storage_revision_tablename($field);
+      $table = $this->sqlQuery->getMetaData('age') == FIELD_LOAD_CURRENT ? Storage::tableName($field) : Storage::revisionTableName($field);
       if ($field['cardinality'] != 1) {
         $this->sqlQuery->addMetaData('simple_query', FALSE);
       }
diff --git a/core/modules/field_ui/field_ui.admin.inc b/core/modules/field_ui/field_ui.admin.inc
index 2d7e7ca..fac05c3 100644
--- a/core/modules/field_ui/field_ui.admin.inc
+++ b/core/modules/field_ui/field_ui.admin.inc
@@ -457,6 +457,10 @@ function field_ui_existing_field_options($entity_type, $bundle) {
   $field_types = field_info_field_types();
 
   foreach (field_info_instances() as $existing_entity_type => $bundles) {
+    // Only deal with instances on the same storage.
+    if (!FieldInstance::isSameStorage($entity_type, $existing_entity_type)) {
+      continue;
+    }
     foreach ($bundles as $existing_bundle => $instances) {
       // No need to look in the current bundle.
       if (!($existing_bundle == $bundle && $existing_entity_type == $entity_type)) {
diff --git a/core/modules/file/file.views.inc b/core/modules/file/file.views.inc
index 6b0fb59..6cd91b3 100644
--- a/core/modules/file/file.views.inc
+++ b/core/modules/file/file.views.inc
@@ -6,6 +6,7 @@
  *
  * @ingroup views_module_handlers
  */
+use Drupal\field_sql_storage\Entity\Storage;
 
 /**
  * Implements hook_views_data().
@@ -488,7 +489,7 @@ function file_field_views_data_views_data_alter(&$data, $field) {
       'help' => t('Relate each @entity with a @field set to the file.', array('@entity' => $entity, '@field' => $label)),
       'id' => 'entity_reverse',
       'field_name' => $field['field_name'],
-      'field table' => _field_sql_storage_tablename($field),
+      'field table' => Storage::tableName($field),
       'field field' => $field['field_name'] . '_fid',
       'base' => $entity_info['base_table'],
       'base field' => $entity_info['entity_keys']['id'],
diff --git a/core/modules/image/image.views.inc b/core/modules/image/image.views.inc
index 77c4ea3..deee0ca 100644
--- a/core/modules/image/image.views.inc
+++ b/core/modules/image/image.views.inc
@@ -51,7 +51,7 @@ function image_field_views_data_views_data_alter(&$data, $field) {
       'help' => t('Relate each @entity with a @field set to the image.', array('@entity' => $entity, '@field' => $label)),
       'id' => 'entity_reverse',
       'field_name' => $field['field_name'],
-      'field table' => _field_sql_storage_tablename($field),
+      'field table' => Storage::tableName($field),
       'field field' => $field['field_name'] . '_fid',
       'base' => $entity_info['base_table'],
       'base field' => $entity_info['entity_keys']['id'],
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 de04d97..da887cd 100644
--- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php
@@ -17,14 +17,14 @@
  * 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.
    *
    * @var bool
    */
-  protected $preventReparenting = FALSE;
+  protected $reparenting = TRUE;
 
   /**
    * Holds an array of router item schema fields.
@@ -34,7 +34,7 @@ class MenuLinkStorageController extends DatabaseStorageController {
   protected static $routerItemFields = array();
 
   /**
-   * Overrides DatabaseStorageController::__construct().
+   * {@inheritdoc}
    */
   public function __construct($entityType) {
     parent::__construct($entityType);
@@ -45,7 +45,7 @@ public function __construct($entityType) {
   }
 
   /**
-   * Overrides DatabaseStorageController::buildQuery().
+   * {@inheritdoc}
    */
   protected function buildQuery($ids, $revision_id = FALSE) {
     $query = parent::buildQuery($ids, $revision_id);
@@ -56,40 +56,7 @@ protected function buildQuery($ids, $revision_id = FALSE) {
   }
 
   /**
-   * Overrides DatabaseStorageController::attachLoad().
-   *
-   * @todo Don't call parent::attachLoad() at all because we want to be able to
-   * control the entity load hooks.
-   */
-  protected function attachLoad(&$menu_links, $load_revision = FALSE) {
-    $routes = array();
-
-    foreach ($menu_links as &$menu_link) {
-      $menu_link->options = unserialize($menu_link->options);
-
-      // Use the weight property from the menu link.
-      $menu_link->router_item['weight'] = $menu_link->weight;
-
-      // For all links that have an associated route, load the route object now
-      // and save it on the object. That way we avoid a select N+1 problem later.
-      if ($menu_link->route_name) {
-        $routes[$menu_link->id()] = $menu_link->route_name;
-      }
-    }
-
-    // Now mass-load any routes needed and associate them.
-    if ($routes) {
-      $route_objects = drupal_container()->get('router.route_provider')->getRoutesByNames($routes);
-      foreach ($routes as $entity_id => $route) {
-        $menu_links[$entity_id]->setRouteObject($route_objects[$route]);
-      }
-    }
-
-    parent::attachLoad($menu_links, $load_revision);
-  }
-
-  /**
-   * Overrides DatabaseStorageController::save().
+   * {@inheritdoc}
    */
   public function save(EntityInterface $entity) {
     // We return SAVED_UPDATED by default because the logic below might not
@@ -112,8 +79,7 @@ public function save(EntityInterface $entity) {
       // Unlike the save() method from DatabaseStorageController, we invoke the
       // '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);
+      $this->notify('pre_save', $entity);
 
       // 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
@@ -126,16 +92,14 @@ public function save(EntityInterface $entity) {
         if ($return) {
           if (!$entity->isNew()) {
             $this->resetCache(array($entity->{$this->idKey}));
-            $this->postSave($entity, TRUE);
-            $this->invokeHook('update', $entity);
+            $this->notify('update', $entity);
           }
           else {
             $return = SAVED_NEW;
             $this->resetCache();
 
             $entity->enforceIsNew(FALSE);
-            $this->postSave($entity, FALSE);
-            $this->invokeHook('insert', $entity);
+            $this->notify('insert', $entity);
           }
         }
       }
@@ -154,148 +118,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);
-      }
-    }
+  public function setReparenting($value) {
+    $this->reparenting = $value;
   }
 
   /**
-   * DatabaseStorageController::postSave().
+   * {@inheritdoc}
    */
-  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();
+  public function getReparenting() {
+    return $this->reparenting;
   }
 
   /**
-   * Sets an internal flag that allows us to prevent the reparenting operations
-   * executed during deletion.
-   *
-   * @param bool $value
-   */
-  public function preventReparenting($value = FALSE) {
-    $this->preventReparenting = $value;
-  }
-
-  /**
-   * Overrides DatabaseStorageController::preDelete().
-   */
-  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);
-        }
-      }
-    }
-  }
-
-  /**
-   * 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);
@@ -321,10 +158,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);
@@ -340,16 +174,13 @@ 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.
-      $query = \Drupal::entityQuery($this->entityType);
+      $query = \Drupal::entityQuery($entity->entityType());
       $query
         ->condition('menu_name', $entity->menu_name)
         ->condition('hidden', 0)
@@ -369,26 +200,9 @@ 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.
+   * {@inheritdoc}
    */
-  protected function findParent(EntityInterface $entity, array $parent_candidates = array()) {
+  public function findParent(EntityInterface $entity, array $parent_candidates = array()) {
     $parent = FALSE;
 
     // This item is explicitely top-level, skip the rest of the parenting.
@@ -455,14 +269,9 @@ protected function findParent(EntityInterface $entity, array $parent_candidates
   }
 
   /**
-   * 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.
+   * {@inheritdoc}
    */
-  protected function setParents(EntityInterface $entity, EntityInterface $parent) {
+  public function setParents(EntityInterface $entity, EntityInterface $parent) {
     $i = 1;
     while ($i < $entity->depth) {
       $p = 'p' . $i++;
@@ -478,16 +287,7 @@ protected function setParents(EntityInterface $entity, EntityInterface $parent)
   }
 
   /**
-   * 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
@@ -511,15 +311,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 = db_update($this->entityInfo['base_table']);
 
     $query->fields(array('menu_name' => $entity->menu_name));
@@ -563,10 +357,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.
+   * {@inheriitdoc}
    */
   public function countMenuLinks($menu_name) {
     $query = \Drupal::entityQuery($this->entityType);
@@ -575,4 +366,5 @@ public function countMenuLinks($menu_name) {
       ->count();
     return $query->execute();
   }
+
 }
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..1d1c722
--- /dev/null
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageControllerInterface.php
@@ -0,0 +1,127 @@
+<?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 {
+
+  /**
+   * 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.
+   */
+  function findParent(EntityInterface $entity, array $parent_candidates = array());
+
+  /**
+   * 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.
+   */
+  public function setParents(EntityInterface $entity, EntityInterface $parent);
+
+  /**
+   * 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);
+
+  /**
+   * 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);
+
+  /**
+   * Enable / disable re-parenting during delete.
+   *
+   * @param bool $value
+   *  TRUE when re-parenting is desired during delete, FALSE when not.
+   */
+  public function setReparenting($value);
+
+  /**
+   * Returns TRUE when re-parenting is desired during delete.
+   *
+   * @return bool
+   *   TRUE when re-parenting is allowed, FALSE when disabled.
+   */
+  public function getReparenting();
+
+  /**
+   * Returns the number of menu links from a menu.
+   *
+   * @param string $menu_name
+   *   The unique name of a menu.
+   */
+  public function countMenuLinks($menu_name);
+
+
+    /**
+   * 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();
+
+}
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkSubscriber.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkSubscriber.php
new file mode 100644
index 0000000..a5943e3
--- /dev/null
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkSubscriber.php
@@ -0,0 +1,200 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link\MenuLinkSubscriber.
+ */
+
+namespace Drupal\menu_link;
+
+use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\Event;
+use Drupal\Core\Entity\EventMultiple;
+use Symfony\Cmf\Component\Routing\RouteProviderInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class MenuLinkSubscriber implements EventSubscriberInterface {
+
+  /**
+   * @var \Drupal\Core\Entity\EntityManager
+   */
+  protected $entityManger;
+
+  /**
+   * @var \Symfony\Cmf\Component\Routing\RouteProviderInterface
+   */
+  protected $routeProvider;
+
+  /**
+   * @param \Drupal\Core\Entity\EntityManager $entity_manager
+   * @param \Symfony\Cmf\Component\Routing\RouteProviderInterface $route_provider
+   */
+  function __construct(EntityManager $entity_manager, RouteProviderInterface $route_provider) {
+    $this->entityManager = $entity_manager;
+    $this->routeProvider = $route_provider;
+  }
+
+  function postLoad(EventMultiple $event) {
+    $menu_links = $event->getEntities();
+    $routes = array();
+
+    foreach ($menu_links as $menu_link) {
+      $menu_link->options = unserialize($menu_link->options);
+
+      // Use the weight property from the menu link.
+      $menu_link->router_item['weight'] = $menu_link->weight;
+
+      // For all links that have an associated route, load the route object now
+      // and save it on the object. That way we avoid a select N+1 problem later.
+      if ($menu_link->route_name) {
+        $routes[$menu_link->id()] = $menu_link->route_name;
+      }
+    }
+
+    // Now mass-load any routes needed and associate them.
+    if ($routes) {
+      $route_objects = $this->routeProvider->getRoutesByNames($routes);
+      foreach ($routes as $entity_id => $route) {
+        $menu_links[$entity_id]->setRouteObject($route_objects[$route]);
+      }
+    }
+  }
+
+  function write(Event $event) {
+    $entity = $event->getEntity();
+    // Check the has_children status of the parent.
+    $this->getStorageController()->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();
+  }
+
+  /**
+   * @return \Drupal\menu_link\MenuLinkStorageControllerInterface
+   */
+  protected function getStorageController() {
+    return $this->entityManager->getStorageController('menu_link');
+  }
+
+  public function preSave(Event $event) {
+    $entity = $event->getEntity();
+    $storage_controller = $this->getStorageController();
+    // 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 = $storage_controller->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 - $storage_controller->findChildrenRelativeDepth($entity->original) - 1;
+      }
+      else {
+        $limit = MENU_MAX_DEPTH - 1;
+      }
+      if ($parent->depth > $limit) {
+        return FALSE;
+      }
+      $entity->depth = $parent->depth + 1;
+      $storage_controller->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)) {
+      $storage_controller->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);
+      }
+    }
+  }
+
+  /**
+   * Overrides DatabaseStorageController::preDelete().
+   */
+  public function preDelete(EventMultiple $event) {
+    $entities = $event->getEntities();
+    $storage_controller = $this->getStorageController();
+    // Nothing to do if we don't want to reparent children.
+    if ($storage_controller->getReparenting()) {
+      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);
+        }
+      }
+    }
+  }
+
+  public function postDelete(EventMultiple $event) {
+    $entities = $event->getEntities();
+    $storage_controller = $this->getStorageController();
+    $affected_menus = array();
+    // Update the has_children status of the parent.
+    foreach ($entities as $entity) {
+      if (!$storage_controller->getReparenting()) {
+        $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();
+  }
+
+  public static function getSubscribedEvents() {
+    $events['menu_link.entity.insert'] = array('write', 255);
+    $events['menu_link.entity.post_delete'] = array('postDelete', 255);
+    $events['menu_link.entity.post_load'] = array('postLoad', 255);
+    $events['menu_link.entity.pre_delete'] = array('preDelete', 255);
+    $events['menu_link.entity.pre_save'] = array('preSave', 255);
+    $events['menu_link.entity.update'] = array('write', 255);
+    return $events;
+  }
+
+}
diff --git a/core/modules/menu_link/menu_link.module b/core/modules/menu_link/menu_link.module
index 3a3bf30..6c76ef5 100644
--- a/core/modules/menu_link/menu_link.module
+++ b/core/modules/menu_link/menu_link.module
@@ -76,11 +76,11 @@ function menu_link_delete($mlid) {
  * @param bool $force
  *   (optional) Forces deletion. Internal use only, setting to TRUE is
  *   discouraged. Defaults to FALSE.
- * @param bool $prevent_reparenting
- *   (optional) Disables the re-parenting logic from the deletion process.
- *   Defaults to FALSE.
+ * @param bool $reparenting
+ *   (optional) Run the re-parenting logic in the deletion process.
+ *   Defaults to TRUE.
  */
-function menu_link_delete_multiple(array $mlids, $force = FALSE, $prevent_reparenting = FALSE) {
+function menu_link_delete_multiple(array $mlids, $force = FALSE, $reparenting = TRUE) {
   if (!$mlids) {
     // If no IDs or invalid IDs were passed, do nothing.
     return;
@@ -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->setReparenting($reparenting);
   $controller->delete($entities);
 }
 
diff --git a/core/modules/menu_link/menu_link.services.yml b/core/modules/menu_link/menu_link.services.yml
new file mode 100644
index 0000000..ec7c5a1
--- /dev/null
+++ b/core/modules/menu_link/menu_link.services.yml
@@ -0,0 +1,6 @@
+services:
+  menu_link.subscriber:
+    class: Drupal\menu_link\MenuLinkSubscriber
+    arguments: ['@plugin.manager.entity', '@router.route_provider']
+    tags:
+      - { name: event_subscriber }
diff --git a/core/modules/node/lib/Drupal/node/NodeStorageController.php b/core/modules/node/lib/Drupal/node/NodeStorageController.php
index c9e832e..70cde8b 100644
--- a/core/modules/node/lib/Drupal/node/NodeStorageController.php
+++ b/core/modules/node/lib/Drupal/node/NodeStorageController.php
@@ -32,51 +32,12 @@ public function create(array $values) {
   /**
    * Overrides Drupal\Core\Entity\DatabaseStorageControllerNG::attachLoad().
    */
-  protected function attachLoad(&$queried_entities, $load_revision = FALSE) {
-    $nodes = $this->mapFromStorageRecords($queried_entities, $load_revision);
-
-    // Create an array of nodes for each content type and pass this to the
-    // object type specific callback. To preserve backward-compatibility we
-    // pass on BC decorators to node-specific hooks, while we pass on the
-    // regular entity objects else.
-    $typed_nodes = array();
+  protected function postLoad($nodes, $load_revision = FALSE) {
+    $bc_entities = array();
     foreach ($nodes as $id => $node) {
-      $queried_entities[$id] = $node->getBCEntity();
-      $typed_nodes[$node->bundle()][$id] = $queried_entities[$id];
-    }
-
-    if ($load_revision) {
-      field_attach_load_revision($this->entityType, $queried_entities);
-    }
-    else {
-      field_attach_load($this->entityType, $queried_entities);
-    }
-
-    // Call object type specific callbacks on each typed array of nodes.
-    foreach ($typed_nodes as $node_type => $nodes_of_type) {
-      // Retrieve the node type 'base' hook implementation based on a Node in
-      // the type-specific stack.
-      if ($function = node_hook($node_type, 'load')) {
-        $function($nodes_of_type);
-      }
-    }
-    // Besides the list of nodes, pass one additional argument to
-    // hook_node_load(), containing a list of node types that were loaded.
-    $argument = array_keys($typed_nodes);
-    $this->hookLoadArguments = array($argument);
-
-    // Call hook_entity_load().
-    foreach (module_implements('entity_load') as $module) {
-      $function = $module . '_entity_load';
-      $function($queried_entities, $this->entityType);
-    }
-    // Call hook_TYPE_load(). The first argument for hook_TYPE_load() are
-    // always the queried entities, followed by additional arguments set in
-    // $this->hookLoadArguments.
-    $args = array_merge(array($queried_entities), $this->hookLoadArguments);
-    foreach (module_implements($this->entityType . '_load') as $module) {
-      call_user_func_array($module . '_' . $this->entityType . '_load', $args);
+      $bc_entities[$id] = $node->getBCEntity();
     }
+    return $bc_entities;
   }
 
   /**
@@ -95,107 +56,11 @@ protected function buildQuery($ids, $revision_id = FALSE) {
   }
 
   /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::invokeHook().
+   * @todo remove http://drupal.org/node/1819726
+   *
+   * @param $nodes
    */
-  protected function invokeHook($hook, EntityInterface $node) {
-    $node = $node->getBCEntity();
-
-    if ($hook == 'insert' || $hook == 'update') {
-      node_invoke($node, $hook);
-    }
-    else if ($hook == 'predelete') {
-      // 'delete' is triggered in 'predelete' is here to preserve hook ordering
-      // from Drupal 7.
-      node_invoke($node, 'delete');
-    }
-
-    // Inline parent::invokeHook() to pass on BC-entities to node-specific
-    // hooks.
-
-    $function = 'field_attach_' . $hook;
-    // @todo: field_attach_delete_revision() is named the wrong way round,
-    // consider renaming it.
-    if ($function == 'field_attach_revision_delete') {
-      $function = 'field_attach_delete_revision';
-    }
-    if (!empty($this->entityInfo['fieldable']) && function_exists($function)) {
-      $function($node);
-    }
-
-    // Invoke the hook.
-    module_invoke_all($this->entityType . '_' . $hook, $node);
-    // Invoke the respective entity-level hook.
-    module_invoke_all('entity_' . $hook, $node, $this->entityType);
-  }
-
-  /**
-   * 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) {
-    if ($entity->isNewRevision()) {
-      // When inserting either a new node or a new node revision, $node->log
-      // must be set because {node_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 a node 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 {node_revision}.log column nullable so that we can
-      // remove this check.
-      if (!isset($record->log)) {
-        $record->log = '';
-      }
-    }
-    elseif (!isset($record->log) || $record->log === '') {
-      // If we are updating an existing node without adding a new revision, we
-      // need to make sure $node->log is unset whenever it is empty. As long as
-      // $node->log is unset, drupal_write_record() will not attempt to update
-      // the existing database column when re-saving the revision; therefore,
-      // this code allows us to avoid clobbering an existing log entry with an
-      // empty one.
-      unset($record->log);
-    }
-
-    if ($entity->isNewRevision()) {
-      $record->timestamp = REQUEST_TIME;
-      $record->uid = isset($entity->revision_uid->value) ? $entity->revision_uid->value : $GLOBALS['user']->uid;
-    }
-  }
-
-  /**
-   * 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) {
-    if (module_exists('search')) {
-      foreach ($entities as $id => $entity) {
-        search_reindex($entity->nid->value, 'node');
-      }
-    }
-  }
-
-  /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::postDelete().
-   */
-  protected function postDelete($nodes) {
+  public function deleteNodeAccess($nodes) {
     // Delete values from other tables also referencing this node.
     $ids = array_keys($nodes);
 
diff --git a/core/modules/node/lib/Drupal/node/StorageSubscriber.php b/core/modules/node/lib/Drupal/node/StorageSubscriber.php
new file mode 100644
index 0000000..fd910b3
--- /dev/null
+++ b/core/modules/node/lib/Drupal/node/StorageSubscriber.php
@@ -0,0 +1,127 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\node\StorageSubscriber.
+ */
+
+namespace Drupal\node;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\Event;
+use Drupal\Core\Entity\PreSaveRevisionEvent;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class StorageSubscriber implements EventSubscriberInterface {
+
+  /**
+   * @todo use node access service instead http://drupal.org/node/1921426
+   *
+   * @var \Drupal\Core\Entity\EntityManager
+   */
+  protected $entityManager;
+
+  /**
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  public function __construct(EntityManager $entity_manager, ModuleHandlerInterface $module_handler) {
+    // @todo use node access service instead http://drupal.org/node/1921426
+    $this->entityManager = $entity_manager;
+    $this->moduleHandler = $module_handler;
+  }
+
+  public function preSave(Event $event) {
+    // Before saving the node, set changed and revision times.
+    $event->getEntity()->changed->value = REQUEST_TIME;
+  }
+
+  public function preSaveRevision(PreSaveRevisionEvent $event) {
+    $entity = $event->getEntity();
+    $record = $event->getRecord();
+    if ($entity->isNewRevision()) {
+      // When inserting either a new node or a new node revision, $node->log
+      // must be set because {node_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 a node 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 {node_revision}.log column nullable so that we can
+      // remove this check.
+      if (!isset($record->log)) {
+        $record->log = '';
+      }
+    }
+    elseif (!isset($record->log) || $record->log === '') {
+      // If we are updating an existing node without adding a new revision, we
+      // need to make sure $node->log is unset whenever it is empty. As long as
+      // $node->log is unset, drupal_write_record() will not attempt to update
+      // the existing database column when re-saving the revision; therefore,
+      // this code allows us to avoid clobbering an existing log entry with an
+      // empty one.
+      unset($record->log);
+    }
+
+    if ($entity->isNewRevision()) {
+      $record->timestamp = REQUEST_TIME;
+      $record->uid = isset($entity->revision_uid->value) ? $entity->revision_uid->value : $GLOBALS['user']->uid;
+    }
+  }
+
+  public function postDelete($nodes) {
+    // @todo use the node access service instead after http://drupal.org/node/1921426
+    $this->entityManager->getStorageController('node')->deleteNodeAccess($nodes);
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave().
+   */
+  protected function write(Event $event, $delete) {
+    $node = $event->getBCEntity();
+    // 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, $delete);
+    }
+  }
+
+  public function insert(Event $event) {
+    $this->write($event, FALSE);
+  }
+
+  public function update(Event $event) {
+    $this->write($event, TRUE);
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\DatabaseStorageController::preDelete().
+   */
+  public function preDelete($entities) {
+    if ($this->moduleHandler->moduleExists('search')) {
+      foreach ($entities as $entity) {
+        $this->moduleHandler->invoke('search', 'reindex', array($entity->nid->value, 'node'));
+      }
+    }
+  }
+  /**
+   * Overrides Drupal\Core\Entity\DatabaseStorageController::postDelete().
+   */
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events['node.entity.post_delete'] = 'postDelete';
+    $events['node.entity.insert'] = 'insert';
+    $events['node.entity.update'] = 'update';
+    $events['node.entity.pre_delete'] = 'preDelete';
+    $events['node.entity.pre_save'] = 'preSave';
+    $events['node.entity.pre_save_revision'] = 'preSaveRevision';
+    return $events;
+  }
+
+}
diff --git a/core/modules/node/node.services.yml b/core/modules/node/node.services.yml
new file mode 100644
index 0000000..3c1d0e4
--- /dev/null
+++ b/core/modules/node/node.services.yml
@@ -0,0 +1,7 @@
+services:
+  node.storage_subscriber:
+    class: Drupal\node\StorageSubscriber
+    arguments: ['@plugin.manager.entity', '@module_handler']
+    tags:
+      - { name: event_subscriber }
+
diff --git a/core/modules/node/tests/modules/node_access_test/lib/Drupal/node_access_test/NodeSubscriber.php b/core/modules/node/tests/modules/node_access_test/lib/Drupal/node_access_test/NodeSubscriber.php
new file mode 100644
index 0000000..c455ccf
--- /dev/null
+++ b/core/modules/node/tests/modules/node_access_test/lib/Drupal/node_access_test/NodeSubscriber.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupalnode_access_test\EntitySubscriber
+ */
+
+namespace Drupal\node_access_test;
+
+
+use Drupal\Core\Entity\Event;
+use Drupal\Core\Entity\EventMultiple;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class NodeSubscriber implements EventSubscriberInterface {
+
+  /**
+   * Implements hook_node_load().
+   */
+  function postLoad(EventMultiple $event) {
+    $nodes = $event->getEntities();
+    $result = db_query('SELECT nid, private FROM {node_access_test} WHERE nid IN(:nids)', array(':nids' => array_keys($nodes)));
+    foreach ($result as $record) {
+      $nodes[$record->nid]->private = $record->private;
+    }
+  }
+
+  /**
+   * Implements hook_node_predelete().
+   */
+
+  function preDelete(Event $event) {
+    db_delete('node_access_test')->condition('nid', $event->getEntity()->id())->execute();
+  }
+
+  /**
+   * Helper for node insert/update.
+   */
+  function write(Event $event) {
+    $node = $event->getEntity();
+    if (isset($node->private)) {
+      db_merge('node_access_test')
+        ->key(array('nid' => $node->id()))
+        ->fields(array('private' => (int) $node->private))
+        ->execute();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events['node.entity.insert'] = 'write';
+    $events['node.entity.post_load'] = 'postLoad';
+    $events['node.entity.pre_delete'] = 'preDelete';
+    $events['node.entity.update'] = 'write';
+    return $events;
+  }
+}
diff --git a/core/modules/node/tests/modules/node_access_test/node_access_test.module b/core/modules/node/tests/modules/node_access_test/node_access_test.module
index 25eddba..8a9cff0 100644
--- a/core/modules/node/tests/modules/node_access_test/node_access_test.module
+++ b/core/modules/node/tests/modules/node_access_test/node_access_test.module
@@ -194,50 +194,6 @@ function node_access_test_form_node_form_alter(&$form, $form_state) {
 }
 
 /**
- * Implements hook_node_load().
- */
-function node_access_test_node_load($nodes, $types) {
-  $result = db_query('SELECT nid, private FROM {node_access_test} WHERE nid IN(:nids)', array(':nids' => array_keys($nodes)));
-  foreach ($result as $record) {
-    $nodes[$record->nid]->private = $record->private;
-  }
-}
-
-/**
- * Implements hook_node_predelete().
- */
-
-function node_access_test_node_predelete(EntityInterface $node) {
-  db_delete('node_access_test')->condition('nid', $node->nid)->execute();
-}
-
-/**
- * Implements hook_node_insert().
- */
-function node_access_test_node_insert(EntityInterface $node) {
-  _node_access_test_node_write($node);
-}
-
-/**
- * Implements hook_nodeapi_update().
- */
-function node_access_test_node_update(EntityInterface $node) {
-  _node_access_test_node_write($node);
-}
-
-/**
- * Helper for node insert/update.
- */
-function _node_access_test_node_write(EntityInterface $node) {
-  if (isset($node->private)) {
-    db_merge('node_access_test')
-      ->key(array('nid' => $node->nid))
-      ->fields(array('private' => (int) $node->private))
-      ->execute();
-  }
-}
-
-/**
  * Implements hook_node_access().
  */
 function node_access_test_node_access($node, $op, $account, $langcode) {
diff --git a/core/modules/node/tests/modules/node_access_test/node_access_test.services.yml b/core/modules/node/tests/modules/node_access_test/node_access_test.services.yml
new file mode 100644
index 0000000..c909db9
--- /dev/null
+++ b/core/modules/node/tests/modules/node_access_test/node_access_test.services.yml
@@ -0,0 +1,5 @@
+services:
+  node_access_test.node_subscriber:
+    class: Drupal\node_access_test\NodeSubscriber
+    tags:
+      - { name: event_subscriber }
diff --git a/core/modules/rdf/lib/Drupal/rdf/EventSubscriber/EntitySubscriber.php b/core/modules/rdf/lib/Drupal/rdf/EventSubscriber/EntitySubscriber.php
new file mode 100644
index 0000000..b7f1d0d
--- /dev/null
+++ b/core/modules/rdf/lib/Drupal/rdf/EventSubscriber/EntitySubscriber.php
@@ -0,0 +1,45 @@
+<?php
+/**
+ * @file
+ * Contains Drupal\rdf\EventSubscriber\EntitySubscriber.
+ */
+
+namespace Drupal\rdf\EventSubscriber;
+
+use Drupal\Core\Entity\EventMultiple;
+use Drupal\Core\Entity\Events;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class EntitySubscriber implements EventSubscriberInterface {
+
+  function entityLoad(EventMultiple $event) {
+    $type = $event->getEntityType();
+    foreach ($event->getEntities() as $entity) {
+      // Extracts the bundle of the entity being loaded.
+      $entity->rdf_mapping = rdf_mapping_load($type, $entity->bundle());
+    }
+  }
+
+  public function commentLoad(EventMultiple $event) {
+    foreach ($event->getEntities() as $comment) {
+      // Pages with many comments can show poor performance. This information
+      // isn't needed until rdf_preprocess_comment() is called, but set it here
+      // to optimize performance for websites that implement an entity cache.
+      $comment->rdf_data['date'] = rdf_rdfa_attributes($comment->rdf_mapping['created'], $comment->created->value);
+      $comment->rdf_data['nid_uri'] = url('node/' . $comment->nid->target_id);
+      if ($comment->pid->target_id) {
+        $comment->rdf_data['pid_uri'] = url('comment/' . $comment->pid->target_id, array('fragment' => 'comment-' . $comment->pid->target_id));
+      }
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events[Events::POST_LOAD] = 'entityLoad';
+    $events['commment.entity.load'] = 'commentLoad';
+    return $events;
+  }
+
+}
diff --git a/core/modules/rdf/rdf.module b/core/modules/rdf/rdf.module
index 547075a..5f97f50 100644
--- a/core/modules/rdf/rdf.module
+++ b/core/modules/rdf/rdf.module
@@ -399,32 +399,6 @@ function rdf_entity_bundle_info_alter(&$bundles) {
 }
 
 /**
- * Implements hook_entity_load().
- */
-function rdf_entity_load($entities, $type) {
-  foreach ($entities as $entity) {
-    // Extracts the bundle of the entity being loaded.
-    $entity->rdf_mapping = rdf_mapping_load($type, $entity->bundle());
-  }
-}
-
-/**
- * Implements hook_comment_load().
- */
-function rdf_comment_load($comments) {
-  foreach ($comments as $comment) {
-    // Pages with many comments can show poor performance. This information
-    // isn't needed until rdf_preprocess_comment() is called, but set it here
-    // to optimize performance for websites that implement an entity cache.
-    $comment->rdf_data['date'] = rdf_rdfa_attributes($comment->rdf_mapping['created'], $comment->created->value);
-    $comment->rdf_data['nid_uri'] = url('node/' . $comment->nid->target_id);
-    if ($comment->pid->target_id) {
-      $comment->rdf_data['pid_uri'] = url('comment/' . $comment->pid->target_id, array('fragment' => 'comment-' . $comment->pid->target_id));
-    }
-  }
-}
-
-/**
  * Implements hook_theme().
  */
 function rdf_theme() {
diff --git a/core/modules/rdf/rdf.services.yml b/core/modules/rdf/rdf.services.yml
index 7b3a515..2f56dc9 100644
--- a/core/modules/rdf/rdf.services.yml
+++ b/core/modules/rdf/rdf.services.yml
@@ -14,3 +14,7 @@ services:
     tags:
       - { name: event_subscriber }
     arguments: ['@rdf.site_schema_manager']
+  rdf.route_subscriber:
+    class: Drupal\rdf\EventSubscriber\EntitySubscriber
+    tags:
+      - { name: event_subscriber }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/LinksTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/LinksTest.php
index ddc834b..d2cddb7 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/LinksTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/LinksTest.php
@@ -27,7 +27,7 @@ public static function getInfo() {
   function createLinkHierarchy($module = 'menu_test') {
     // First remove all the menu links.
     $menu_links = menu_link_load_multiple();
-    menu_link_delete_multiple(array_keys($menu_links), TRUE, TRUE);
+    menu_link_delete_multiple(array_keys($menu_links), TRUE, FALSE);
 
     // Then create a simple link hierarchy:
     // - $parent
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/EntitySubscriber.php b/core/modules/taxonomy/lib/Drupal/taxonomy/EntitySubscriber.php
new file mode 100644
index 0000000..044df4e
--- /dev/null
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/EntitySubscriber.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\taxonomy\EntitySubscriber
+ */
+
+namespace Drupal\taxonomy;
+
+use Drupal\Core\Entity\Event;
+use Drupal\Core\Entity\EventMultiple;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class EntitySubscriber implements EventSubscriberInterface {
+
+  public function insert(Event $event) {
+    if ($event->getBackendType() == 'sql') {
+      // Add taxonomy index entries for the node.
+      taxonomy_build_node_index($event->getBCEntity());
+    }
+  }
+
+  public function update(Event $event) {
+    if ($event->getBackendType() == 'sql') {
+      $node = $event->getBCEntity();
+      // Always rebuild the node's taxonomy index entries on node save.
+      taxonomy_delete_node_index($node);
+      taxonomy_build_node_index($node);
+    }
+  }
+
+  public function preDelete(Event $event) {
+    // Clean up the {taxonomy_index} table when nodes are deleted.
+    taxonomy_delete_node_index($event->getBCEntity());
+  }
+
+  public function postDelete(EventMultiple $event) {
+    if (config('taxonomy.settings')->get('maintain_index_table')) {
+      // Clean up the {taxonomy_index} table when terms are deleted.
+      db_delete('taxonomy_index')->condition('tid', array_keys($event->getEntities()))->execute();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events['node.entity.insert'] = 'insert';
+    $events['node.entity.update'] = 'update';
+    $events['node.entity.pre_delete'] = 'preDelete';
+    $events['taxonomy_term.entity.post_delete'] = 'postDelete';
+    return $events;
+  }
+
+}
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/StorageSubscriber.php b/core/modules/taxonomy/lib/Drupal/taxonomy/StorageSubscriber.php
new file mode 100644
index 0000000..72e314c
--- /dev/null
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/StorageSubscriber.php
@@ -0,0 +1,109 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\taxonomy\StorageSubscriber.
+ */
+
+namespace Drupal\taxonomy;
+
+use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\Event;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class StorageSubscriber implements EventSubscriberInterface {
+
+  function __construct(EntityManager $entity_manager) {
+    $this->entityManager = $entity_manager;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\DatabaseStorageController::postDelete().
+   */
+  public function postDelete($entities) {
+    // See if any of the term's children are about to be become orphans.
+    $orphans = array();
+    $tids = array_keys($entities);
+    foreach ($tids 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->tid);
+          // Because the parent has already been deleted, the parent count might
+          // be 0.
+          if (count($parents) <= 1) {
+            $orphans[] = $child->tid;
+          }
+        }
+      }
+    }
+
+    // Delete term hierarchy information after looking up orphans but before
+    // deleting them so that their children/parent information is consistent.
+    $controller = $this->entityManager->getStorageController('taxonomy_term');
+    $controller->deleteHierarchy($tids);
+
+    if (!empty($orphans)) {
+      $entities = $controller->load($orphans);
+      $controller->delete($entities);
+    }
+  }
+
+  public function insert(Event $event) {
+    $entity = $event->getEntity();
+    if (isset($entity->parent)) {
+      $this->entityManager->getStorageController('taxonomy_term')->insertHierarchy($entity->id(), $entity->parent);
+    }
+  }
+
+  public function update(Event $event) {
+    $entity = $event->getEntity();
+    if (isset($entity->parent)) {
+      $this->entityManager->getStorageController('taxonomy_term')->updateHierarchy($entity->id(), $entity->parent);
+    }
+  }
+
+  /**
+   * Implements hook_field_presave().
+   *
+   * Create any new terms defined in a freetagging vocabulary.
+   */
+  function taxonomy_field_presave(EntityInterface $entity, $field, $instance, $langcode, &$items) {
+    foreach ($items as $delta => $item) {
+      if (!$item['tid'] && isset($item['entity'])) {
+        unset($item['tid']);
+        taxonomy_term_save($item['entity']);
+        $items[$delta]['tid'] = $item['entity']->tid;
+      }
+    }
+  }
+
+
+
+  /**
+   * Returns an array of event names this subscriber wants to listen to.
+   *
+   * The array keys are event names and the value can be:
+   *
+   *  * The method name to call (priority defaults to 0)
+   *  * An array composed of the method name to call and the priority
+   *  * An array of arrays composed of the method names to call and respective
+   *    priorities, or 0 if unset
+   *
+   * For instance:
+   *
+   *  * array('eventName' => 'methodName')
+   *  * array('eventName' => array('methodName', $priority))
+   *  * array('eventName' => array(array('methodName1', $priority), array('methodName2'))
+   *
+   * @return array The event names to listen to
+   *
+   * @api
+   */
+  public static function getSubscribedEvents() {
+    $events['taxonomy_term.entity.insert'] = 'insert';
+    $events['taxonomy_term.entity.post_delete'] = 'postDelete';
+    $events['taxonomy_term.entity.update'] = 'update';
+    return $events;
+  }
+}
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php
index c7dbd97..5821bf9 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php
@@ -7,14 +7,13 @@
 
 namespace Drupal\taxonomy;
 
-use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\Query\QueryInterface;
 use Drupal\Core\Entity\DatabaseStorageController;
 
 /**
  * Defines a Controller class for taxonomy terms.
  */
-class TermStorageController extends DatabaseStorageController {
+class TermStorageController extends DatabaseStorageController implements TermStorageInterface {
 
   /**
    * Overrides Drupal\Core\Entity\DatabaseStorageController::create().
@@ -44,59 +43,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->tid);
-          // Because the parent has already been deleted, the parent count might
-          // be 0.
-          if (count($parents) <= 1) {
-            $orphans[] = $child->tid;
-          }
-        }
-      }
-    }
-
-    // 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)) {
-      taxonomy_term_delete_multiple($orphans);
-    }
-  }
-
-  /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave().
-   */
-  protected function postSave(EntityInterface $entity, $update) {
-    if (isset($entity->parent)) {
-      db_delete('taxonomy_term_hierarchy')
-        ->condition('tid', $entity->tid)
-        ->execute();
-
-      $query = db_insert('taxonomy_term_hierarchy')
-        ->fields(array('tid', 'parent'));
-
-      foreach ($entity->parent as $parent) {
-        $query->values(array(
-          'tid' => $entity->tid,
-          'parent' => $parent
-        ));
-      }
-      $query->execute();
-    }
-  }
-
-  /**
    * Overrides Drupal\Core\Entity\DatabaseStorageController::resetCache().
    */
   public function resetCache(array $ids = NULL) {
@@ -109,4 +55,28 @@ public function resetCache(array $ids = NULL) {
     drupal_static_reset('taxonomy_term_load_children');
     parent::resetCache($ids);
   }
+
+  function deleteHierarchy($tids) {
+    db_delete('taxonomy_term_hierarchy')
+      ->condition('tid', $tids)
+      ->execute();
+  }
+
+  function insertHierarchy($tid, array $parents) {
+    $query = db_insert('taxonomy_term_hierarchy')
+      ->fields(array('tid', 'parent'));
+
+    foreach ($parents as $parent) {
+      $query->values(array(
+        'tid' => $tid,
+        'parent' => $parent
+      ));
+    }
+    $query->execute();
+  }
+
+  function updateHierarchy($tid, array $parents) {
+    $this->deleteHierarchy(array($tid));
+    $this->insertHierarchy($tid, $parents);
+  }
 }
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageInterface.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageInterface.php
new file mode 100644
index 0000000..4bc1b87
--- /dev/null
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageInterface.php
@@ -0,0 +1,20 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\taxonomy\TermStorageInterface.
+ */
+
+
+namespace Drupal\taxonomy;
+
+
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+
+interface TermStorageInterface extends EntityStorageControllerInterface {
+
+  function insertHierarchy($tid, array $parents);
+  function updateHierarchy($tid, array $parents);
+  function deleteHierarchy($tids);
+
+}
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index 89f334b..b54762a 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -1244,14 +1244,6 @@ function taxonomy_field_presave(EntityInterface $entity, $field, $instance, $lan
 }
 
 /**
- * Implements hook_node_insert().
- */
-function taxonomy_node_insert(EntityInterface $node) {
-  // Add taxonomy index entries for the node.
-  taxonomy_build_node_index($node);
-}
-
-/**
  * Builds and inserts taxonomy index entries for a given node.
  *
  * The index lists all terms that are related to a given node entity, and is
@@ -1283,7 +1275,7 @@ function taxonomy_build_node_index($node) {
     foreach (field_info_instances('node', $node->type) as $instance) {
       $field_name = $instance['field_name'];
       $field = field_info_field($field_name);
-      if ($field['module'] == 'taxonomy' && $field['storage']['type'] == 'field_sql_storage') {
+      if ($field['module'] == 'taxonomy') {
         // If a field value is not set in the node object when node_save() is
         // called, the old value from $node->original is used.
         if (isset($node->{$field_name})) {
@@ -1321,23 +1313,6 @@ function taxonomy_build_node_index($node) {
 }
 
 /**
- * Implements hook_node_update().
- */
-function taxonomy_node_update(EntityInterface $node) {
-  // Always rebuild the node's taxonomy index entries on node save.
-  taxonomy_delete_node_index($node);
-  taxonomy_build_node_index($node);
-}
-
-/**
- * Implements hook_node_predelete().
- */
-function taxonomy_node_predelete(EntityInterface $node) {
-  // Clean up the {taxonomy_index} table when nodes are deleted.
-  taxonomy_delete_node_index($node);
-}
-
-/**
  * Deletes taxonomy index entries for a given node.
  *
  * @param \Drupal\Core\Entity\EntityInterface $node
@@ -1350,16 +1325,6 @@ function taxonomy_delete_node_index(EntityInterface $node) {
 }
 
 /**
- * Implements hook_taxonomy_term_delete().
- */
-function taxonomy_taxonomy_term_delete(Term $term) {
-  if (config('taxonomy.settings')->get('maintain_index_table')) {
-    // Clean up the {taxonomy_index} table when terms are deleted.
-    db_delete('taxonomy_index')->condition('tid', $term->tid)->execute();
-  }
-}
-
-/**
  * @} End of "defgroup taxonomy_index".
  */
 
diff --git a/core/modules/taxonomy/taxonomy.services.yml b/core/modules/taxonomy/taxonomy.services.yml
new file mode 100644
index 0000000..8734a4f
--- /dev/null
+++ b/core/modules/taxonomy/taxonomy.services.yml
@@ -0,0 +1,10 @@
+services:
+  taxonomy.storage_subscriber:
+    class: Drupal\taxonomy\StorageSubscriber
+    arguments: ['@plugin.manager.entity']
+    tags:
+      - { name: event_subscriber }
+  taxonomy.entity_subscriber:
+    class: Drupal\taxonomy\EntitySubscriber
+    tags:
+      - { name: event_subscriber }
diff --git a/core/modules/taxonomy/taxonomy.views.inc b/core/modules/taxonomy/taxonomy.views.inc
index 0644f82..6d8ae7b 100644
--- a/core/modules/taxonomy/taxonomy.views.inc
+++ b/core/modules/taxonomy/taxonomy.views.inc
@@ -6,6 +6,7 @@
  *
  * @ingroup views_module_handlers
  */
+use Drupal\field_sql_storage\Entity\Storage;
 
 /**
  * Implements hook_views_data().
@@ -379,7 +380,7 @@ function taxonomy_field_views_data_views_data_alter(&$data, $field) {
       'help' => t('Relate each @entity with a @field set to the term.', array('@entity' => $entity, '@field' => $label)),
       'id' => 'entity_reverse',
       'field_name' => $field['field_name'],
-      'field table' => _field_sql_storage_tablename($field),
+      'field table' => Storage::tableName($field),
       'field field' => $field['field_name'] . '_tid',
       'base' => $entity_info['base_table'],
       'base field' => $entity_info['entity_keys']['id'],
diff --git a/core/modules/user/lib/Drupal/user/Entity/UserEntityNodeLoad.php b/core/modules/user/lib/Drupal/user/Entity/UserEntityNodeLoad.php
new file mode 100644
index 0000000..8b19edd
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Entity/UserEntityNodeLoad.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * Cotains \Drupal\user\Entity\UserSqlNodeLoad
+ */
+
+namespace Drupal\user\Entity;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Entity\Events;
+use Drupal\Core\Entity\EventMultiple;
+use Drupal\Core\Entity\Query\QueryFactory;
+use Drupal\Core\Entity\false;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * Class UserEntityNodeLoad
+ * @package Drupal\user\Entity
+ */
+class UserEntityNodeLoad implements EventSubscriberInterface {
+
+  function __construct(EntityManager $entity_manager, QueryFactory $query_factory) {
+    $this->entityManager = $entity_manager;
+    $this->queryFactory = $query_factory;
+  }
+
+  /**
+   * @param EventMultiple $event
+   */
+  function postLoad(EventMultiple $event) {
+    $uids = array();
+    $nodes = $event->getEntities();
+    foreach ($nodes as $nid => $node) {
+      if (!isset($node->name)) {
+        $uids[$nid] = $node->uid->value;
+      }
+    }
+    if ($uids) {
+      $uids = $this->queryFactory->get('user')
+        ->condition('uid', $uids)
+        ->execute();
+      $users = $this->entityManager->getStorageController('user')->load($uids);
+      // Add these values back into the node objects.
+      foreach ($uids as $nid => $uid) {
+        $nodes[$nid]->name = $users[$uid]->name;
+      }
+    }
+  }
+
+  /**
+   */
+  public static function getSubscribedEvents() {
+    $events['node.entity.post_load'] = array('postLoad', 70);
+    return $events;
+  }
+
+}
diff --git a/core/modules/user/lib/Drupal/user/Entity/UserSqlNodeLoad.php b/core/modules/user/lib/Drupal/user/Entity/UserSqlNodeLoad.php
new file mode 100644
index 0000000..707fe7d
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/Entity/UserSqlNodeLoad.php
@@ -0,0 +1,49 @@
+<?php
+
+/**
+ * Cotains \Drupal\user\Entity\UserSqlNodeLoad
+ */
+
+namespace Drupal\user\Entity;
+
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Entity\Events;
+use Drupal\Core\Entity\EventMultiple;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+/**
+ * @todo Deprecated by $node->author, attached in
+ *   \Drupal\node\NodeRenderController::buildContent(). Update code that
+ *   depends on these properties.
+ */
+class UserSqlNodeLoad implements EventSubscriberInterface {
+
+  function __construct(Connection $connection) {
+    $this->connection = $connection;
+  }
+
+  function postLoad(EventMultiple $event) {
+    $uids = array();
+    $nodes = $event->getEntities();
+    foreach ($nodes as $nid => $node) {
+      if (!isset($node->name)) {
+        $uids[$nid] = $node->uid->value;
+      }
+    }
+    if ($uids) {
+      $user_names = $this->connection->query("SELECT uid, name FROM {users} WHERE uid IN (:uids)", array(':uids' => $uids))->fetchAllKeyed();
+      // Add these values back into the node objects.
+      foreach ($uids as $nid => $uid) {
+        $nodes[$nid]->name = $user_names[$uid];
+      }
+    }
+  }
+
+  /**
+   */
+  public static function getSubscribedEvents() {
+    $events['node.entity.post_load'] = array('postLoad', 80);
+    return $events;
+  }
+}
diff --git a/core/modules/user/lib/Drupal/user/StorageSubscriber.php b/core/modules/user/lib/Drupal/user/StorageSubscriber.php
new file mode 100644
index 0000000..3262f93
--- /dev/null
+++ b/core/modules/user/lib/Drupal/user/StorageSubscriber.php
@@ -0,0 +1,153 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\user\StorageSubscriber.
+ */
+
+namespace Drupal\user;
+
+use Drupal\Core\Entity\EntityMalformedException;
+use Drupal\Core\Entity\EntityManager;
+use Drupal\Core\Entity\Event;
+use Drupal\Core\Entity\EventMultiple;
+use Drupal\Core\Password\PasswordInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+
+class StorageSubscriber implements EventSubscriberInterface {
+
+  /**
+   * @var \Drupal\Core\Entity\EntityManager
+   */
+  protected $entityManger;
+
+  function __construct(EntityManager $entity_manager, UserDataInterface $user_data, PasswordInterface $password) {
+    $this->entityManager = $entity_manager;
+    $this->userData = $user_data;
+    $this->password = $password;
+  }
+
+  /**
+   * @return \Drupal\user\UserStorageController
+   */
+  protected function getStorageController() {
+    return $this->entityManager->getStorageController('user');
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\DatabaseStorageController::postLoad().
+   */
+  public function postLoad(EventMultiple $event) {
+    $queried_users = $event->getEntities();
+    foreach ($queried_users as $key => $record) {
+      $queried_users[$key]->roles = array();
+      if ($record->uid) {
+        $queried_users[$record->uid]->roles[DRUPAL_AUTHENTICATED_RID] = DRUPAL_AUTHENTICATED_RID;
+      }
+      else {
+        $queried_users[$record->uid]->roles[DRUPAL_ANONYMOUS_RID] = DRUPAL_ANONYMOUS_RID;
+      }
+    }
+    $this->getStorageController()->addRoles($queried_users);
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\DatabaseStorageController::preSave().
+   */
+  public function preSave(Event $event) {
+    $user = $event->getEntity();
+    // Update the user password if it has changed.
+    if ($user->isNew() || (!empty($user->pass) && $user->pass != $user->original->pass)) {
+      // Allow alternate password hashing schemes.
+      $user->pass = $this->password->hash(trim($user->pass));
+      // Abort if the hashing failed and returned FALSE.
+      if (!$user->pass) {
+        throw new EntityMalformedException('The entity does not have a password.');
+      }
+    }
+
+    if (!$user->isNew()) {
+      // If the password is empty, that means it was not changed, so use the
+      // original password.
+      if (empty($user->pass)) {
+        $user->pass = $user->original->pass;
+      }
+    }
+
+    // Prepare user roles.
+    if (isset($user->roles)) {
+      $user->roles = array_filter($user->roles);
+    }
+
+    // Store account cancellation information.
+    foreach (array('user_cancel_method', 'user_cancel_notify') as $key) {
+      if (isset($user->{$key})) {
+        $this->userData->set('user', $user->id(), substr($key, 5), $user->{$key});
+      }
+    }
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\DatabaseStorageController::postSave().
+   */
+  public function update(Event $event) {
+    $user = $event->getEntity();
+    $uid = $user->uid;
+    // If the password has been changed, delete all open sessions for the
+    // user and recreate the current one.
+    if ($user->pass != $user->original->pass) {
+      drupal_session_destroy_uid($uid);
+      if ($uid == $GLOBALS['user']->uid) {
+        drupal_session_regenerate();
+      }
+    }
+
+    // Remove roles that are no longer enabled for the user.
+    $user->roles = array_filter($user->roles);
+
+    // Reload user roles if provided.
+    if ($user->roles != $user->original->roles) {
+      $this->getStorageController()->deleteRoles(array($uid => $user));
+      $this->getStorageController()->saveRoles($user);
+    }
+
+    // If the user was blocked, delete the user's sessions to force a logout.
+    if ($user->original->status != $user->status && $user->status == 0) {
+      drupal_session_destroy_uid($uid);
+    }
+
+    // Send emails after we have the new user object.
+    if ($user->status != $user->original->status) {
+      // The user's status is changing; conditionally send notification email.
+      $op = $user->status == 1 ? 'status_activated' : 'status_blocked';
+      _user_mail_notify($op, $user);
+    }
+  }
+
+  public function insert(Event $event) {
+    $user = $event->getEntity();
+    // Save user roles.
+    if (count($user->roles) > 1) {
+      $this->getStorageController()->saveRoles($user);
+    }
+  }
+
+  public function postDelete(EventMultiple $event) {
+    $users = $event->getEntities();
+    $this->userData->delete(NULL, array_keys($users));
+    $this->getStorageController()->deleteRoles($users);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getSubscribedEvents() {
+    $events['user.entity.insert'] = 'insert';
+    $events['user.entity.post_delete'] = 'postDelete';
+    $events['user.entity.post_load'] = 'postLoad';
+    $events['user.entity.pre_save'] = 'preSave';
+    $events['user.entity.update'] = 'update';
+    return $events;
+  }
+
+}
diff --git a/core/modules/user/lib/Drupal/user/UserStorageController.php b/core/modules/user/lib/Drupal/user/UserStorageController.php
index 3d8e981..a9b1491 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;
 
 /**
@@ -17,34 +16,9 @@
  * 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 {
 
-  /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::attachLoad().
-   */
-  function attachLoad(&$queried_users, $load_revision = FALSE) {
-    foreach ($queried_users as $key => $record) {
-      $queried_users[$key]->roles = array();
-      if ($record->uid) {
-        $queried_users[$record->uid]->roles[DRUPAL_AUTHENTICATED_RID] = DRUPAL_AUTHENTICATED_RID;
-      }
-      else {
-        $queried_users[$record->uid]->roles[DRUPAL_ANONYMOUS_RID] = DRUPAL_ANONYMOUS_RID;
-      }
-    }
-
-    // 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;
-    }
-
-    // Call the default attachLoad() method. This will add fields and call
-    // hook_user_load().
-    parent::attachLoad($queried_users, $load_revision);
-  }
-
-  /**
+ /**
    * Overrides Drupal\Core\Entity\DatabaseStorageController::create().
    */
   public function create(array $values) {
@@ -69,112 +43,43 @@ 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 = drupal_container()->get('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.');
-      }
-    }
-
-    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})) {
-        drupal_container()->get('user.data')->set('user', $entity->id(), substr($key, 5), $entity->{$key});
-      }
+  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) {
-        db_delete('users_roles')
-          ->condition('uid', $entity->uid)
-          ->execute();
-
-        $query = db_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 = db_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();
+  public function saveRoles(EntityInterface $user) {
+    $query = db_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->uid,
+          'rid' => $rid,
+        ));
       }
     }
+    $query->execute();
   }
 
   /**
-   * Overrides Drupal\Core\Entity\DatabaseStorageController::postDelete().
+   * {@inheritdoc}
    */
-  protected function postDelete($entities) {
+  public function deleteRoles(array $users) {
+    $test = $users;
     db_delete('users_roles')
-      ->condition('uid', array_keys($entities), 'IN')
+      ->condition('uid', array_keys($users), 'IN')
       ->execute();
-    drupal_container()->get('user.data')->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..7320028
--- /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 all roles of a user.
+   *
+   * @param array $users
+   */
+  public function deleteRoles(array $users);
+
+}
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index 710e56d..a0af416 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -2466,29 +2466,6 @@ function user_form_process_password_confirm($element) {
 }
 
 /**
- * Implements hook_node_load().
- *
- * @todo Deprecated by $node->author, attached in
- *   \Drupal\node\NodeRenderController::buildContent(). Update code that
- *   depends on these properties.
- */
-function user_node_load($nodes, $types) {
-  // Build an array of all uids for node authors, keyed by nid.
-  $uids = array();
-  foreach ($nodes as $nid => $node) {
-    $uids[$nid] = $node->uid;
-  }
-
-  // Fetch name and data for these users.
-  $user_names = db_query("SELECT uid, name FROM {users} WHERE uid IN (:uids)", array(':uids' => $uids))->fetchAllKeyed();
-
-  // Add these values back into the node objects.
-  foreach ($uids as $nid => $uid) {
-    $nodes[$nid]->name = $user_names[$uid];
-  }
-}
-
-/**
  * Implements hook_action_info().
  */
 function user_action_info() {
diff --git a/core/modules/user/user.services.yml b/core/modules/user/user.services.yml
index e8c5e52..9743ce6 100644
--- a/core/modules/user/user.services.yml
+++ b/core/modules/user/user.services.yml
@@ -13,3 +13,18 @@ services:
   user.autocomplete:
     class: Drupal\user\UserAutocomplete
     arguments: ['@database', '@config.factory']
+  user.entity.sql.node.load:
+    class: Drupal\user\Entity\UserSqlNodeLoad
+    arguments: ['@database']
+    tags:
+      - { name: event_subscriber }
+  user.entity.node.load:
+    class: Drupal\user\Entity\UserEntityNodeLoad
+    arguments: ['@plugin.manager.entity', '@entity.query']
+    tags:
+      - { name: event_subscriber }
+  user.entity.subscriber:
+    class: Drupal\user\StorageSubscriber
+    arguments: ['@plugin.manager.entity', '@user.data', '@password']
+    tags:
+      - { name: event_subscriber }
