diff --git a/core/includes/schema.inc b/core/includes/schema.inc
index 47e7836..5773d71 100644
--- a/core/includes/schema.inc
+++ b/core/includes/schema.inc
@@ -370,13 +370,15 @@ function drupal_schema_fields_sql($table, $prefix = NULL) {
  *   If the record insert or update failed, returns FALSE. If it succeeded,
  *   returns SAVED_NEW or SAVED_UPDATED, depending on the operation performed.
  */
-function drupal_write_record($table, &$record, $primary_keys = array()) {
+function drupal_write_record($table, &$record, $primary_keys = array(), $schema = NULL) {
   // Standardize $primary_keys to an array.
   if (is_string($primary_keys)) {
     $primary_keys = array($primary_keys);
   }
 
-  $schema = drupal_get_schema($table);
+  if (!isset($schema)) {
+    $schema = drupal_get_schema($table);
+  }
   if (empty($schema)) {
     return FALSE;
   }
diff --git a/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php b/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php
index 222145f..bba9b11 100644
--- a/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php
+++ b/core/lib/Drupal/Core/Entity/FieldableDatabaseStorageController.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Entity\EntityTypeInterface;
 use Drupal\Core\Entity\Query\QueryInterface;
+use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Language\Language;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Component\Uuid\Uuid;
@@ -40,6 +41,13 @@ class FieldableDatabaseStorageController extends FieldableEntityStorageControlle
   protected $revisionKey = FALSE;
 
   /**
+   * The base table of the entity, if the entity has storage.
+   *
+   * @var string
+   */
+  protected $baseTable;
+
+  /**
    * The table that stores revisions, if the entity supports revisions.
    *
    * @var string
@@ -68,6 +76,13 @@ class FieldableDatabaseStorageController extends FieldableEntityStorageControlle
   protected $cache;
 
   /**
+   * The computed schema array of this entity type.
+   *
+   * @var array
+   */
+  protected $schema;
+
+  /**
    * Active database connection.
    *
    * @var \Drupal\Core\Database\Connection
@@ -116,6 +131,11 @@ public function __construct(EntityTypeInterface $entity_type, Connection $databa
     // Check if the entity type supports UUIDs.
     $this->uuidKey = $this->entityType->getKey('uuid');
 
+    // Some entity types are not stored in the database at all.
+    if ($base_table = $this->entityType->getBaseTable()) {
+      $this->baseTable = $base_table;
+    }
+
     // Check if the entity type supports revisions.
     if ($this->entityType->hasKey('revision')) {
       $this->revisionKey = $this->entityType->getKey('revision');
@@ -123,12 +143,14 @@ public function __construct(EntityTypeInterface $entity_type, Connection $databa
     }
 
     // Check if the entity type has a dedicated table for fields.
+    // @todo Replace this with a check for the 'language' entity key when
+    //   https://drupal.org/node/2143729 has landed.
     if ($data_table = $this->entityType->getDataTable()) {
       $this->dataTable = $data_table;
       // Entity types having both revision and translation support should always
       // define a revision data table.
-      if ($this->revisionTable && $revision_data_table = $this->entityType->getRevisionDataTable()) {
-        $this->revisionDataTable = $revision_data_table;
+      if ($this->revisionTable) {
+        $this->revisionDataTable = $this->entityType->getRevisionDataTable();
       }
     }
   }
@@ -139,6 +161,11 @@ public function __construct(EntityTypeInterface $entity_type, Connection $databa
   public function loadMultiple(array $ids = NULL) {
     $entities = array();
 
+    // If the schema has not been initialized yet, no entities exist.
+    if (!$this->schemaExists()) {
+      return $entities;
+    }
+
     // 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,
@@ -319,6 +346,10 @@ protected function attachPropertyData(array &$entities) {
    * Implements \Drupal\Core\Entity\EntityStorageControllerInterface::loadRevision().
    */
   public function loadRevision($revision_id) {
+    if (!$this->schemaExists()) {
+      return FALSE;
+    }
+
     // Build and execute the query.
     $query_result = $this->buildQuery(array(), $revision_id)->execute();
     $queried_entities = $query_result->fetchAllAssoc($this->idKey);
@@ -469,7 +500,7 @@ protected function postLoad(array &$queried_entities) {
    * Implements \Drupal\Core\Entity\EntityStorageControllerInterface::delete().
    */
   public function delete(array $entities) {
-    if (!$entities) {
+    if (!$entities || $this->schemaExists()) {
       // If no IDs or invalid IDs were passed, do nothing.
       return;
     }
@@ -534,6 +565,8 @@ public function delete(array $entities) {
   public function save(EntityInterface $entity) {
     $transaction = $this->database->startTransaction();
     try {
+      $this->ensureSchema();
+
       // Sync the changes made in the fields array to the internal values array.
       $entity->updateOriginalValues();
 
@@ -551,7 +584,7 @@ public function save(EntityInterface $entity) {
 
       if (!$entity->isNew()) {
         if ($entity->isDefaultRevision()) {
-          $return = drupal_write_record($this->entityType->getBaseTable(), $record, $this->idKey);
+          $return = drupal_write_record($this->baseTable, $record, $this->idKey, $this->getSchema('base_table'));
         }
         else {
           // @todo, should a different value be returned when saving an entity
@@ -581,7 +614,7 @@ public function save(EntityInterface $entity) {
         // 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);
+        $return = drupal_write_record($this->baseTable, $record, array(), $this->getSchema('base_table'));
         $entity->{$this->idKey}->value = (string) $record->{$this->idKey};
         if ($this->revisionTable) {
           $entity->setNewRevision();
@@ -669,11 +702,16 @@ protected function mapToStorageRecord(EntityInterface $entity, $table_key = 'bas
     $record = new \stdClass();
     $values = array();
     $definitions = $entity->getFieldDefinitions();
-    $schema = drupal_get_schema($this->entityType->get($table_key));
+    $schema = $this->getSchema($table_key);
+    // Backwards-compatibility layer for entity types that declare their schema
+    // in hook_schema() manually.
+    if (!$schema) {
+      $schema = drupal_get_schema($this->entityType->get($table_key));
+    }
     $is_new = $entity->isNew();
 
     $multi_column_fields = array();
-    foreach (drupal_schema_fields_sql($this->entityType->get($table_key)) as $name) {
+    foreach (array_keys($schema['fields']) as $name) {
       // Check for fields which store data in multiple columns and process them
       // separately.
       if ($field = strstr($name, '__', TRUE)) {
@@ -746,7 +784,7 @@ protected function saveRevision(EntityInterface $entity) {
     $entity->preSaveRevision($this, $record);
 
     if ($entity->isNewRevision()) {
-      drupal_write_record($this->revisionTable, $record);
+      drupal_write_record($this->revisionTable, $record, array(), $this->getSchema('revision_table'));
       if ($entity->isDefaultRevision()) {
         $this->database->update($this->entityType->getBaseTable())
           ->fields(array($this->revisionKey => $record->{$this->revisionKey}))
@@ -755,7 +793,7 @@ protected function saveRevision(EntityInterface $entity) {
       }
     }
     else {
-      drupal_write_record($this->revisionTable, $record, $this->revisionKey);
+      drupal_write_record($this->revisionTable, $record, $this->revisionKey, $this->getSchema('revision_table'));
     }
 
     // Make sure to update the new revision key for the entity.
@@ -1455,4 +1493,228 @@ static public function _fieldColumnName(FieldConfigInterface $field, $column) {
     return in_array($column, FieldConfig::getReservedColumns()) ? $column : $field->getName() . '_' . $column;
   }
 
+  /**
+   * Gets the schema for this entity type.
+   *
+   * @param string $table_key
+   *   (optional) The entity key identifying the target table to return the
+   *   schema for. If omitted, returns the schema for all table types.
+   *
+   * @return array|null
+   *   Returns the schema definitions for the entity type's tables. If the
+   *   entity type does not have a schema, returns NULL.
+   */
+  public function getSchema($table_key = NULL) {
+    $passed_table_key = $table_key;
+
+    $tables = array_filter(array(
+      'base_table' => $this->baseTable,
+      'revision_table' => $this->revisionTable,
+      'data_table' => $this->dataTable,
+      'revision_data_table' => $this->revisionDataTable,
+    ));
+
+    // If this entity type does not support storage, no schema information can
+    // be collected.
+    if (empty($tables)) {
+      $this->schema = NULL;
+    }
+
+    if (!isset($this->schema)) {
+      $entity_type_id = $this->entityTypeId;
+      $entity_keys = array_filter(array(
+        'id' => $this->idKey,
+        'revision' => $this->revisionKey,
+        'bundle' => $this->bundleKey,
+        'uuid' => $this->uuidKey,
+      ));
+      if ($this->dataTable) {
+        $entity_keys['language'] = 'langcode';
+        $entity_keys['default_language'] = 'default_langcode';
+      }
+
+      // Add primary keys and common foreign keys to the tables.
+      $this->schema[$tables['base_table']]['primary key'] = array($entity_keys['id']);
+      $this->schema[$tables['base_table']]['foreign keys'] = array();
+      if (isset($tables['revision_table'])) {
+        $this->schema[$tables['revision_table']]['primary key'] = array($entity_keys['revision']);
+        $this->schema[$tables['revision_table']]['foreign keys'][$entity_keys['id']] = array(
+          'table' => $tables['base_table'],
+          'columns' => array($entity_keys['id'] => $entity_keys['id']),
+        );
+      }
+      if (isset($tables['data_table'])) {
+        $this->schema[$tables['data_table']]['primary key'] = array($entity_keys['id'], 'langcode');
+        $this->schema[$tables['data_table']]['foreign keys'][$entity_keys['id']] = array(
+          'table' => $tables['base_table'],
+          'columns' => array($entity_keys['id'] => $entity_keys['id']),
+        );
+      }
+      if (isset($tables['revision_data_table'])) {
+        $this->schema[$tables['revision_data_table']]['primary key'] = array($entity_keys['revision'], 'langcode');
+        $this->schema[$tables['revision_data_table']]['foreign keys'][$entity_keys['id']] = array(
+          'table' => $tables['base_table'],
+          'columns' => array($entity_keys['id'] => $entity_keys['id']),
+        );
+        $this->schema[$tables['revision_data_table']]['foreign keys'][$entity_keys['revision']] = array(
+          'table' => $tables['revision_table'],
+          'columns' => array($entity_keys['revision'] => $entity_keys['revision']),
+        );
+      }
+
+      $class = $this->entityClass;
+      // Invoke ContentEntityInterface::baseFieldDefinitions() directly. Avoid
+      // info or alter hooks.
+      foreach($class::baseFieldDefinitions($entity_type_id) as $field_name => $field_definition) {
+        /** @var \Drupal\Core\Field\FieldDefinitionInterface $field_definition */
+        if ($field_definition->isComputed()) {
+          continue;
+        }
+
+        if (FALSE !== $key = array_search($field_name, $entity_keys)) {
+          if ($key !== 'default_language') {
+            $this->addFieldSchema($this->schema[$this->baseTable], $field_name, $field_definition);
+          }
+          if ($this->revisionTable && in_array($key, array('id', 'revision', 'language'))) {
+            $this->addFieldSchema($this->schema[$this->revisionTable], $field_name, $field_definition);
+          }
+          if ($this->dataTable && in_array($key, array('id', 'revision', 'language', 'default_language'))) {
+            $this->addFieldSchema($this->schema[$this->dataTable], $field_name, $field_definition);
+          }
+          if ($this->revisionDataTable && in_array($key, array('id', 'revision', 'language', 'default_language'))) {
+            $this->addFieldSchema($this->schema[$this->revisionDataTable], $field_name, $field_definition);
+          }
+        }
+        else {
+          if (!$this->dataTable || !$field_definition->isTranslatable()) {
+            $this->addFieldSchema($this->schema[$this->baseTable], $field_name, $field_definition);
+            if ($this->revisionTable) {
+              $this->addFieldSchema($this->schema[$this->revisionTable], $field_name, $field_definition);
+            }
+          }
+          else {
+            $this->addFieldSchema($this->schema[$this->dataTable], $field_name, $field_definition);
+            if ($this->revisionDataTable) {
+              $this->addFieldSchema($this->schema[$this->revisionDataTable], $field_name, $field_definition);
+            }
+          }
+        }
+      }
+
+      // Change the ID field in the base table to 'serial' if it is 'int'.
+      if ($this->schema[$tables['base_table']]['fields'][$entity_keys['id']]['type'] == 'int') {
+        $this->schema[$tables['base_table']]['fields'][$entity_keys['id']]['type'] = 'serial';
+        unset($this->schema[$tables['base_table']]['fields'][$entity_keys['id']]['default']);
+      }
+      // Change the revision ID field in the revision table 'serial' if it is
+      // 'int'.
+      if (isset($tables['revision_table']) && $this->schema[$tables['revision_table']]['fields'][$entity_keys['revision']]['type'] == 'int') {
+        $this->schema[$tables['revision_table']]['fields'][$entity_keys['revision']]['type'] = 'serial';
+        unset($this->schema[$tables['revision_table']]['fields'][$entity_keys['revision']]['default']);
+      }
+
+      // Add table descriptions.
+      foreach ($tables as $table_key => $table) {
+        $this->schema[$table]['description'] = "The $table_key table for $entity_type_id entities.";
+      }
+    }
+
+    if (isset($passed_table_key)) {
+      if (isset($this->schema[$tables[$passed_table_key]])) {
+        return $this->schema[$tables[$passed_table_key]];
+      }
+      return;
+    }
+
+    return $this->schema;
+  }
+
+  /**
+   * Adds the schema for a single to the table schema.
+   *
+   * @param array $schema
+   *   The schema array for a table, passed by reference.
+   * @param string $field_name
+   *   The name of the field.
+   * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
+   *   The field definition of the field.
+   */
+  protected function addFieldSchema(&$schema, $field_name, FieldDefinitionInterface $field_definition) {
+    $field_schema = $field_definition->getSchema();
+    if (count($field_schema['columns']) == 1) {
+      $schema['fields'][$field_name] = $field_schema['columns'][$field_definition->getMainPropertyName()];
+      $schema['fields'][$field_name]['description'] = $field_definition->getDescription();
+
+      if (!empty($field_schema['unique keys'])) {
+        $schema['unique keys'][$field_name] = array($field_name);
+      }
+      if (!empty($field_schema['indexes'])) {
+        // @todo Support indexes specified as an array of column name and
+        //   length.
+        $schema['indexes'][$field_name] = array($field_name);
+      }
+      if (!empty($field_schema['foreign keys'])) {
+        $schema['foreign keys'] += $field_schema['foreign keys'];
+      }
+    }
+    else {
+      foreach ($field_schema['columns'] as $column_name => $column_schema) {
+        $schema['fields'][$field_name . '__' . $column_name] = $column_schema;
+      }
+
+      // @todo Descriptions, unique keys, indexes and foreign keys.
+    }
+  }
+
+  /**
+   * Checks whether the entity tables exist in the database.
+   *
+   * @return bool
+   *   Whether or not the schema tables exist in the database.
+   */
+  protected function schemaExists() {
+    if ($this->baseTable) {
+      return $this->database->schema()->tableExists($this->baseTable);
+    }
+    // Return FALSE for entities without any storage.
+    return FALSE;
+  }
+
+  /**
+   * Ensures the entity tables exist in the database.
+   *
+   * If the entity tables do not exist, they are created according to the schema
+   * definitions computed from the entity type's fields.
+   *
+   * @see \Drupal\Core\Entity\FieldableDatabaseStorageController::getSchema()
+   *
+   * @todo Make this protected.
+   */
+  public function ensureSchema() {
+    if (!$this->schemaExists() && ($schema = $this->getSchema())) {
+      foreach ($schema as $table_name => $table_schema) {
+        $this->database->schema()->createTable($table_name, $table_schema);
+      }
+    }
+  }
+
+  /**
+   * Deletes the entity tables from the database.
+   *
+   * @todo Make this protected.
+   */
+  public function deleteSchema() {
+    if ($this->schemaExists()) {
+      $tables = array_filter(array(
+        $this->baseTable,
+        $this->revisionTable,
+        $this->dataTable,
+        $this->revisionDataTable,
+      ));
+      foreach ($tables as $table_name) {
+        $this->database->schema()->dropTable($table_name);
+      }
+    }
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Entity/Query/Sql/Query.php b/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
index 407cef5..62fd9a6 100644
--- a/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
+++ b/core/lib/Drupal/Core/Entity/Query/Sql/Query.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Database\Query\SelectInterface;
 use Drupal\Core\Entity\EntityManagerInterface;
 use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\FieldableDatabaseStorageController;
 use Drupal\Core\Entity\Query\QueryBase;
 use Drupal\Core\Entity\Query\QueryException;
 use Drupal\Core\Entity\Query\QueryInterface;
@@ -53,13 +54,6 @@ class Query extends QueryBase implements QueryInterface {
   protected $connection;
 
   /**
-   * Stores the entity manager used by the query.
-   *
-   * @var \Drupal\Core\Entity\EntityManagerInterface
-   */
-  protected $entityManager;
-
-  /**
    * Constructs a query object.
    *
    * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
@@ -107,6 +101,12 @@ protected function prepare() {
     if ($this->entityType->getDataTable()) {
       $simple_query = FALSE;
     }
+
+    $storage_controller = \Drupal::entityManager()->getStorageController($this->entityTypeId);
+    if ($storage_controller instanceof FieldableDatabaseStorageController) {
+      $storage_controller->ensureSchema();
+    }
+
     $this->sqlQuery = $this->connection->select($base_table, 'base_table', array('conjunction' => $this->conjunction));
     $this->sqlQuery->addMetaData('entity_type', $this->entityTypeId);
     $id_field = $this->entityType->getKey('id');
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
index 58a5961..90a2711 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/EntityReferenceItem.php
@@ -89,6 +89,7 @@ public static function schema(FieldDefinitionInterface $field_definition) {
           'type' => 'int',
           'unsigned' => TRUE,
           'not null' => TRUE,
+          'default' => 0,
         ),
       );
     }
diff --git a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php
index 0a7a8cf..07c9a81 100644
--- a/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php
+++ b/core/lib/Drupal/Core/Field/Plugin/Field/FieldType/IntegerItem.php
@@ -42,6 +42,7 @@ public static function schema(FieldDefinitionInterface $field_definition) {
         'value' => array(
           'type' => 'int',
           'not null' => TRUE,
+          'default' => 0,
         ),
       ),
     );
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.install b/core/modules/system/tests/modules/entity_test/entity_test.install
index 3df5396..eb46f54 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.install
+++ b/core/modules/system/tests/modules/entity_test/entity_test.install
@@ -9,433 +9,57 @@
  * Implements hook_install().
  */
 function entity_test_install() {
-  $entity_types = array(
+  $entity_manager = \Drupal::entityManager();
+
+  $entity_type_ids = array(
     'entity_test',
     'entity_test_rev',
     'entity_test_mul',
     'entity_test_mulrev',
   );
-  foreach ($entity_types as $entity_type) {
+  foreach ($entity_type_ids as $entity_type_id) {
+    $storage_controller = $entity_manager->getStorageController($entity_type_id);
+
+    // Install schema.
+    $storage_controller->ensureSchema();
+
     // Auto-create fields for testing.
-    entity_create('field_config', array(
+    $storage_controller->create(array(
       'name' => 'field_test_text',
-      'entity_type' => $entity_type,
+      'entity_type' => $entity_type_id,
       'type' => 'text',
       'cardinality' => 1,
       'translatable' => FALSE,
     ))->save();
-    entity_create('field_instance_config', array(
-      'entity_type' => $entity_type,
+    $storage_controller->create(array(
+      'entity_type' => $entity_type_id,
       'field_name' => 'field_test_text',
-      'bundle' => $entity_type,
+      'bundle' => $entity_type_id,
       'label' => 'Test text-field',
     ))->save();
 
-    entity_get_form_display($entity_type, $entity_type, 'default')
+    entity_get_form_display($entity_type_id, $entity_type_id, 'default')
       ->setComponent('field_test_text', array('type' => 'text_text'))
       ->save();
   }
 }
 
 /**
- * Implements hook_schema().
+ * Implements hook_uninstall().
  */
-function entity_test_schema() {
-  // Schema for simple entity.
-  $schema['entity_test'] = array(
-    'description' => 'Stores entity_test items.',
-    'fields' => array(
-      'id' => array(
-        'type' => 'serial',
-        'not null' => TRUE,
-        'description' => 'Primary Key: Unique entity-test item ID.',
-      ),
-      'uuid' => array(
-        'description' => 'Unique Key: Universally unique identifier for this entity.',
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => FALSE,
-      ),
-      'type' => array(
-        'description' => 'The bundle of the test entity.',
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'langcode' => array(
-        'description' => 'The {language}.langcode of the original variant of this test entity.',
-        'type' => 'varchar',
-        'length' => 12,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'name' => array(
-        'description' => 'The name of the test entity.',
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'user_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => FALSE,
-        'default' => NULL,
-        'description' => 'The {users}.uid of the associated user.',
-      ),
-    ),
-    'primary key' => array('id'),
-    'unique keys' => array(
-      'uuid' => array('uuid'),
-    ),
-  );
-
-  // Schema for entity with revisions.
-  $schema['entity_test_rev'] = array(
-    'description' => 'Stores entity_test_rev items.',
-    'fields' => array(
-      'id' => array(
-        'type' => 'serial',
-        'not null' => TRUE,
-        'description' => 'Primary Key: Unique entity-test item ID.',
-      ),
-      'revision_id' => array(
-        'description' => 'The current {entity_test_rev_property_revision}.revision_id version identifier.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'uuid' => array(
-        'description' => 'Unique Key: Universally unique identifier for this entity.',
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => FALSE,
-      ),
-      'type' => array(
-        'description' => 'The bundle of the test entity.',
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'name' => array(
-        'description' => 'The name of the test entity.',
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'user_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => FALSE,
-        'default' => NULL,
-        'description' => 'The {users}.uid of the associated user.',
-      ),
-    ),
-    'primary key' => array('id'),
-    'unique keys' => array(
-      'uuid' => array('uuid'),
-    ),
-  );
-  $schema['entity_test_rev_revision'] = array(
-    'description' => 'Stores entity_test_rev item property revisions.',
-    'fields' => array(
-      'id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'description' => 'The {entity_test_rev}.id of the test entity.',
-      ),
-      'revision_id' => array(
-        'type' => 'serial',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'description' => 'The primary identifier for this version.',
-      ),
-      'langcode' => array(
-        'description' => 'The {language}.langcode of this variant of this test entity.',
-        'type' => 'varchar',
-        'length' => 12,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'name' => array(
-        'description' => 'The name of the test entity.',
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'user_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => FALSE,
-        'default' => NULL,
-        'description' => 'The {users}.uid of the associated user.',
-      ),
-    ),
-    'indexes' => array(
-      'user_id' => array('user_id'),
-    ),
-    'foreign keys' => array(
-      'user_id' => array('users' => 'uid'),
-      'id' => array('entity_test_rev' => 'id'),
-    ),
-    'primary key' => array('revision_id'),
-  );
-
-  // Schema for entity with data table.
-  $schema['entity_test_mul'] = array(
-    'description' => 'Stores entity_test_mul items.',
-    'fields' => array(
-      'id' => array(
-        'type' => 'serial',
-        'not null' => TRUE,
-        'description' => 'Primary Key: Unique entity-test item ID.',
-      ),
-      'uuid' => array(
-        'description' => 'Unique Key: Universally unique identifier for this entity.',
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => FALSE,
-      ),
-      'type' => array(
-        'description' => 'The bundle of the test entity.',
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'langcode' => array(
-        'description' => 'The {language}.langcode of the original variant of this test entity.',
-        'type' => 'varchar',
-        'length' => 12,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-    ),
-    'primary key' => array('id'),
-    'unique keys' => array(
-      'uuid' => array('uuid'),
-    ),
-  );
-  $schema['entity_test_mul_property_data'] = array(
-    'description' => 'Stores entity_test_mul item properties.',
-    'fields' => array(
-      'id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'description' => 'The {entity_test_mul}.id of the test entity.',
-      ),
-      'langcode' => array(
-        'description' => 'The {language}.langcode of this variant of this test entity.',
-        'type' => 'varchar',
-        'length' => 12,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'default_langcode' => array(
-        'description' => 'Boolean indicating whether the current variant is in the original entity language.',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 1,
-      ),
-      'name' => array(
-        'description' => 'The name of the test entity.',
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => FALSE,
-      ),
-      'user_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => FALSE,
-        'default' => NULL,
-        'description' => 'The {users}.uid of the associated user.',
-      ),
-    ),
-    'indexes' => array(
-      'user_id' => array('user_id'),
-    ),
-    'foreign keys' => array(
-      'user_id' => array('users' => 'uid'),
-      'id' => array('entity_test_mul' => 'id'),
-    ),
-    'primary key' => array('id', 'langcode'),
-  );
+function entity_test_uninstall() {
+  $entity_manager = \Drupal::entityManager();
 
-  // Schema for entity with data table and revisions.
-  $schema['entity_test_mulrev'] = array(
-    'description' => 'Stores entity_test_mulrev items.',
-    'fields' => array(
-      'id' => array(
-        'type' => 'serial',
-        'not null' => TRUE,
-        'description' => 'Primary Key: Unique entity-test item ID.',
-      ),
-      'revision_id' => array(
-        'description' => 'The current {entity_test_mulrev_property_revision}.revision_id version identifier.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'uuid' => array(
-        'description' => 'Unique Key: Universally unique identifier for this entity.',
-        'type' => 'varchar',
-        'length' => 128,
-        'not null' => FALSE,
-      ),
-      'type' => array(
-        'description' => 'The bundle of the test entity.',
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-    ),
-    'primary key' => array('id'),
-    'unique keys' => array(
-      'uuid' => array('uuid'),
-    ),
-  );
-  $schema['entity_test_mulrev_revision'] = array(
-    'description' => 'Stores entity_test_rev item property revisions.',
-    'fields' => array(
-      'id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'description' => 'The {entity_test_rev}.id of the test entity.',
-      ),
-      'revision_id' => array(
-        'type' => 'serial',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'description' => 'The primary identifier for this version.',
-      ),
-      'langcode' => array(
-        'description' => 'The {language}.langcode of this variant of this test entity.',
-        'type' => 'varchar',
-        'length' => 12,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-    ),
-    'foreign keys' => array(
-      'id' => array('entity_test_rev' => 'id'),
-    ),
-    'primary key' => array('revision_id'),
-  );
-  $schema['entity_test_mulrev_property_data'] = array(
-    'description' => 'Stores entity_test_mulrev item properties.',
-    'fields' => array(
-      'id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'description' => 'The {entity_test_mulrev}.id of the test entity.',
-      ),
-      'revision_id' => array(
-        'description' => 'The current {entity_test_mulrev_property_revision}.revision_id version identifier.',
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'default' => 0,
-      ),
-      'langcode' => array(
-        'description' => 'The {language}.langcode of this variant of this test entity.',
-        'type' => 'varchar',
-        'length' => 12,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'default_langcode' => array(
-        'description' => 'Boolean indicating whether the current variant is in the original entity language.',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 1,
-      ),
-      'name' => array(
-        'description' => 'The name of the test entity.',
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'user_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => FALSE,
-        'default' => NULL,
-        'description' => 'The {users}.uid of the associated user.',
-      ),
-    ),
-    'indexes' => array(
-      'user_id' => array('user_id'),
-    ),
-    'foreign keys' => array(
-      'user_id' => array('users' => 'uid'),
-      'id' => array('entity_test_mulrev' => 'id'),
-    ),
-    'primary key' => array('id', 'langcode'),
-  );
-  $schema['entity_test_mulrev_property_revision'] = array(
-    'description' => 'Stores entity_test_mulrev item property revisions.',
-    'fields' => array(
-      'id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'description' => 'The {entity_test_mulrev}.id of the test entity.',
-      ),
-      'revision_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-        'description' => 'The primary identifier for this version.',
-      ),
-      'langcode' => array(
-        'description' => 'The {language}.langcode of this variant of this test entity.',
-        'type' => 'varchar',
-        'length' => 12,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'default_langcode' => array(
-        'description' => 'Boolean indicating whether the current variant is in the original entity language.',
-        'type' => 'int',
-        'not null' => TRUE,
-        'default' => 1,
-      ),
-      'name' => array(
-        'description' => 'The name of the test entity.',
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-      ),
-      'user_id' => array(
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => FALSE,
-        'default' => NULL,
-        'description' => 'The {users}.uid of the associated user.',
-      ),
-    ),
-    'indexes' => array(
-      'user_id' => array('user_id'),
-    ),
-    'foreign keys' => array(
-      'user_id' => array('users' => 'uid'),
-      'id' => array('entity_test_mulrev' => 'id'),
-    ),
-    'primary key' => array('revision_id', 'langcode'),
+  $entity_type_ids = array(
+    'entity_test',
+    'entity_test_rev',
+    'entity_test_mul',
+    'entity_test_mulrev',
   );
+  foreach ($entity_type_ids as $entity_type_id) {
+    $storage_controller = $entity_manager->getStorageController($entity_type_id);
 
-  return $schema;
+    // Install schema.
+    $storage_controller->deleteSchema();
+  }
 }
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php
index 72acdf2..88330b9 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTest.php
@@ -133,7 +133,8 @@ public static function baseFieldDefinitions($entity_type) {
     $fields['type'] = FieldDefinition::create('string')
       ->setLabel(t('Type'))
       ->setDescription(t('The bundle of the test entity.'))
-      ->setRequired(TRUE);
+      ->setRequired(TRUE)
+      ->setSetting('max_length', 32);
 
     $fields['user_id'] = FieldDefinition::create('entity_reference')
       ->setLabel(t('User ID'))
diff --git a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestLabelCallback.php b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestLabelCallback.php
index 481711f..e881b9a 100644
--- a/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestLabelCallback.php
+++ b/core/modules/system/tests/modules/entity_test/lib/Drupal/entity_test/Entity/EntityTestLabelCallback.php
@@ -15,7 +15,6 @@
  *   label = @Translation("Entity test label callback"),
  *   field_cache = FALSE,
  *   base_table = "entity_test",
- *   revision_table = "entity_test_revision",
  *   label_callback = "entity_test_label_callback",
  *   fieldable = TRUE,
  *   entity_keys = {
diff --git a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php
index e366b85..3c4198f 100644
--- a/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php
+++ b/core/modules/taxonomy/lib/Drupal/taxonomy/Entity/Term.php
@@ -240,7 +240,10 @@ public static function baseFieldDefinitions($entity_type) {
       ->setDescription(t('The parents of this term.'))
       // Save new terms with no parents by default.
       ->setSetting('default_value', 0)
-      ->setConstraints(array('TermParent' => array()));
+      ->setConstraints(array('TermParent' => array()))
+      // The parent of a taxonomy term is not computed from other fields but is
+      // stored separately in the {taxonomy_term_hierarchy] table.
+      ->setComputed(TRUE);
 
     $fields['changed'] = FieldDefinition::create('integer')
       ->setLabel(t('Changed'))
diff --git a/core/modules/user/lib/Drupal/user/Entity/User.php b/core/modules/user/lib/Drupal/user/Entity/User.php
index 44bce77..02a0bce 100644
--- a/core/modules/user/lib/Drupal/user/Entity/User.php
+++ b/core/modules/user/lib/Drupal/user/Entity/User.php
@@ -513,7 +513,10 @@ public static function baseFieldDefinitions($entity_type) {
     // https://drupal.org/node/2044859.
     $fields['roles'] = FieldDefinition::create('string')
       ->setLabel(t('Roles'))
-      ->setDescription(t('The roles the user has.'));
+      ->setDescription(t('The roles the user has.'))
+      // The roles of a user are not computed from other fields but are stored
+      // separately in the {users_roles} table.
+      ->setComputed(TRUE);
 
     return $fields;
   }
