diff --git a/core/modules/entity/entity.api.php b/core/modules/entity/entity.api.php
index 87115bd..13c7ad5 100644
--- a/core/modules/entity/entity.api.php
+++ b/core/modules/entity/entity.api.php
@@ -21,20 +21,24 @@
  *   An array whose keys are entity type names and whose values identify
  *   properties of those types that the system needs to know about:
  *   - label: The human-readable name of the type.
+ *   - entity class: The name of the entity class, defaults to
+ *     Drupal\entity\Entity. The entity class must implement EntityInterface.
  *   - controller class: The name of the class that is used to load the objects.
- *     The class has to implement the Drupal\entity\EntityControllerInterface
- *     interface. Leave blank to use the Drupal\entity\EntityController
- *     implementation.
- *   - base table: (used by Drupal\entity\EntityController) The name of the
- *     entity type's base table.
- *   - static cache: (used by Drupal\entity\EntityController) FALSE to disable
- *     static caching of entities during a page request. Defaults to TRUE.
+ *     The class has to implement the
+ *     Drupal\entity\EntityStorageControllerInterface interface. Leave blank
+ *     to use the Drupal\entity\EntityDatabaseStorageController implementation.
+ *   - base table: (used by Drupal\entity\EntityDatabaseStorageController) The
+ *     name of the entity type's base table.
+ *   - static cache: (used by Drupal\entity\EntityDatabaseStorageController)
+ *     FALSE to disable static caching of entities during a page request.
+ *     Defaults to TRUE.
  *   - field cache: (used by Field API loading and saving of field data) FALSE
  *     to disable Field API's persistent cache of field data. Only recommended
  *     if a higher level persistent cache is available for the entity type.
  *     Defaults to TRUE.
  *   - load hook: The name of the hook which should be invoked by
- *     Drupal\entity\EntityController::attachLoad(), for example 'node_load'.
+ *     Drupal\entity\EntityDatabaseStorageController::attachLoad(), for example
+ *     'node_load'.
  *   - uri callback: A function taking an entity as argument and returning the
  *     uri elements of the entity, e.g. 'path' and 'options'. The actual entity
  *     uri can be constructed by passing these elements to url().
@@ -131,7 +135,8 @@ function hook_entity_info() {
   $return = array(
     'node' => array(
       'label' => t('Node'),
-      'controller class' => 'NodeController',
+      'entity class' => 'Drupal\node\Node',
+      'controller class' => 'Drupal\node\NodeStorageController',
       'base table' => 'node',
       'revision table' => 'node_revision',
       'uri callback' => 'node_uri',
@@ -211,7 +216,7 @@ function hook_entity_info() {
  */
 function hook_entity_info_alter(&$entity_info) {
   // Set the controller class for nodes to an alternate implementation of the
-  // Drupal\entity\EntityController interface.
+  // Drupal\entity\EntityDatabaseStorageController interface.
   $entity_info['node']['controller class'] = 'Drupal\mymodule\MyCustomNodeController';
 }
 
diff --git a/core/modules/entity/entity.module b/core/modules/entity/entity.module
index c167bd4..aa32a33 100644
--- a/core/modules/entity/entity.module
+++ b/core/modules/entity/entity.module
@@ -68,7 +68,8 @@ function entity_get_info($entity_type = NULL) {
       foreach ($entity_info as $name => $data) {
         $entity_info[$name] += array(
           'fieldable' => FALSE,
-          'controller class' => 'Drupal\entity\EntityController',
+          'entity class' => 'Drupal\entity\Entity',
+          'controller class' => 'Drupal\entity\EntityDatabaseStorageController',
           'static cache' => TRUE,
           'field cache' => TRUE,
           'load hook' => $name . '_load',
@@ -92,7 +93,7 @@ function entity_get_info($entity_type = NULL) {
           $entity_info[$name]['bundles'] = array($name => array('label' => $entity_info[$name]['label']));
         }
         // Prepare entity schema fields SQL info for
-        // Drupal\entity\EntityControllerInterface::buildQuery().
+        // Drupal\entity\EntityDatabaseStorageControllerInterface::buildQuery().
         if (isset($entity_info[$name]['base table'])) {
           $entity_info[$name]['schema_fields_sql']['base table'] = drupal_schema_fields_sql($entity_info[$name]['base table']);
           if (isset($entity_info[$name]['revision table'])) {
@@ -206,8 +207,8 @@ function entity_create_stub_entity($entity_type, $ids) {
  *
  * @see hook_entity_info()
  * @see entity_load_multiple()
- * @see Drupal\entity\EntityControllerInterface
- * @see Drupal\entity\EntityController
+ * @see Drupal\entity\EntityStorageControllerInterface
+ * @see Drupal\entity\EntityDatabaseStorageController
  * @see Drupal\entity\EntityFieldQuery
  */
 function entity_load($entity_type, $id, $reset = FALSE) {
@@ -223,13 +224,14 @@ function entity_load($entity_type, $id, $reset = FALSE) {
  * database access if loaded again during the same page request.
  *
  * The actual loading is done through a class that has to implement the
- * Drupal\entity\EntityControllerInterface interface. By default,
- * Drupal\entity\EntityController is used. Entity types can specify
- * that a different class should be used by setting the 'controller class' key
- * in hook_entity_info(). These classes can either implement the
- * Drupal\entity\EntityControllerInterface interface, or, most
- * commonly, extend the Drupal\entity\EntityController class. See
- * node_entity_info() and the NodeController in node.module as an example.
+ * Drupal\entity\EntityStorageControllerInterface interface. By default,
+ * Drupal\entity\EntityDatabaseStorageController is used. Entity types can
+ * specify that a different class should be used by setting the
+ * 'controller class' key in hook_entity_info(). These classes can either
+ * implement the Drupal\entity\EntityStorageControllerInterface interface, or,
+ * most commonly, extend the Drupal\entity\EntityDatabaseStorageController
+ * class. See node_entity_info() and the NodeController in node.module as an
+ * example.
  *
  * @param string $entity_type
  *   The entity type to load, e.g. node or user.
@@ -249,8 +251,8 @@ function entity_load($entity_type, $id, $reset = FALSE) {
  * @todo Remove $conditions in Drupal 8.
  *
  * @see hook_entity_info()
- * @see Drupal\entity\EntityControllerInterface
- * @see Drupal\entity\EntityController
+ * @see Drupal\entity\EntityStorageControllerInterface
+ * @see Drupal\entity\EntityDatabaseStorageController
  * @see Drupal\entity\EntityFieldQuery
  */
 function entity_load_multiple($entity_type, $ids = FALSE, $conditions = array(), $reset = FALSE) {
diff --git a/core/modules/entity/lib/Drupal/entity/EntityController.php b/core/modules/entity/lib/Drupal/entity/EntityController.php
deleted file mode 100644
index 3edc4cc..0000000
--- a/core/modules/entity/lib/Drupal/entity/EntityController.php
+++ /dev/null
@@ -1,359 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\entity\EntityController.
- */
-
-namespace Drupal\entity;
-
-use PDO;
-
-/**
- * Defines a base entity controller class.
- *
- * Default implementation of Drupal\entity\EntityControllerInterface.
- *
- * This class can be used as-is by most simple entity types. Entity types
- * requiring special handling can extend the class.
- */
-class EntityController implements EntityControllerInterface {
-
-  /**
-   * Static cache of entities.
-   *
-   * @var array
-   */
-  protected $entityCache;
-
-  /**
-   * Entity type for this controller instance.
-   *
-   * @var string
-   */
-  protected $entityType;
-
-  /**
-   * Array of information about the entity.
-   *
-   * @var array
-   *
-   * @see entity_get_info()
-   */
-  protected $entityInfo;
-
-  /**
-   * Additional arguments to pass to hook_TYPE_load().
-   *
-   * Set before calling Drupal\entity\EntityController::attachLoad().
-   *
-   * @var array
-   */
-  protected $hookLoadArguments;
-
-  /**
-   * Name of the entity's ID field in the entity database table.
-   *
-   * @var string
-   */
-  protected $idKey;
-
-  /**
-   * Name of entity's revision database table field, if it supports revisions.
-   *
-   * Has the value FALSE if this entity does not use revisions.
-   *
-   * @var string
-   */
-  protected $revisionKey;
-
-  /**
-   * The table that stores revisions, if the entity supports revisions.
-   *
-   * @var string
-   */
-  protected $revisionTable;
-
-  /**
-   * Whether this entity type should use the static cache.
-   *
-   * Set by entity info.
-   *
-   * @var boolean
-   */
-  protected $cache;
-
-  /**
-   * Implements Drupal\entity\EntityController::__construct().
-   *
-   * Sets basic variables.
-   */
-  public function __construct($entityType) {
-    $this->entityType = $entityType;
-    $this->entityInfo = entity_get_info($entityType);
-    $this->entityCache = array();
-    $this->hookLoadArguments = array();
-    $this->idKey = $this->entityInfo['entity keys']['id'];
-
-    // Check if the entity type supports revisions.
-    if (!empty($this->entityInfo['entity keys']['revision'])) {
-      $this->revisionKey = $this->entityInfo['entity keys']['revision'];
-      $this->revisionTable = $this->entityInfo['revision table'];
-    }
-    else {
-      $this->revisionKey = FALSE;
-    }
-
-    // Check if the entity type supports static caching of loaded entities.
-    $this->cache = !empty($this->entityInfo['static cache']);
-  }
-
-  /**
-   * Implements Drupal\entity\EntityControllerInterface::resetCache().
-   */
-  public function resetCache(array $ids = NULL) {
-    if (isset($ids)) {
-      foreach ($ids as $id) {
-        unset($this->entityCache[$id]);
-      }
-    }
-    else {
-      $this->entityCache = array();
-    }
-  }
-
-  /**
-   * Implements Drupal\entity\EntityControllerInterface::load().
-   */
-  public function load($ids = array(), $conditions = array()) {
-    $entities = array();
-
-    // Revisions are not statically cached, and require a different query to
-    // other conditions, so separate the revision id into its own variable.
-    if ($this->revisionKey && isset($conditions[$this->revisionKey])) {
-      $revision_id = $conditions[$this->revisionKey];
-      unset($conditions[$this->revisionKey]);
-    }
-    else {
-      $revision_id = FALSE;
-    }
-
-    // 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 && !$revision_id) {
-      $entities += $this->cacheGet($ids, $conditions);
-      // 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 FALSE (so we load all entities), if there are any ids left to
-    // load, if loading a revision, or if $conditions was passed without $ids.
-    if ($ids === FALSE || $ids || $revision_id || ($conditions && !$passed_ids)) {
-      // Build and execute the query.
-      $query_result = $this->buildQuery($ids, $conditions, $revision_id)->execute();
-
-      if (!empty($this->entityInfo['entity class'])) {
-        // We provide the necessary arguments for PDO to create objects of the
-        // specified entity class.
-        // @see Drupal\entity\EntityInterface::__construct()
-        $query_result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['entity class'], array(array(), $this->entityType));
-      }
-      $queried_entities = $query_result->fetchAllAssoc($this->idKey);
-    }
-
-    // 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, $revision_id);
-      $entities += $queried_entities;
-    }
-
-    if ($this->cache) {
-      // Add entities to the cache if we are not loading a revision.
-      if (!empty($queried_entities) && !$revision_id) {
-        $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->{$this->idKey}] = $entity;
-      }
-      $entities = $passed_ids;
-    }
-
-    return $entities;
-  }
-
-  /**
-   * 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\CommentStorageController::buildQuery() or
-   * Drupal\taxonomy\TermStorageController::buildQuery() for examples.
-   *
-   * @param $ids
-   *   An array of entity IDs, or FALSE to load all entities.
-   * @param $conditions
-   *   An array of conditions in the form 'field' => $value.
-   * @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.
-   */
-  protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
-    $query = db_select($this->entityInfo['base table'], 'base');
-
-    $query->addTag($this->entityType . '_load_multiple');
-
-    if ($revision_id) {
-      $query->join($this->revisionTable, 'revision', "revision.{$this->idKey} = base.{$this->idKey} AND revision.{$this->revisionKey} = :revisionId", array(':revisionId' => $revision_id));
-    }
-    elseif ($this->revisionKey) {
-      $query->join($this->revisionTable, 'revision', "revision.{$this->revisionKey} = base.{$this->revisionKey}");
-    }
-
-    // Add fields from the {entity} table.
-    $entity_fields = $this->entityInfo['schema_fields_sql']['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']);
-      // The id field is provided by entity, so remove it.
-      unset($entity_revision_fields[$this->idKey]);
-
-      // Remove all fields from the base table that are also fields by the same
-      // name in the revision table.
-      $entity_field_keys = array_flip($entity_fields);
-      foreach ($entity_revision_fields as $key => $name) {
-        if (isset($entity_field_keys[$name])) {
-          unset($entity_fields[$entity_field_keys[$name]]);
-        }
-      }
-      $query->fields('revision', $entity_revision_fields);
-    }
-
-    $query->fields('base', $entity_fields);
-
-    if ($ids) {
-      $query->condition("base.{$this->idKey}", $ids, 'IN');
-    }
-    if ($conditions) {
-      foreach ($conditions as $field => $value) {
-        $query->condition('base.' . $field, $value);
-      }
-    }
-    return $query;
-  }
-
-  /**
-   * 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 NodeController::attachLoad() for an example.
-   *
-   * @param $queried_entities
-   *   Associative array of query results, keyed on the entity ID.
-   * @param $revision_id
-   *   ID of the revision that was loaded, or FALSE if the most current revision
-   *   was loaded.
-   */
-  protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
-    // Attach fields.
-    if ($this->entityInfo['fieldable']) {
-      if ($revision_id) {
-        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->entityInfo['load hook']) as $module) {
-      call_user_func_array($module . '_' . $this->entityInfo['load hook'], $args);
-    }
-  }
-
-  /**
-   * Gets entities from the static cache.
-   *
-   * @param $ids
-   *   If not empty, return entities that match these IDs.
-   * @param $conditions
-   *   If set, return entities that match all of these conditions.
-   *
-   * @return
-   *   Array of entities from the entity cache.
-   */
-  protected function cacheGet($ids, $conditions = array()) {
-    $entities = array();
-    // Load any available entities from the internal cache.
-    if (!empty($this->entityCache)) {
-      if ($ids) {
-        $entities += array_intersect_key($this->entityCache, array_flip($ids));
-      }
-      // If loading entities only by conditions, fetch all available entities
-      // from the cache. Entities which don't match are removed later.
-      elseif ($conditions) {
-        $entities = $this->entityCache;
-      }
-    }
-
-    // Exclude any entities loaded from cache if they don't match $conditions.
-    // This ensures the same behavior whether loading from memory or database.
-    if ($conditions) {
-      foreach ($entities as $entity) {
-        $entity_values = (array) $entity;
-        if (array_diff_assoc($conditions, $entity_values)) {
-          unset($entities[$entity->{$this->idKey}]);
-        }
-      }
-    }
-    return $entities;
-  }
-
-  /**
-   * Stores entities in the static entity cache.
-   *
-   * @param $entities
-   *   Entities to store in the cache.
-   */
-  protected function cacheSet($entities) {
-    $this->entityCache += $entities;
-  }
-}
diff --git a/core/modules/entity/lib/Drupal/entity/EntityControllerInterface.php b/core/modules/entity/lib/Drupal/entity/EntityControllerInterface.php
deleted file mode 100644
index 703094f..0000000
--- a/core/modules/entity/lib/Drupal/entity/EntityControllerInterface.php
+++ /dev/null
@@ -1,52 +0,0 @@
-<?php
-
-/**
- * @file
- * Definition of Drupal\entity\EntityControllerInterface.
- */
-
-namespace Drupal\entity;
-
-/**
- * Defines a common interface for entity controller classes.
- *
- * All entity controller classes specified via the 'controller class' key
- * returned by hook_entity_info() or hook_entity_info_alter() have to implement
- * this interface.
- *
- * Most simple, SQL-based entity controllers will do better by extending
- * Drupal\entity\EntityController instead of implementing this interface
- * directly.
- */
-interface EntityControllerInterface {
-
-  /**
-   * Constructs a new Drupal\entity\EntityControllerInterface object.
-   *
-   * @param $entityType
-   *   The entity type for which the instance is created.
-   */
-  public function __construct($entityType);
-
-  /**
-   * Resets the internal, static entity cache.
-   *
-   * @param $ids
-   *   (optional) If specified, the cache is reset for the entities with the
-   *   given ids only.
-   */
-  public function resetCache(array $ids = NULL);
-
-  /**
-   * Loads one or more entities.
-   *
-   * @param $ids
-   *   An array of entity IDs, or FALSE to load all entities.
-   * @param $conditions
-   *   An array of conditions in the form 'field' => $value.
-   *
-   * @return
-   *   An array of entity objects indexed by their ids.
-   */
-  public function load($ids = array(), $conditions = array());
-}
diff --git a/core/modules/entity/lib/Drupal/entity/EntityDatabaseStorageController.php b/core/modules/entity/lib/Drupal/entity/EntityDatabaseStorageController.php
index 13a529a..6453494 100644
--- a/core/modules/entity/lib/Drupal/entity/EntityDatabaseStorageController.php
+++ b/core/modules/entity/lib/Drupal/entity/EntityDatabaseStorageController.php
@@ -7,10 +7,356 @@
 
 namespace Drupal\entity;
 
+use PDO;
+
 /**
- * Implements the entity storage controller interface for the database.
+ * Defines a base entity controller class.
+ *
+ * Default implementation of
+ * Drupal\entity\EntityDatabaseStorageControllerInterface.
+ *
+ * This class can be used as-is by most simple entity types. Entity types
+ * requiring special handling can extend the class.
  */
-class EntityDatabaseStorageController extends EntityController implements EntityStorageControllerInterface {
+class EntityDatabaseStorageController implements EntityStorageControllerInterface {
+
+  /**
+   * Static cache of entities.
+   *
+   * @var array
+   */
+  protected $entityCache;
+
+  /**
+   * Entity type for this controller instance.
+   *
+   * @var string
+   */
+  protected $entityType;
+
+  /**
+   * Array of information about the entity.
+   *
+   * @var array
+   *
+   * @see entity_get_info()
+   */
+  protected $entityInfo;
+
+  /**
+   * Additional arguments to pass to hook_TYPE_load().
+   *
+   * Set before calling Drupal\entity\EntityDatabaseStorageController::attachLoad().
+   *
+   * @var array
+   */
+  protected $hookLoadArguments;
+
+  /**
+   * Name of the entity's ID field in the entity database table.
+   *
+   * @var string
+   */
+  protected $idKey;
+
+  /**
+   * Name of entity's revision database table field, if it supports revisions.
+   *
+   * Has the value FALSE if this entity does not use revisions.
+   *
+   * @var string
+   */
+  protected $revisionKey;
+
+  /**
+   * The table that stores revisions, if the entity supports revisions.
+   *
+   * @var string
+   */
+  protected $revisionTable;
+
+  /**
+   * Whether this entity type should use the static cache.
+   *
+   * Set by entity info.
+   *
+   * @var boolean
+   */
+  protected $cache;
+
+  /**
+   * Implements Drupal\entity\EntityStorageControllerInterface::__construct().
+   *
+   * Sets basic variables.
+   */
+  public function __construct($entityType) {
+    $this->entityType = $entityType;
+    $this->entityInfo = entity_get_info($entityType);
+    $this->entityCache = array();
+    $this->hookLoadArguments = array();
+    $this->idKey = $this->entityInfo['entity keys']['id'];
+
+    // Check if the entity type supports revisions.
+    if (!empty($this->entityInfo['entity keys']['revision'])) {
+      $this->revisionKey = $this->entityInfo['entity keys']['revision'];
+      $this->revisionTable = $this->entityInfo['revision table'];
+    }
+    else {
+      $this->revisionKey = FALSE;
+    }
+
+    // Check if the entity type supports static caching of loaded entities.
+    $this->cache = !empty($this->entityInfo['static cache']);
+  }
+
+  /**
+   * Implements Drupal\entity\EntityStorageControllerInterface::resetCache().
+   */
+  public function resetCache(array $ids = NULL) {
+    if (isset($ids)) {
+      foreach ($ids as $id) {
+        unset($this->entityCache[$id]);
+      }
+    }
+    else {
+      $this->entityCache = array();
+    }
+  }
+
+  /**
+   * Implements Drupal\entity\EntityStorageControllerInterface::load().
+   */
+  public function load($ids = array(), $conditions = array()) {
+    $entities = array();
+
+    // Revisions are not statically cached, and require a different query to
+    // other conditions, so separate the revision id into its own variable.
+    if ($this->revisionKey && isset($conditions[$this->revisionKey])) {
+      $revision_id = $conditions[$this->revisionKey];
+      unset($conditions[$this->revisionKey]);
+    }
+    else {
+      $revision_id = FALSE;
+    }
+
+    // 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 && !$revision_id) {
+      $entities += $this->cacheGet($ids, $conditions);
+      // 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 FALSE (so we load all entities), if there are any ids left to
+    // load, if loading a revision, or if $conditions was passed without $ids.
+    if ($ids === FALSE || $ids || $revision_id || ($conditions && !$passed_ids)) {
+      // Build and execute the query.
+      $query_result = $this->buildQuery($ids, $conditions, $revision_id)->execute();
+
+      if (!empty($this->entityInfo['entity class'])) {
+        // We provide the necessary arguments for PDO to create objects of the
+        // specified entity class.
+        // @see Drupal\entity\EntityInterface::__construct()
+        $query_result->setFetchMode(PDO::FETCH_CLASS, $this->entityInfo['entity class'], array(array(), $this->entityType));
+      }
+      $queried_entities = $query_result->fetchAllAssoc($this->idKey);
+    }
+
+    // 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, $revision_id);
+      $entities += $queried_entities;
+    }
+
+    if ($this->cache) {
+      // Add entities to the cache if we are not loading a revision.
+      if (!empty($queried_entities) && !$revision_id) {
+        $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->{$this->idKey}] = $entity;
+      }
+      $entities = $passed_ids;
+    }
+
+    return $entities;
+  }
+
+  /**
+   * 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\CommentStorageController::buildQuery() or
+   * Drupal\taxonomy\TermStorageController::buildQuery() for examples.
+   *
+   * @param $ids
+   *   An array of entity IDs, or FALSE to load all entities.
+   * @param $conditions
+   *   An array of conditions in the form 'field' => $value.
+   * @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.
+   */
+  protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
+    $query = db_select($this->entityInfo['base table'], 'base');
+
+    $query->addTag($this->entityType . '_load_multiple');
+
+    if ($revision_id) {
+      $query->join($this->revisionTable, 'revision', "revision.{$this->idKey} = base.{$this->idKey} AND revision.{$this->revisionKey} = :revisionId", array(':revisionId' => $revision_id));
+    }
+    elseif ($this->revisionKey) {
+      $query->join($this->revisionTable, 'revision', "revision.{$this->revisionKey} = base.{$this->revisionKey}");
+    }
+
+    // Add fields from the {entity} table.
+    $entity_fields = $this->entityInfo['schema_fields_sql']['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']);
+      // The id field is provided by entity, so remove it.
+      unset($entity_revision_fields[$this->idKey]);
+
+      // Remove all fields from the base table that are also fields by the same
+      // name in the revision table.
+      $entity_field_keys = array_flip($entity_fields);
+      foreach ($entity_revision_fields as $key => $name) {
+        if (isset($entity_field_keys[$name])) {
+          unset($entity_fields[$entity_field_keys[$name]]);
+        }
+      }
+      $query->fields('revision', $entity_revision_fields);
+    }
+
+    $query->fields('base', $entity_fields);
+
+    if ($ids) {
+      $query->condition("base.{$this->idKey}", $ids, 'IN');
+    }
+    if ($conditions) {
+      foreach ($conditions as $field => $value) {
+        $query->condition('base.' . $field, $value);
+      }
+    }
+    return $query;
+  }
+
+  /**
+   * 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 NodeController::attachLoad() for an example.
+   *
+   * @param $queried_entities
+   *   Associative array of query results, keyed on the entity ID.
+   * @param $revision_id
+   *   ID of the revision that was loaded, or FALSE if the most current revision
+   *   was loaded.
+   */
+  protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
+    // Attach fields.
+    if ($this->entityInfo['fieldable']) {
+      if ($revision_id) {
+        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->entityInfo['load hook']) as $module) {
+      call_user_func_array($module . '_' . $this->entityInfo['load hook'], $args);
+    }
+  }
+
+  /**
+   * Gets entities from the static cache.
+   *
+   * @param $ids
+   *   If not empty, return entities that match these IDs.
+   * @param $conditions
+   *   If set, return entities that match all of these conditions.
+   *
+   * @return
+   *   Array of entities from the entity cache.
+   */
+  protected function cacheGet($ids, $conditions = array()) {
+    $entities = array();
+    // Load any available entities from the internal cache.
+    if (!empty($this->entityCache)) {
+      if ($ids) {
+        $entities += array_intersect_key($this->entityCache, array_flip($ids));
+      }
+      // If loading entities only by conditions, fetch all available entities
+      // from the cache. Entities which don't match are removed later.
+      elseif ($conditions) {
+        $entities = $this->entityCache;
+      }
+    }
+
+    // Exclude any entities loaded from cache if they don't match $conditions.
+    // This ensures the same behavior whether loading from memory or database.
+    if ($conditions) {
+      foreach ($entities as $entity) {
+        $entity_values = (array) $entity;
+        if (array_diff_assoc($conditions, $entity_values)) {
+          unset($entities[$entity->{$this->idKey}]);
+        }
+      }
+    }
+    return $entities;
+  }
+
+  /**
+   * Stores entities in the static entity cache.
+   *
+   * @param $entities
+   *   Entities to store in the cache.
+   */
+  protected function cacheSet($entities) {
+    $this->entityCache += $entities;
+  }
 
   /**
    * Implements Drupal\entity\EntityStorageControllerInterface::create().
diff --git a/core/modules/entity/lib/Drupal/entity/EntityStorageControllerInterface.php b/core/modules/entity/lib/Drupal/entity/EntityStorageControllerInterface.php
index cacc57c..52833b1 100644
--- a/core/modules/entity/lib/Drupal/entity/EntityStorageControllerInterface.php
+++ b/core/modules/entity/lib/Drupal/entity/EntityStorageControllerInterface.php
@@ -8,9 +8,47 @@
 namespace Drupal\entity;
 
 /**
- * Defines a common interface for entity storage controllers.
+ * Defines a common interface for entity controller classes.
+ *
+ * All entity controller classes specified via the 'controller class' key
+ * returned by hook_entity_info() or hook_entity_info_alter() have to implement
+ * this interface.
+ *
+ * Most simple, SQL-based entity controllers will do better by extending
+ * Drupal\entity\EntityDatabaseStorageController instead of implementing this
+ * interface directly.
  */
-interface EntityStorageControllerInterface extends EntityControllerInterface {
+interface EntityStorageControllerInterface {
+
+  /**
+   * Constructs a new Drupal\entity\EntityStorageControllerInterface object.
+   *
+   * @param $entityType
+   *   The entity type for which the instance is created.
+   */
+  public function __construct($entityType);
+
+  /**
+   * Resets the internal, static entity cache.
+   *
+   * @param $ids
+   *   (optional) If specified, the cache is reset for the entities with the
+   *   given ids only.
+   */
+  public function resetCache(array $ids = NULL);
+
+  /**
+   * Loads one or more entities.
+   *
+   * @param $ids
+   *   An array of entity IDs, or FALSE to load all entities.
+   * @param $conditions
+   *   An array of conditions in the form 'field' => $value.
+   *
+   * @return
+   *   An array of entity objects indexed by their ids.
+   */
+  public function load($ids = array(), $conditions = array());
 
   /**
    * Constructs a new entity object, without permanently saving it.
diff --git a/core/modules/entity/tests/entity.test b/core/modules/entity/tests/entity.test
index 1047279..e7e3fc3 100644
--- a/core/modules/entity/tests/entity.test
+++ b/core/modules/entity/tests/entity.test
@@ -243,6 +243,6 @@ class EntityAPIInfoTestCase extends WebTestBase  {
     module_enable(array('entity_cache_test'));
     $info = variable_get('entity_cache_test');
     $this->assertEqual($info['label'], 'Entity Cache Test', 'Entity info label is correct.');
-    $this->assertEqual($info['controller class'], 'Drupal\entity\EntityController', 'Entity controller class info is correct.');
+    $this->assertEqual($info['controller class'], 'Drupal\entity\EntityDatabaseStorageController', 'Entity controller class info is correct.');
   }
 }
diff --git a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityBundleController.php b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityBundleController.php
index 7f74b9b..a50e3db 100644
--- a/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityBundleController.php
+++ b/core/modules/field/tests/modules/field_test/lib/Drupal/field_test/TestEntityBundleController.php
@@ -7,16 +7,16 @@
 
 namespace Drupal\field_test;
 
-use Drupal\entity\EntityController;
+use Drupal\entity\EntityDatabaseStorageController;
 
 /**
  * Controller class for the test_entity_bundle entity type.
  *
- * This extends the Drupal\entity\EntityController class, adding
+ * This extends the Drupal\entity\EntityDatabaseStorageController class, adding
  * required special handling for bundles (since they are not stored in the
  * database).
  */
-class TestEntityBundleController extends EntityController {
+class TestEntityBundleController extends EntityDatabaseStorageController {
 
   protected function attachLoad(&$entities, $revision_id = FALSE) {
     // Add bundle information.
diff --git a/core/modules/node/lib/Drupal/node/NodeStorageController.php b/core/modules/node/lib/Drupal/node/NodeStorageController.php
index dccec71..fb372c1 100644
--- a/core/modules/node/lib/Drupal/node/NodeStorageController.php
+++ b/core/modules/node/lib/Drupal/node/NodeStorageController.php
@@ -152,7 +152,7 @@ class NodeStorageController extends EntityDatabaseStorageController {
   }
 
   /**
-   * Overrides Drupal\entity\EntityController::attachLoad().
+   * Overrides Drupal\entity\EntityDatabaseStorageController::attachLoad().
    */
   protected function attachLoad(&$nodes, $revision_id = FALSE) {
     // Create an array of nodes for each content type and pass this to the
@@ -177,7 +177,7 @@ class NodeStorageController extends EntityDatabaseStorageController {
   }
 
   /**
-   * Overrides Drupal\entity\EntityController::buildQuery().
+   * Overrides Drupal\entity\EntityDatabaseStorageController::buildQuery().
    */
   protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
     // Ensure that uid is taken from the {node} table,
diff --git a/core/modules/node/node.api.php b/core/modules/node/node.api.php
index 915d906..1958af5 100644
--- a/core/modules/node/node.api.php
+++ b/core/modules/node/node.api.php
@@ -529,7 +529,7 @@ function hook_node_insert(Drupal\node\Node $node) {
  * Act on nodes being loaded from the database.
  *
  * This hook is invoked during node loading, which is handled by entity_load(),
- * via classes NodeController and Drupal\entity\EntityController.
+ * via classes NodeController and Drupal\entity\EntityDatabaseStorageController.
  * After the node information is read from the database or the entity cache,
  * hook_load() is invoked on the node's content type module, then
  * field_attach_node_revision() or field_attach_load() is called, then
@@ -1158,7 +1158,7 @@ function hook_insert(Drupal\node\Node $node) {
  * (use hook_node_load() to respond to all node loads).
  *
  * This hook is invoked during node loading, which is handled by entity_load(),
- * via classes NodeController and Drupal\entity\EntityController.
+ * via classes NodeController and Drupal\entity\EntityDatabaseStorageController.
  * After the node information is read from the database or the entity cache,
  * hook_load() is invoked on the node's content type module, then
  * field_attach_node_revision() or field_attach_load() is called, then
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php
index 5f511be..56fa72d 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/TermStorageController.php
@@ -131,7 +131,7 @@ class TermStorageController extends EntityDatabaseStorageController {
   }
 
   /**
-   * Implements Drupal\entity\EntityControllerInterface::resetCache().
+   * Overrides Drupal\entity\EntityDatabaseStorageController::resetCache().
    */
   public function resetCache(array $ids = NULL) {
     drupal_static_reset('taxonomy_term_count_nodes');
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php
index 8d64467..b5d7564 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/VocabularyStorageController.php
@@ -79,7 +79,7 @@ class VocabularyStorageController extends EntityDatabaseStorageController {
   }
 
   /**
-   * Implements Drupal\entity\DrupalEntityControllerInterface::resetCache().
+   * Overrides Drupal\entity\DrupalEntityDatabaseStorageController::resetCache().
    */
   public function resetCache(array $ids = NULL) {
     drupal_static_reset('taxonomy_vocabulary_get_names');
