diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
index f807abd..0107ce7 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityStorage.php
@@ -7,7 +7,6 @@
 
 namespace Drupal\Core\Config\Entity;
 
-use Drupal\Component\Utility\String;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityMalformedException;
@@ -15,8 +14,6 @@
 use Drupal\Core\Config\Config;
 use Drupal\Core\Config\StorageInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
-use Drupal\Core\Entity\EntityStorageException;
-use Drupal\Core\Entity\Query\QueryFactory;
 use Drupal\Component\Uuid\UuidInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Symfony\Component\DependencyInjection\ContainerInterface;
@@ -46,6 +43,11 @@ class ConfigEntityStorage extends EntityStorageBase implements ConfigEntityStora
   protected $uuidService;
 
   /**
+   * {@inheritdoc}
+   */
+  protected $uuidKey = 'uuid';
+
+  /**
    * Name of the entity's status key or FALSE if a status is not supported.
    *
    * @var string|bool
@@ -113,53 +115,6 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
   }
 
   /**
-   * {@inheritdoc}
-   */
-  public function loadMultiple(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.
-    $passed_ids = !empty($ids) ? array_flip($ids) : FALSE;
-
-    // Load any remaining entities. This is the case if $ids is set to NULL (so
-    // we load all entities).
-    if ($ids === NULL || $ids) {
-      $queried_entities = $this->buildQuery($ids);
-    }
-
-    // Pass all entities loaded from the database through $this->postLoad(),
-    // which calls the
-    // entity type specific load callback, for example hook_node_type_load().
-    if (!empty($queried_entities)) {
-      $this->postLoad($queried_entities);
-      $entities += $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;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function load($id) {
-    $entities = $this->loadMultiple(array($id));
-    return isset($entities[$id]) ? $entities[$id] : NULL;
-  }
-
-  /**
    * Implements Drupal\Core\Entity\EntityStorageInterface::loadRevision().
    */
   public function loadRevision($revision_id) {
@@ -188,28 +143,9 @@ public static function getIDFromConfigName($config_name, $config_prefix) {
   }
 
   /**
-   * Builds the query to load the entity.
-   *
-   * This has full revision support. For entities requiring special queries,
-   * the class can be extended, and the default query can be constructed by
-   * calling parent::buildQuery(). This is usually necessary when the object
-   * being loaded needs to be augmented with additional data from another
-   * table, such as loading node type into comments or vocabulary machine name
-   * into terms, however it can also support $conditions on different tables.
-   * See Drupal\comment\CommentStorage::buildQuery() or
-   * Drupal\taxonomy\TermStorage::buildQuery() for examples.
-   *
-   * @param $ids
-   *   An array of entity IDs, or NULL to load all entities.
-   * @param $revision_id
-   *   The ID of the revision to load, or FALSE if this query is asking for the
-   *   most current revision(s).
-   *
-   * @return SelectQuery
-   *   A SelectQuery object for loading the entity.
+   * {@inheritdoc}
    */
-  protected function buildQuery($ids, $revision_id = FALSE) {
-    $config_class = $this->entityType->getClass();
+  protected function doLoad(array $ids = NULL) {
     $prefix = $this->getConfigPrefix();
 
     // Get the names of the configuration entities we are going to load.
@@ -227,68 +163,34 @@ protected function buildQuery($ids, $revision_id = FALSE) {
     // Load all of the configuration entities.
     $result = array();
     foreach ($this->configFactory->loadMultiple($names) as $config) {
-      $result[$config->get($this->idKey)] = new $config_class($config->get(), $this->entityTypeId);
+      $result[$config->get($this->idKey)] = $config->get();
     }
     return $result;
   }
 
   /**
-   * Implements Drupal\Core\Entity\EntityStorageInterface::create().
+   * {@inheritdoc}
    */
-  public function create(array $values = array()) {
-    $class = $this->entityType->getClass();
-    $class::preCreate($this, $values);
-
+  protected function doCreate($entity_class, array $values) {
     // Set default language to site default if not provided.
     $values += array('langcode' => $this->languageManager->getDefaultLanguage()->id);
-
-    $entity = new $class($values, $this->entityTypeId);
-    // Mark this entity as new, so isNew() returns TRUE. This does not check
-    // whether a configuration entity with the same ID (if any) already exists.
-    $entity->enforceIsNew();
-
-    // Assign a new UUID if there is none yet.
-    if (!$entity->uuid()) {
-      $entity->set('uuid', $this->uuidService->generate());
-    }
-    $entity->postCreate($this);
-
-    // Modules might need to add or change the data initially held by the new
-    // entity object, for instance to fill-in default values.
-    $this->invokeHook('create', $entity);
+    $entity = new $entity_class($values, $this->entityTypeId);
 
     // Default status to enabled.
     if (!empty($this->statusKey) && !isset($entity->{$this->statusKey})) {
       $entity->{$this->statusKey} = TRUE;
     }
-
     return $entity;
   }
 
   /**
-   * Implements Drupal\Core\Entity\EntityStorageInterface::delete().
+   * {@inheritdoc}
    */
-  public function delete(array $entities) {
-    if (!$entities) {
-      // If no IDs or invalid IDs were passed, do nothing.
-      return;
-    }
-
-    $entity_class = $this->entityType->getClass();
-    $entity_class::preDelete($this, $entities);
-    foreach ($entities as $entity) {
-      $this->invokeHook('predelete', $entity);
-    }
-
+  protected function doDelete($entities) {
     foreach ($entities as $entity) {
       $config = $this->configFactory->get($this->getConfigPrefix() . $entity->id());
       $config->delete();
     }
-
-    $entity_class::postDelete($this, $entities);
-    foreach ($entities as $entity) {
-      $this->invokeHook('delete', $entity);
-    }
   }
 
   /**
@@ -298,33 +200,21 @@ public function delete(array $entities) {
    *   When attempting to save a configuration entity that has no ID.
    */
   public function save(EntityInterface $entity) {
-    $prefix = $this->getConfigPrefix();
-
     // Configuration entity IDs are strings, and '0' is a valid ID.
     $id = $entity->id();
     if ($id === NULL || $id === '') {
       throw new EntityMalformedException('The entity does not have an ID.');
     }
 
-    // Load the stored entity, if any.
-    // At this point, the original ID can only be NULL or a valid ID.
-    if ($entity->getOriginalId() !== NULL) {
-      $id = $entity->getOriginalId();
-    }
-    $config = $this->configFactory->get($prefix . $id);
-
-    // Prevent overwriting an existing configuration file if the entity is new.
-    if ($entity->isNew() && !$config->isNew()) {
-      throw new EntityStorageException(String::format('@type entity with ID @id already exists.', array('@type' => $this->entityTypeId, '@id' => $id)));
-    }
-
-    if (!$config->isNew() && !isset($entity->original)) {
-      $entity->original = $this->loadUnchanged($id);
-    }
-
-    $entity->preSave($this);
-    $this->invokeHook('presave', $entity);
+    return parent::save($entity);
+  }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function doSave($id, EntityInterface $entity) {
+    $is_new = $entity->isNew();
+    $prefix = $this->getConfigPrefix();
     if ($id !== $entity->id()) {
       // Renaming a config object needs to cater for:
       // - Storage controller needs to access the original object.
@@ -332,34 +222,26 @@ public function save(EntityInterface $entity) {
       // - All instances of the object need to be renamed.
       $config = $this->configFactory->rename($prefix . $id, $prefix . $entity->id());
     }
+    else {
+      $config = $this->configFactory->get($prefix . $id);
+    }
 
     // Retrieve the desired properties and set them in config.
     foreach ($entity->toArray() as $key => $value) {
       $config->set($key, $value);
     }
+    $config->save();
 
-    if (!$config->isNew()) {
-      $return = SAVED_UPDATED;
-      $config->save();
-      $entity->postSave($this, TRUE);
-      $this->invokeHook('update', $entity);
-    }
-    else {
-      $return = SAVED_NEW;
-      $config->save();
-      $entity->enforceIsNew(FALSE);
-      $entity->postSave($this, FALSE);
-      $this->invokeHook('insert', $entity);
-    }
+    return $is_new ? SAVED_NEW : SAVED_UPDATED;
+  }
 
-    // After saving, this is now the "original entity", and subsequent saves
-    // will be updates instead of inserts, and updates must always be able to
-    // correctly identify the original entity.
-    $entity->setOriginalId($entity->id());
-
-    unset($entity->original);
-
-    return $return;
+  /**
+   * {@inheritdoc}
+   */
+  protected function has($id, EntityInterface $entity) {
+    $prefix = $this->getConfigPrefix();
+    $config = $this->configFactory->get($prefix . $id);
+    return !$config->isNew();
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php b/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
index f82af84..453bb9e 100644
--- a/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
+++ b/core/lib/Drupal/Core/Config/Entity/ConfigEntityType.php
@@ -39,6 +39,11 @@ class ConfigEntityType extends EntityType {
   /**
    * {@inheritdoc}
    */
+  protected $static_cache = FALSE;
+
+  /**
+   * {@inheritdoc}
+   */
   public function getControllerClasses() {
     return parent::getControllerClasses() + array(
       'storage' => 'Drupal\Core\Config\Entity\ConfigEntityStorage',
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php
index 6b7de03..8c1a255 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityDatabaseStorage.php
@@ -58,13 +58,6 @@ class ContentEntityDatabaseStorage extends ContentEntityStorageBase {
   protected $revisionDataTable;
 
   /**
-   * Whether this entity type should use the static cache.
-   *
-   * @var boolean
-   */
-  protected $cache;
-
-  /**
    * Active database connection.
    *
    * @var \Drupal\Core\Database\Connection
@@ -133,74 +126,25 @@ public function __construct(EntityTypeInterface $entity_type, Connection $databa
   /**
    * {@inheritdoc}
    */
-  public function loadMultiple(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));
-      }
-    }
-
-    // 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) {
-      // Build and execute the query.
-      $query_result = $this->buildQuery($ids)->execute();
-      $queried_entities = $query_result->fetchAllAssoc($this->idKey);
-    }
-
-    // Pass all entities loaded from the database through $this->postLoad(),
-    // 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->postLoad($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;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function load($id) {
-    $entities = $this->loadMultiple(array($id));
-    return isset($entities[$id]) ? $entities[$id] : NULL;
+  protected function doLoad(array $ids = NULL) {
+    // Build and execute the query.
+    return $this
+      ->buildQuery($ids)
+      ->execute()
+      ->fetchAllAssoc($this->idKey);
   }
 
   /**
    * Maps from storage records to entity objects.
    *
+   * 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\NodeStorage::attachLoad() for an example.
+   *
    * @param array $records
    *   Associative array of query results, keyed on the entity ID.
    *
@@ -236,6 +180,12 @@ protected function mapFromStorageRecords(array $records) {
       }
     }
     $this->attachPropertyData($entities);
+
+    // Attach field values.
+    if ($this->entityType->isFieldable()) {
+      $this->loadFieldItems($entities);
+    }
+
     return $entities;
   }
 
@@ -387,7 +337,7 @@ protected function buildPropertyQuery(QueryInterface $entity_query, array $value
    *   The ID of the revision to load, or FALSE if this query is asking for the
    *   most current revision(s).
    *
-   * @return SelectQuery
+   * @return \Drupal\Core\Database\Query\Select
    *   A SelectQuery object for loading the entity.
    */
   protected function buildQuery($ids, $revision_id = FALSE) {
@@ -437,32 +387,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\NodeStorage::attachLoad() for an example.
-   *
-   * @param $queried_entities
-   *   Associative array of query results, keyed on the entity ID.
-   */
-  protected function postLoad(array &$queried_entities) {
-    // Map the loaded records into entity objects and according fields.
-    $queried_entities = $this->mapFromStorageRecords($queried_entities);
-
-    // Attach field values.
-    if ($this->entityType->isFieldable()) {
-      $this->loadFieldItems($queried_entities);
-    }
-
-    parent::postLoad($queried_entities);
-  }
-
-  /**
    * Implements \Drupal\Core\Entity\EntityStorageInterface::delete().
    */
   public function delete(array $entities) {
@@ -473,48 +397,8 @@ public function delete(array $entities) {
 
     $transaction = $this->database->startTransaction();
     try {
-      $entity_class = $this->entityClass;
-      $entity_class::preDelete($this, $entities);
+      parent::delete($entities);
 
-      foreach ($entities as $entity) {
-        $this->invokeHook('predelete', $entity);
-      }
-      $ids = array_keys($entities);
-
-      $this->database->delete($this->entityType->getBaseTable())
-        ->condition($this->idKey, $ids)
-        ->execute();
-
-      if ($this->revisionTable) {
-        $this->database->delete($this->revisionTable)
-          ->condition($this->idKey, $ids)
-          ->execute();
-      }
-
-      if ($this->dataTable) {
-        $this->database->delete($this->dataTable)
-          ->condition($this->idKey, $ids)
-          ->execute();
-      }
-
-      if ($this->revisionDataTable) {
-        $this->database->delete($this->revisionDataTable)
-          ->condition($this->idKey, $ids)
-          ->execute();
-      }
-
-      foreach ($entities as $entity) {
-        $this->invokeFieldMethod('delete', $entity);
-        $this->deleteFieldItems($entity);
-      }
-
-      // Reset the cache as soon as the changes have been applied.
-      $this->resetCache($ids);
-
-      $entity_class::postDelete($this, $entities);
-      foreach ($entities as $entity) {
-        $this->invokeHook('delete', $entity);
-      }
       // Ignore slave server temporarily.
       db_ignore_slave();
     }
@@ -528,87 +412,53 @@ public function delete(array $entities) {
   /**
    * {@inheritdoc}
    */
+  protected function doDelete($entities) {
+    $ids = array_keys($entities);
+
+    $this->database->delete($this->entityType->getBaseTable())
+      ->condition($this->idKey, $ids)
+      ->execute();
+
+    if ($this->revisionTable) {
+      $this->database->delete($this->revisionTable)
+        ->condition($this->idKey, $ids)
+        ->execute();
+    }
+
+    if ($this->dataTable) {
+      $this->database->delete($this->dataTable)
+        ->condition($this->idKey, $ids)
+        ->execute();
+    }
+
+    if ($this->revisionDataTable) {
+      $this->database->delete($this->revisionDataTable)
+        ->condition($this->idKey, $ids)
+        ->execute();
+    }
+
+    foreach ($entities as $entity) {
+      $this->invokeFieldMethod('delete', $entity);
+      $this->deleteFieldItems($entity);
+    }
+
+    // Reset the cache as soon as the changes have been applied.
+    $this->resetCache($ids);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function save(EntityInterface $entity) {
     $transaction = $this->database->startTransaction();
     try {
       // Sync the changes made in the fields array to the internal values array.
       $entity->updateOriginalValues();
 
-      // Load the stored entity, if any.
-      if (!$entity->isNew() && !isset($entity->original)) {
-        $id = $entity->id();
-        if ($entity->getOriginalId() !== NULL) {
-          $id = $entity->getOriginalId();
-        }
-        $entity->original = $this->loadUnchanged($id);
-      }
-
-      $entity->preSave($this);
-      $this->invokeFieldMethod('preSave', $entity);
-      $this->invokeHook('presave', $entity);
-
-      // Create the storage record to be saved.
-      $record = $this->mapToStorageRecord($entity);
-
-      if (!$entity->isNew()) {
-        if ($entity->isDefaultRevision()) {
-          $return = drupal_write_record($this->entityType->getBaseTable(), $record, $this->idKey);
-        }
-        else {
-          // @todo, should a different value be returned when saving an entity
-          // with $isDefaultRevision = FALSE?
-          $return = FALSE;
-        }
-        if ($this->revisionTable) {
-          $record->{$this->revisionKey} = $this->saveRevision($entity);
-        }
-        if ($this->dataTable) {
-          $this->savePropertyData($entity);
-        }
-        if ($this->revisionDataTable) {
-          $this->savePropertyData($entity, 'revision_data_table');
-        }
-        $entity->setNewRevision(FALSE);
-        $this->invokeFieldMethod('update', $entity);
-        $this->saveFieldItems($entity, TRUE);
-        $this->resetCache(array($entity->id()));
-        $entity->postSave($this, TRUE);
-        $this->invokeHook('update', $entity);
-        if ($this->dataTable) {
-          $this->invokeTranslationHooks($entity);
-        }
-      }
-      else {
-        // Ensure the entity is still seen as new after assigning it an id,
-        // while storing its data.
-        $entity->enforceIsNew();
-        $return = drupal_write_record($this->entityType->getBaseTable(), $record);
-        $entity->{$this->idKey}->value = (string) $record->{$this->idKey};
-        if ($this->revisionTable) {
-          $entity->setNewRevision();
-          $record->{$this->revisionKey} = $this->saveRevision($entity);
-        }
-        if ($this->dataTable) {
-          $this->savePropertyData($entity);
-        }
-        if ($this->revisionDataTable) {
-          $this->savePropertyData($entity, 'revision_data_table');
-        }
-
-
-        $entity->enforceIsNew(FALSE);
-        $this->invokeFieldMethod('insert', $entity);
-        $this->saveFieldItems($entity, FALSE);
-        // Reset general caches, but keep caches specific to certain entities.
-        $this->resetCache(array());
-        $entity->postSave($this, FALSE);
-        $this->invokeHook('insert', $entity);
-      }
+      $return = parent::save($entity);
 
       // Ignore slave server temporarily.
       db_ignore_slave();
-      unset($entity->original);
-
       return $return;
     }
     catch (\Exception $e) {
@@ -619,6 +469,72 @@ public function save(EntityInterface $entity) {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  protected function doSave($id, EntityInterface $entity) {
+    // Create the storage record to be saved.
+    $record = $this->mapToStorageRecord($entity);
+
+    $is_new = $entity->isNew();
+    if (!$is_new) {
+      if ($entity->isDefaultRevision()) {
+        $return = drupal_write_record($this->entityType->getBaseTable(), $record, $this->idKey);
+      }
+      else {
+        // @todo, should a different value be returned when saving an entity
+        // with $isDefaultRevision = FALSE?
+        $return = FALSE;
+      }
+      if ($this->revisionTable) {
+        $record->{$this->revisionKey} = $this->saveRevision($entity);
+      }
+      if ($this->dataTable) {
+        $this->savePropertyData($entity);
+      }
+      if ($this->revisionDataTable) {
+        $this->savePropertyData($entity, 'revision_data_table');
+      }
+      $entity->setNewRevision(FALSE);
+      $cache_ids = array($entity->id());
+    }
+    else {
+      // Ensure the entity is still seen as new after assigning it an id,
+      // while storing its data.
+      $entity->enforceIsNew();
+      $return = drupal_write_record($this->entityType->getBaseTable(), $record);
+      $entity->{$this->idKey}->value = (string) $record->{$this->idKey};
+      if ($this->revisionTable) {
+        $entity->setNewRevision();
+        $record->{$this->revisionKey} = $this->saveRevision($entity);
+      }
+      if ($this->dataTable) {
+        $this->savePropertyData($entity);
+      }
+      if ($this->revisionDataTable) {
+        $this->savePropertyData($entity, 'revision_data_table');
+      }
+      $entity->enforceIsNew(FALSE);
+      // Reset general caches, but keep caches specific to certain entities.
+      $cache_ids = array();
+    }
+    $this->invokeFieldMethod($is_new ? 'insert' : 'update', $entity);
+    $this->saveFieldItems($entity, !$is_new);
+    $this->resetCache($cache_ids);
+
+    if (!$is_new && $this->dataTable) {
+      $this->invokeTranslationHooks($entity);
+    }
+    return $return;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function has($id, EntityInterface $entity) {
+    return !$entity->isNew();
+  }
+
+  /**
    * Stores the entity property language-aware data.
    *
    * @param \Drupal\Core\Entity\EntityInterface $entity
@@ -655,6 +571,16 @@ protected function savePropertyData(EntityInterface $entity, $table_key = 'data_
   }
 
   /**
+   * {@inheritdoc}
+   */
+  protected function invokeHook($hook, EntityInterface $entity) {
+    if ($hook == 'presave') {
+      $this->invokeFieldMethod('preSave', $entity);
+    }
+    parent::invokeHook($hook, $entity);
+  }
+
+  /**
    * Maps from an entity object to the storage record.
    *
    * @param \Drupal\Core\Entity\EntityInterface $entity
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php b/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php
index 3a7e7e59..ac2497c 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityNullStorage.php
@@ -27,6 +27,12 @@ public function loadMultiple(array $ids = NULL) {
   /**
    * {@inheritdoc}
    */
+  protected function doLoad(array $ids = NULL) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function load($id) {
     return NULL;
   }
@@ -60,6 +66,12 @@ public function delete(array $entities) {
   /**
    * {@inheritdoc}
    */
+  protected function doDelete($entities) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function save(EntityInterface $entity) {
   }
 
@@ -106,4 +118,16 @@ protected function readFieldItemsToPurge(EntityInterface $entity, FieldInstanceC
   protected function purgeFieldItems(EntityInterface $entity, FieldInstanceConfigInterface $instance) {
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  protected function doSave($id, EntityInterface $entity) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function has($id, EntityInterface $entity) {
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
index 0c2bfe4..d8930e5 100644
--- a/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/ContentEntityStorageBase.php
@@ -54,10 +54,7 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
   /**
    * {@inheritdoc}
    */
-  public function create(array $values = array()) {
-    $entity_class = $this->entityType->getClass();
-    $entity_class::preCreate($this, $values);
-
+  protected function doCreate($entity_class, array $values) {
     // We have to determine the bundle first.
     $bundle = FALSE;
     if ($this->bundleKey) {
@@ -82,12 +79,6 @@ public function create(array $values = array()) {
     foreach ($values as $name => $value) {
       $entity->$name = $value;
     }
-    $entity->postCreate($this);
-
-    // Modules might need to add or change the data initially held by the new
-    // entity object, for instance to fill-in default values.
-    $this->invokeHook('create', $entity);
-
     return $entity;
   }
 
diff --git a/core/lib/Drupal/Core/Entity/EntityDatabaseStorage.php b/core/lib/Drupal/Core/Entity/EntityDatabaseStorage.php
index c25ff56..969e011 100644
--- a/core/lib/Drupal/Core/Entity/EntityDatabaseStorage.php
+++ b/core/lib/Drupal/Core/Entity/EntityDatabaseStorage.php
@@ -9,16 +9,7 @@
 
 use Drupal\Component\Uuid\UuidInterface;
 use Drupal\Core\Database\Connection;
-use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\Query\QueryInterface;
-use Drupal\Core\Language\Language;
-use Drupal\Component\Utility\NestedArray;
-use Drupal\Component\Uuid\Uuid;
-use Drupal\field\FieldInfo;
-use Drupal\field\FieldConfigUpdateForbiddenException;
-use Drupal\field\FieldConfigInterface;
-use Drupal\field\FieldInstanceConfigInterface;
-use Drupal\field\Entity\FieldConfig;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -93,76 +84,12 @@ public function __construct(EntityTypeInterface $entity_type, Connection $databa
   /**
    * {@inheritdoc}
    */
-  public function loadMultiple(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));
-      }
-    }
-
-    // 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) {
-      // Build and execute the query.
-      $query_result = $this->buildQuery($ids)->execute();
-
-      if ($class = $this->entityType->getClass()) {
-        // 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, $class, array(array(), $this->entityTypeId));
-      }
-      $queried_entities = $query_result->fetchAllAssoc($this->idKey);
-    }
-
-    // Pass all entities loaded from the database through $this->postLoad(),
-    // 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->postLoad($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;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function load($id) {
-    $entities = $this->loadMultiple(array($id));
-    return isset($entities[$id]) ? $entities[$id] : NULL;
+  protected function doLoad(array $ids = NULL) {
+    // Build and execute the query.
+    return $this
+      ->buildQuery($ids)
+      ->execute()
+      ->fetchAllAssoc($this->idKey, \PDO::FETCH_ASSOC);
   }
 
   /**
@@ -211,7 +138,7 @@ protected function buildPropertyQuery(QueryInterface $entity_query, array $value
    * @param array|null $ids
    *   An array of entity IDs, or NULL to load all entities.
    *
-   * @return SelectQuery
+   * @return \Drupal\Core\Database\Query\Select
    *   A SelectQuery object for loading the entity.
    */
   protected function buildQuery($ids, $revision_id = FALSE) {
@@ -233,28 +160,6 @@ protected function buildQuery($ids, $revision_id = FALSE) {
   /**
    * {@inheritdoc}
    */
-  public function create(array $values = array()) {
-    $entity_class = $this->entityType->getClass();
-    $entity_class::preCreate($this, $values);
-
-    $entity = new $entity_class($values, $this->entityTypeId);
-
-    // Assign a new UUID if there is none yet.
-    if ($this->uuidKey && !isset($entity->{$this->uuidKey})) {
-      $entity->{$this->uuidKey} = $this->uuidService->generate();
-    }
-    $entity->postCreate($this);
-
-    // Modules might need to add or change the data initially held by the new
-    // entity object, for instance to fill-in default values.
-    $this->invokeHook('create', $entity);
-
-    return $entity;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function delete(array $entities) {
     if (!$entities) {
       // If no IDs or invalid IDs were passed, do nothing.
@@ -263,24 +168,8 @@ public function delete(array $entities) {
     $transaction = $this->database->startTransaction();
 
     try {
-      $entity_class = $this->entityType->getClass();
-      $entity_class::preDelete($this, $entities);
-      foreach ($entities as $entity) {
-        $this->invokeHook('predelete', $entity);
-      }
-      $ids = array_keys($entities);
+      parent::delete($entities);
 
-      $this->database->delete($this->entityType->getBaseTable())
-        ->condition($this->idKey, $ids, 'IN')
-        ->execute();
-
-      // Reset the cache as soon as the changes have been applied.
-      $this->resetCache($ids);
-
-      $entity_class::postDelete($this, $entities);
-      foreach ($entities as $entity) {
-        $this->invokeHook('delete', $entity);
-      }
       // Ignore slave server temporarily.
       db_ignore_slave();
     }
@@ -294,41 +183,27 @@ public function delete(array $entities) {
   /**
    * {@inheritdoc}
    */
+  protected function doDelete($entities) {
+    $ids = array_keys($entities);
+
+    $this->database->delete($this->entityType->getBaseTable())
+      ->condition($this->idKey, $ids, 'IN')
+      ->execute();
+
+    // Reset the cache as soon as the changes have been applied.
+    $this->resetCache($ids);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function save(EntityInterface $entity) {
     $transaction = $this->database->startTransaction();
     try {
-      // Load the stored entity, if any.
-      if (!$entity->isNew() && !isset($entity->original)) {
-        $id = $entity->id();
-        if ($entity->getOriginalId() !== NULL) {
-          $id = $entity->getOriginalId();
-        }
-        $entity->original = $this->loadUnchanged($id);
-      }
-
-      $entity->preSave($this);
-      $this->invokeHook('presave', $entity);
-
-      if (!$entity->isNew()) {
-        $return = drupal_write_record($this->entityType->getBaseTable(), $entity, $this->idKey);
-        $this->resetCache(array($entity->id()));
-        $entity->postSave($this, TRUE);
-        $this->invokeHook('update', $entity);
-      }
-      else {
-        $return = drupal_write_record($this->entityType->getBaseTable(), $entity);
-        // Reset general caches, but keep caches specific to certain entities.
-        $this->resetCache(array());
-
-        $entity->enforceIsNew(FALSE);
-        $entity->postSave($this, FALSE);
-        $this->invokeHook('insert', $entity);
-      }
+      $return = parent::save($entity);
 
       // Ignore slave server temporarily.
       db_ignore_slave();
-      unset($entity->original);
-
       return $return;
     }
     catch (\Exception $e) {
@@ -341,6 +216,30 @@ public function save(EntityInterface $entity) {
   /**
    * {@inheritdoc}
    */
+  protected function doSave($id, EntityInterface $entity) {
+    if (!$entity->isNew()) {
+      $return = drupal_write_record($this->entityType->getBaseTable(), $entity, $this->idKey);
+      $this->resetCache(array($entity->id()));
+    }
+    else {
+      $return = drupal_write_record($this->entityType->getBaseTable(), $entity);
+      // Reset general caches, but keep caches specific to certain entities.
+      $this->resetCache(array());
+    }
+
+    return $return;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function has($id, EntityInterface $entity) {
+    return !$entity->isNew();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getQueryServiceName() {
     return 'entity.query.sql';
   }
diff --git a/core/lib/Drupal/Core/Entity/EntityStorageBase.php b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
index 1befefc..0c0e4ef 100644
--- a/core/lib/Drupal/Core/Entity/EntityStorageBase.php
+++ b/core/lib/Drupal/Core/Entity/EntityStorageBase.php
@@ -6,9 +6,9 @@
  */
 
 namespace Drupal\Core\Entity;
+
+use Drupal\Component\Utility\String;
 use Drupal\Core\Entity\Query\QueryInterface;
-use Drupal\Core\Extension\ModuleHandlerInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * A base entity storage class.
@@ -155,12 +155,128 @@ protected function invokeHook($hook, EntityInterface $entity) {
   }
 
   /**
+   * {@inheritdoc}
+   */
+  public function create(array $values = array()) {
+    $entity_class = $this->entityType->getClass();
+    $entity_class::preCreate($this, $values);
+
+    $entity = $this->doCreate($entity_class, $values);
+    $entity->enforceIsNew();
+
+    // Assign a new UUID if there is none yet.
+    if ($this->uuidKey && !isset($entity->{$this->uuidKey})) {
+      $entity->{$this->uuidKey} = $this->uuidService->generate();
+    }
+    $entity->postCreate($this);
+
+    // Modules might need to add or change the data initially held by the new
+    // entity object, for instance to fill-in default values.
+    $this->invokeHook('create', $entity);
+
+    return $entity;
+  }
+
+  /**
+   * Performs storage-specific creation of entities.
+   *
+   * @param string $entity_class
+   *   The name of the entity type class.
+   * @param array $values
+   *   An array of values to set, keyed by property name.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface
+   */
+  protected function doCreate($entity_class, array $values) {
+    return new $entity_class($values, $this->entityTypeId);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function load($id) {
+    $entities = $this->loadMultiple(array($id));
+    return isset($entities[$id]) ? $entities[$id] : NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadMultiple(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));
+      }
+    }
+
+    // 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 = $this->doLoad($ids);
+    }
+
+    // Pass all entities loaded from the database through $this->postLoad(),
+    // 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->postLoad($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;
+  }
+
+  /**
+   * Performs storage-specific loading of entities.
+   *
+   * @param array|null $ids
+   *   (optional) An array of entity IDs, or NULL to load all entities.
+   *
+   * @return mixed[]
+   *   Associative array of query results, keyed on the entity ID.
+   */
+  abstract protected function doLoad(array $ids = NULL);
+
+  /**
    * Attaches data to entities upon loading.
    *
    * @param array $queried_entities
    *   Associative array of query results, keyed on the entity ID.
    */
   protected function postLoad(array &$queried_entities) {
+    $queried_entities = $this->mapFromStorageRecords($queried_entities);
+
     $entity_class = $this->entityType->getClass();
     $entity_class::postLoad($this, $queried_entities);
     // Call hook_entity_load().
@@ -176,6 +292,132 @@ protected function postLoad(array &$queried_entities) {
   }
 
   /**
+   * Maps from storage records to entity objects.
+   *
+   * @param array $records
+   *   Associative array of query results, keyed on the entity ID.
+   *
+   * @return \Drupal\Core\Entity\EntityInterface[]
+   *   An array of entity objects implementing the EntityInterface.
+   */
+  protected function mapFromStorageRecords(array $records) {
+    $class = $this->entityType->getClass();
+    $entities = array();
+    foreach ($records as $record) {
+      $entity = new $class($record, $this->entityTypeId);
+      $entities[$entity->id()] = $entity;
+    }
+    return $entities;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function delete(array $entities) {
+    if (!$entities) {
+      // If no IDs or invalid IDs were passed, do nothing.
+      return;
+    }
+
+    $entity_class = $this->entityType->getClass();
+    $entity_class::preDelete($this, $entities);
+    foreach ($entities as $entity) {
+      $this->invokeHook('predelete', $entity);
+    }
+
+    $this->doDelete($entities);
+
+    $entity_class::postDelete($this, $entities);
+    foreach ($entities as $entity) {
+      $this->invokeHook('delete', $entity);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(EntityInterface $entity) {
+    $id = $entity->id();
+
+    // Track the original ID.
+    if ($entity->getOriginalId() !== NULL) {
+      $id = $entity->getOriginalId();
+    }
+
+    // Track if this entity is new.
+    $is_new = $entity->isNew();
+    // Track if this entity exists already.
+    $id_exists = $this->has($id, $entity);
+
+    // A new entity should not already exist.
+    if ($id_exists && $is_new) {
+      throw new EntityStorageException(String::format('@type entity with ID @id already exists.', array('@type' => $this->entityTypeId, '@id' => $id)));
+    }
+
+    // Load the original entity, if any.
+    if ($id_exists && !isset($entity->original)) {
+      $entity->original = $this->loadUnchanged($id);
+    }
+
+    // Allow code to run before saving.
+    $entity->preSave($this);
+    $this->invokeHook('presave', $entity);
+
+    // Perform the save.
+    $return = $this->doSave($id, $entity);
+
+    // The entity is no longer new.
+    $entity->enforceIsNew(FALSE);
+
+    // Allow code to run after saving.
+    $entity->postSave($this, !$is_new);
+    $this->invokeHook($is_new ? 'insert' : 'update', $entity);
+
+    // After saving, this is now the "original entity", and subsequent saves
+    // will be updates instead of inserts, and updates must always be able to
+    // correctly identify the original entity.
+    $entity->setOriginalId($entity->id());
+
+    unset($entity->original);
+
+    return $return;
+  }
+
+  /**
+   * Performs storage-specific saving of the entity.
+   *
+   * @param int|string $id
+   *   The original entity ID.
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to save.
+   *
+   * @return bool|int
+   *   If the record insert or update failed, returns FALSE. If it succeeded,
+   *   returns SAVED_NEW or SAVED_UPDATED, depending on the operation performed.
+   */
+  abstract protected function doSave($id, EntityInterface $entity);
+
+  /**
+   * Determines if this entity already exists in storage.
+   *
+   * @param int|string $id
+   *   The original entity ID.
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity being saved.
+   *
+   * @return bool
+   */
+  abstract protected function has($id, EntityInterface $entity);
+
+  /**
+   * Performs storage-specific entity deletion.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface[] $entities
+   *   An array of entity objects to delete.
+   */
+  abstract protected function doDelete($entities);
+
+  /**
    * Builds an entity query.
    *
    * @param \Drupal\Core\Entity\Query\QueryInterface $entity_query
