diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index db35fd7..eafa0ee 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -1665,12 +1665,6 @@ function install_profile_themes(&$install_state) {
  *   An array of information about the current installation state.
  */
 function install_install_profile(&$install_state) {
-  // Now that all modules are installed, make sure the entity storage and other
-  // handlers are up to date with the current entity and field definitions. For
-  // example, Path module adds a base field to nodes and taxonomy terms after
-  // those modules are already installed.
-  \Drupal::service('entity.definition_update_manager')->applyUpdates();
-
   \Drupal::service('module_installer')->install(array(drupal_get_profile()), FALSE);
   // Install all available optional config. During installation the module order
   // is determined by dependencies. If there are no dependencies between modules
diff --git a/core/includes/update.inc b/core/includes/update.inc
index 34882c3..5dbbe79 100644
--- a/core/includes/update.inc
+++ b/core/includes/update.inc
@@ -10,7 +10,6 @@
 
 use Drupal\Component\Graph\Graph;
 use Drupal\Component\Utility\SafeMarkup;
-use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Utility\Error;
 
 /**
@@ -222,29 +221,6 @@ function update_do_one($module, $number, $dependency_map, &$context) {
 }
 
 /**
- * Performs entity definition updates, which can trigger schema updates.
- *
- * @param $context
- *   The batch context array.
- */
-function update_entity_definitions(&$context) {
-  try {
-    \Drupal::service('entity.definition_update_manager')->applyUpdates();
-  }
-  catch (EntityStorageException $e) {
-    watchdog_exception('update', $e);
-    $variables = Error::decodeException($e);
-    unset($variables['backtrace']);
-    // The exception message is run through
-    // \Drupal\Component\Utility\SafeMarkup::checkPlain() by
-    // \Drupal\Core\Utility\Error::decodeException().
-    $ret['#abort'] = array('success' => FALSE, 'query' => t('%type: !message in %function (line %line of %file).', $variables));
-    $context['results']['core']['update_entity_definitions'] = $ret;
-    $context['results']['#abort'][] = 'update_entity_definitions';
-  }
-}
-
-/**
  * Returns a list of all the pending database updates.
  *
  * @return
diff --git a/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php b/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php
index 902aab9..d44b742 100644
--- a/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php
+++ b/core/lib/Drupal/Core/Entity/EntityDefinitionUpdateManager.php
@@ -95,13 +95,13 @@ public function getChangeSummary() {
    * {@inheritdoc}
    */
   public function applyUpdates() {
-    $change_list = $this->getChangeList();
-    if ($change_list) {
+    $complete_change_list = $this->getChangeList();
+    if ($complete_change_list) {
       // self::getChangeList() only disables the cache and does not invalidate.
       // In case there are changes, explicitly invalidate caches.
       $this->entityManager->clearCachedDefinitions();
     }
-    foreach ($change_list as $entity_type_id => $change_list) {
+    foreach ($complete_change_list as $entity_type_id => $change_list) {
       // Process entity type definition changes before storage definitions ones
       // this is necessary when you change an entity type from non-revisionable
       // to revisionable and at the same time add revisionable fields to the
diff --git a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php
index b701279..472ed20 100644
--- a/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php
+++ b/core/lib/Drupal/Core/Entity/Sql/DefaultTableMapping.php
@@ -189,9 +189,10 @@ public function getFieldTableName($field_name) {
   public function getColumnNames($field_name) {
     if (!isset($this->columnMapping[$field_name])) {
       $this->columnMapping[$field_name] = array();
-      $storage_definition = $this->fieldStorageDefinitions[$field_name];
-      foreach (array_keys($this->fieldStorageDefinitions[$field_name]->getColumns()) as $property_name) {
-        $this->columnMapping[$field_name][$property_name] = $this->getFieldColumnName($storage_definition, $property_name);
+      if (isset($this->fieldStorageDefinitions[$field_name])) {
+        foreach (array_keys($this->fieldStorageDefinitions[$field_name]->getColumns()) as $property_name) {
+          $this->columnMapping[$field_name][$property_name] = $this->getFieldColumnName($this->fieldStorageDefinitions[$field_name], $property_name);
+        }
       }
     }
     return $this->columnMapping[$field_name];
diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
index 689655a..259d240 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
@@ -10,6 +10,8 @@
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Database\Database;
+use Drupal\Core\Database\DatabaseExceptionWrapper;
+use Drupal\Core\Database\SchemaException;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\ContentEntityStorageBase;
 use Drupal\Core\Entity\EntityBundleListenerInterface;
@@ -1351,7 +1353,9 @@ public function requiresFieldDataMigration(FieldStorageDefinitionInterface $stor
    * {@inheritdoc}
    */
   public function onEntityTypeCreate(EntityTypeInterface $entity_type) {
-    $this->getStorageSchema()->onEntityTypeCreate($entity_type);
+    $this->wrapSchemaException(function () use ($entity_type) {
+      $this->getStorageSchema()->onEntityTypeCreate($entity_type);
+    });
   }
 
   /**
@@ -1364,14 +1368,18 @@ public function onEntityTypeUpdate(EntityTypeInterface $entity_type, EntityTypeI
     // definition.
     $this->initTableLayout();
     // Let the schema handler adapt to possible table layout changes.
-    $this->getStorageSchema()->onEntityTypeUpdate($entity_type, $original);
+    $this->wrapSchemaException(function () use ($entity_type, $original) {
+      $this->getStorageSchema()->onEntityTypeUpdate($entity_type, $original);
+    });
   }
 
   /**
    * {@inheritdoc}
    */
   public function onEntityTypeDelete(EntityTypeInterface $entity_type) {
-    $this->getStorageSchema()->onEntityTypeDelete($entity_type);
+    $this->wrapSchemaException(function () use ($entity_type) {
+      $this->getStorageSchema()->onEntityTypeDelete($entity_type);
+    });
   }
 
   /**
@@ -1386,14 +1394,18 @@ public function onFieldStorageDefinitionCreate(FieldStorageDefinitionInterface $
     if ($this->getTableMapping()->allowsSharedTableStorage($storage_definition)) {
       $this->tableMapping = NULL;
     }
-    $this->getStorageSchema()->onFieldStorageDefinitionCreate($storage_definition);
+    $this->wrapSchemaException(function () use ($storage_definition) {
+      $this->getStorageSchema()->onFieldStorageDefinitionCreate($storage_definition);
+    });
   }
 
   /**
    * {@inheritdoc}
    */
   public function onFieldStorageDefinitionUpdate(FieldStorageDefinitionInterface $storage_definition, FieldStorageDefinitionInterface $original) {
-    $this->getStorageSchema()->onFieldStorageDefinitionUpdate($storage_definition, $original);
+    $this->wrapSchemaException(function () use ($storage_definition, $original) {
+      $this->getStorageSchema()->onFieldStorageDefinitionUpdate($storage_definition, $original);
+    });
   }
 
   /**
@@ -1421,7 +1433,31 @@ public function onFieldStorageDefinitionDelete(FieldStorageDefinitionInterface $
     }
 
     // Update the field schema.
-    $this->getStorageSchema()->onFieldStorageDefinitionDelete($storage_definition);
+    $this->wrapSchemaException(function () use ($storage_definition) {
+      $this->getStorageSchema()->onFieldStorageDefinitionDelete($storage_definition);
+    });
+  }
+
+  /**
+   * Wraps a database schema exception into an entity storage exception.
+   *
+   * @param callable $callback
+   *   The callback to be executed.
+   *
+   * @throws \Drupal\Core\Entity\EntityStorageException
+   *   When a database schema exception is thrown.
+   */
+  protected function wrapSchemaException(callable $callback) {
+    $message = 'Exception thrown while performing a schema update.';
+    try {
+      $callback();
+    }
+    catch (SchemaException $e) {
+      throw new EntityStorageException($message, 0, $e);
+    }
+    catch (DatabaseExceptionWrapper $e) {
+      throw new EntityStorageException($message, 0, $e);
+    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
index b92636c..d0acdd0 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Core\Entity\Sql;
 
+use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Database\DatabaseException;
 use Drupal\Core\Entity\ContentEntityTypeInterface;
@@ -1391,6 +1392,20 @@ protected function updateSharedTableSchema(FieldStorageDefinitionInterface $stor
           if ($field_name == $updated_field_name) {
             $schema[$table_name] = $this->getSharedTableFieldSchema($storage_definition, $table_name, $column_names);
 
+            // Handle NOT NULL constraints.
+            foreach ($schema[$table_name]['fields'] as $column_name => $specifier) {
+              $not_null = !empty($specifier['not null']);
+              $original_not_null = !empty($original_schema[$table_name]['fields'][$column_name]['not null']);
+              if ($not_null !== $original_not_null) {
+                if ($not_null && $this->hasNullFieldPropertyData($table_name, $column_name)) {
+                  throw new EntityStorageException(SafeMarkup::format('The "@name" column cannot have NOT NULL constraints as it holds NULL values.', ['@name' => $column_name]));
+                }
+                $column_schema = $original_schema[$table_name]['fields'][$column_name];
+                $column_schema['not null'] = $not_null;
+                $schema_handler->changeField($table_name, $field_name, $field_name, $column_schema);
+              }
+            }
+
             // Drop original indexes and unique keys.
             if (!empty($original_schema[$table_name]['indexes'])) {
               foreach ($original_schema[$table_name]['indexes'] as $name => $specifier) {
@@ -1423,6 +1438,26 @@ protected function updateSharedTableSchema(FieldStorageDefinitionInterface $stor
   }
 
   /**
+   * Checks whether a field property has NULL values.
+   *
+   * @param $table_name
+   *   The name of the table to inspect.
+   * @param $column_name
+   *   The name of the column holding the field property data.
+   *
+   * @return bool
+   *   TRUE if NULL data is found, FALSE otherwise.
+   */
+  protected function hasNullFieldPropertyData($table_name, $column_name) {
+    $query = $this->database->select($table_name, 't')
+      ->fields('t', [$column_name])
+      ->range(0, 1);
+    $query->isNull('t.' . $column_name);
+    $result = $query->execute()->fetchAssoc();
+    return (bool) $result;
+  }
+
+  /**
    * Gets the schema for a single field definition.
    *
    * Entity types may override this method in order to optimize the generated
@@ -1803,10 +1838,15 @@ protected function hasColumnChanges(FieldStorageDefinitionInterface $storage_def
     }
 
     if (!$storage_definition->hasCustomStorage()) {
-      $schema = $this->getSchemaFromStorageDefinition($storage_definition);
-      foreach ($this->loadFieldSchemaData($original) as $table => $spec) {
-        if ($spec['fields'] != $schema[$table]['fields']) {
-          return TRUE;
+      $keys = array_flip($this->getColumnSchemaRelevantKeys());
+      $definition_schema = $this->getSchemaFromStorageDefinition($storage_definition);
+      foreach ($this->loadFieldSchemaData($original) as $table => $table_schema) {
+        foreach ($table_schema['fields'] as $name => $spec) {
+          $definition_spec = array_intersect_key($definition_schema[$table]['fields'][$name], $keys);
+          $stored_spec = array_intersect_key($spec, $keys);
+          if ($definition_spec != $stored_spec) {
+            return TRUE;
+          }
         }
       }
     }
@@ -1814,4 +1854,18 @@ protected function hasColumnChanges(FieldStorageDefinitionInterface $storage_def
     return FALSE;
   }
 
+  /**
+   * Returns a list of column schema keys affecting data storage.
+   *
+   * When comparing schema definitions, only changes in certain properties
+   * actually affect how data is stored and thus, if applied, may imply data
+   * manipulation.
+   *
+   * @return string[]
+   *   An array of key names.
+   */
+  protected function getColumnSchemaRelevantKeys() {
+    return ['type', 'size', 'length', 'unsigned'];
+  }
+
 }
diff --git a/core/lib/Drupal/Core/Extension/ModuleInstaller.php b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
index 41e698c..30fc845 100644
--- a/core/lib/Drupal/Core/Extension/ModuleInstaller.php
+++ b/core/lib/Drupal/Core/Extension/ModuleInstaller.php
@@ -10,9 +10,8 @@
 use Drupal\Component\Serialization\Yaml;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheBackendInterface;
-use Drupal\Core\Config\PreExistingConfigException;
-use Drupal\Core\Config\StorageInterface;
 use Drupal\Core\DrupalKernelInterface;
+use Drupal\Core\Entity\EntityStorageException;
 
 /**
  * Default implementation of the module installer.
@@ -212,15 +211,34 @@ public function install(array $module_list, $enable_dependencies = TRUE) {
           $version = max(max($versions), $version);
         }
 
-        // Notify interested components that this module's entity types are new.
-        // For example, a SQL-based storage handler can use this as an
-        // opportunity to create the necessary database tables.
+        // Notify interested components that this module's entity types and
+        // field storage definitions are new. For example, a SQL-based storage
+        // handler can use this as an opportunity to create the necessary
+        // database tables.
         // @todo Clean this up in https://www.drupal.org/node/2350111.
         $entity_manager = \Drupal::entityManager();
         foreach ($entity_manager->getDefinitions() as $entity_type) {
           if ($entity_type->getProvider() == $module) {
             $entity_manager->onEntityTypeCreate($entity_type);
           }
+          elseif ($entity_type->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) {
+            // The module being installed may be adding new fields to existing
+            // entity types. Field definitions for any entity type defined by
+            // the module are handled in the if branch.
+            foreach ($entity_manager->getFieldStorageDefinitions($entity_type->id()) as $storage_definition) {
+              if ($storage_definition->getProvider() == $module) {
+                // If the module being installed is also defining a storage key
+                // for the entity type, the entity schema may not exist yet. It
+                // will be created later in that case.
+                try {
+                  $entity_manager->onFieldStorageDefinitionCreate($storage_definition);
+                }
+                catch (EntityStorageException $e) {
+                  watchdog_exception('system', $e, 'An error occurred while notifying the creation of the @name field storage definition: "!message" in %function (line %line of %file).', ['@name' => $storage_definition->getName()]);
+                }
+              }
+            }
+          }
         }
 
         // Install default configuration of the module.
@@ -366,6 +384,21 @@ public function uninstall(array $module_list, $uninstall_dependents = TRUE) {
         if ($entity_type->getProvider() == $module) {
           $entity_manager->onEntityTypeDelete($entity_type);
         }
+        elseif ($entity_type->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) {
+          // The module being installed may be adding new fields to existing
+          // entity types. Field definitions for any entity type defined by
+          // the module are handled in the if branch.
+          $entity_type_id = $entity_type->id();
+          /** @var \Drupal\Core\Entity\FieldableEntityStorageInterface $storage */
+          $storage = $entity_manager->getStorage($entity_type_id);
+          foreach ($entity_manager->getFieldStorageDefinitions($entity_type_id) as $storage_definition) {
+            // @todo We need to trigger field purging here.
+            //   See https://www.drupal.org/node/2282119.
+            if ($storage_definition->getProvider() == $module && !$storage->countFieldData($storage_definition, TRUE)) {
+              $entity_manager->onFieldStorageDefinitionDelete($storage_definition);
+            }
+          }
+        }
       }
 
       // Remove the schema.
diff --git a/core/modules/block_content/block_content.install b/core/modules/block_content/block_content.install
new file mode 100644
index 0000000..db536dd
--- /dev/null
+++ b/core/modules/block_content/block_content.install
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the block_content module.
+ */
+
+use Drupal\Core\Field\BaseFieldDefinition;
+
+/**
+ * Add 'revision_translation_affected' field to 'block_content' entities.
+ */
+function block_content_update_8001() {
+  $entity_manager = \Drupal::entityManager();
+  $field_storage_definitions = $entity_manager->getLastInstalledFieldStorageDefinitions('block_content');
+
+  // This field was added to
+  // \Drupal\block_content\Entity\BlockContent::baseFieldDefinitions() in
+  // https://www.drupal.org/node/2453153, but this update function wasn't
+  // added until https://www.drupal.org/node/2542748. In between, sites could
+  // have performed interim updates, which would have included automated
+  // entity schema updates prior to that being removed (see that issue for
+  // details). Therefore, we check for whether the field storage definition has
+  // already been installed.
+  if (!isset($field_storage_definitions['revision_translation_affected'])) {
+    // For sites that did not perform interim updates prior to the removal
+    // of automated entity schema updates, install the definition with the
+    // settings it had in BlockContent::baseFieldDefinitions() at the time
+    // this update function was written. If/when code is deployed that
+    // changes that definition, the corresponding module must implement an
+    // update function that invokes
+    // $entity_manager->onFieldStorageDefinitionUpdate() with the new
+    // definition.
+    $field_storage_definition = BaseFieldDefinition::create('boolean')
+      ->setLabel(t('Revision translation affected'))
+      ->setDescription(t('Indicates if the last edit of a translation belongs to current revision.'))
+      ->setReadOnly(TRUE)
+      ->setRevisionable(TRUE)
+      ->setTranslatable(TRUE)
+      // @see \Drupal\Core\Entity\EntityManager::buildBaseFieldDefinitions()
+      ->setProvider('block_content')
+      ->setName('revision_translation_affected')
+      ->setTargetEntityTypeId('block_content')
+      ->setTargetBundle(NULL);
+
+    $entity_manager->onFieldStorageDefinitionCreate($field_storage_definition);
+  }
+}
diff --git a/core/modules/node/node.install b/core/modules/node/node.install
index a7cb55e..8928e8d 100644
--- a/core/modules/node/node.install
+++ b/core/modules/node/node.install
@@ -5,9 +5,7 @@
  * Install, update and uninstall functions for the node module.
  */
 
-use Drupal\Component\Utility\SafeMarkup;
-use Drupal\Component\Uuid\Uuid;
-use Drupal\Core\Url;
+use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\user\RoleInterface;
 
 /**
@@ -153,3 +151,96 @@ function node_uninstall() {
   // Delete remaining general module variables.
   \Drupal::state()->delete('node.node_access_needs_rebuild');
 }
+
+/**
+ * Add 'revision_translation_affected' field to 'node' entities.
+ */
+function node_update_8001() {
+  $entity_manager = \Drupal::entityManager();
+  $field_storage_definitions = $entity_manager->getLastInstalledFieldStorageDefinitions('node');
+
+  // This field was added to
+  // \Drupal\node\Entity\Node::baseFieldDefinitions() in
+  // https://www.drupal.org/node/2453153, but this update function wasn't
+  // added until https://www.drupal.org/node/2542748. In between, sites could
+  // have performed interim updates, which would have included automated
+  // entity schema updates prior to that being removed (see that issue for
+  // details). Therefore, we check for whether the field storage definition has
+  // already been installed.
+  if (!isset($field_storage_definitions['revision_translation_affected'])) {
+    // For sites that did not perform interim updates prior to the removal
+    // of automated entity schema updates, install the definition with the
+    // settings it had in Node::baseFieldDefinitions() at the time this update
+    // function was written. If/when code is deployed that changes that
+    // definition, the corresponding module must implement an update function
+    // that invokes $entity_manager->onFieldStorageDefinitionUpdate() with the
+    // new definition.
+    $field_storage_definition = BaseFieldDefinition::create('boolean')
+      ->setLabel(t('Revision translation affected'))
+      ->setDescription(t('Indicates if the last edit of a translation belongs to current revision.'))
+      ->setReadOnly(TRUE)
+      ->setRevisionable(TRUE)
+      ->setTranslatable(TRUE)
+      // @see \Drupal\Core\Entity\EntityManager::buildBaseFieldDefinitions()
+      ->setProvider('node')
+      ->setName('revision_translation_affected')
+      ->setTargetEntityTypeId('node')
+      ->setTargetBundle(NULL);
+
+    $entity_manager->onFieldStorageDefinitionCreate($field_storage_definition);
+  }
+}
+
+/**
+ * Remove obsolete indexes from the node schema.
+ */
+function node_update_8002() {
+  // The "node__default_langcode" and "node_field__langcode" indexes were
+  // removed from \Drupal\node\NodeStorageSchema in
+  // https://www.drupal.org/node/2261669, but this update function wasn't
+  // added until https://www.drupal.org/node/2542748. Regenerate the related
+  // schemas to ensure they match the currently expected status.
+  $entity_manager = \Drupal::entityManager();
+  // Regenerate entity type indexes, this should drop "node__default_langcode".
+  $entity_type = $entity_manager->getLastInstalledDefinition('node');
+  $entity_manager->onEntityTypeUpdate($entity_type, $entity_type);
+  // Regenerate "langcode" indexes, this should drop "node_field__langcode".
+  $storage_definitions = $entity_manager->getLastInstalledFieldStorageDefinitions('node');
+  $storage_definition = $storage_definitions['langcode'];
+  $entity_manager->onFieldStorageDefinitionUpdate($storage_definition, $storage_definition);
+}
+
+/**
+ * Promote 'status' and 'uid' fields to entity keys.
+ */
+function node_update_8003() {
+  $entity_manager = \Drupal::entityManager();
+  $old_entity_type = $entity_manager->getLastInstalledDefinition('node');
+  $entity_keys = $old_entity_type->getKeys();
+
+  // The 'status' and 'uid' fields were added to the 'entity_keys' annotation
+  // of \Drupal\node\Entity\Node in https://www.drupal.org/node/2498919, but
+  // this update function wasn't added until
+  // https://www.drupal.org/node/2542748. In between, sites could have
+  // performed interim updates, which would have included automated entity
+  // schema updates prior to that being removed (see that issue for details).
+  // Therefore, we check for whether the keys have already been installed.
+  $entity_keys['status'] = 'status';
+  $entity_keys['uid'] = 'uid';
+  $new_entity_type = clone $old_entity_type;
+  $new_entity_type->set('entity_keys', $entity_keys);
+  $entity_manager->onEntityTypeUpdate($new_entity_type, $old_entity_type);
+
+  // @todo The above should be enough, since that is the only definition that
+  //   changed. But \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema varies
+  //   field schema by whether a field is an entity key, so invoke
+  //   onFieldStorageDefinitionUpdate() with an unmodified
+  //   $field_storage_definition to trigger the necessary changes.
+  //   SqlContentEntityStorageSchema::onEntityTypeUpdate() should be fixed to
+  //   automatically handle this.
+  $field_storage_definitions = $entity_manager->getLastInstalledFieldStorageDefinitions('node');
+  foreach (array('status', 'uid') as $field_name) {
+    $field_storage_definition = $field_storage_definitions[$field_name];
+    $entity_manager->onFieldStorageDefinitionUpdate($field_storage_definition, $field_storage_definition);
+  }
+}
diff --git a/core/modules/system/src/Controller/DbUpdateController.php b/core/modules/system/src/Controller/DbUpdateController.php
index aa46654..28ab72b 100644
--- a/core/modules/system/src/Controller/DbUpdateController.php
+++ b/core/modules/system/src/Controller/DbUpdateController.php
@@ -9,7 +9,6 @@
 
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Controller\ControllerBase;
-use Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\KeyValueStore\KeyValueExpirableFactoryInterface;
 use Drupal\Core\Render\BareHtmlPageRendererInterface;
@@ -62,13 +61,6 @@ class DbUpdateController extends ControllerBase {
   protected $account;
 
   /**
-   * The entity definition update manager.
-   *
-   * @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
-   */
-  protected $entityDefinitionUpdateManager;
-
-  /**
    * The bare HTML page renderer.
    *
    * @var \Drupal\Core\Render\BareHtmlPageRendererInterface
@@ -97,19 +89,16 @@ class DbUpdateController extends ControllerBase {
    *   The module handler.
    * @param \Drupal\Core\Session\AccountInterface $account
    *   The current user.
-   * @param \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface $entity_definition_update_manager
-   *   The entity definition update manager.
    * @param \Drupal\Core\Render\BareHtmlPageRendererInterface $bare_html_page_renderer
    *   The bare HTML page renderer.
    */
-  public function __construct($root, KeyValueExpirableFactoryInterface $key_value_expirable_factory, CacheBackendInterface $cache, StateInterface $state, ModuleHandlerInterface $module_handler, AccountInterface $account, EntityDefinitionUpdateManagerInterface $entity_definition_update_manager, BareHtmlPageRendererInterface $bare_html_page_renderer) {
+  public function __construct($root, KeyValueExpirableFactoryInterface $key_value_expirable_factory, CacheBackendInterface $cache, StateInterface $state, ModuleHandlerInterface $module_handler, AccountInterface $account, BareHtmlPageRendererInterface $bare_html_page_renderer) {
     $this->root = $root;
     $this->keyValueExpirableFactory = $key_value_expirable_factory;
     $this->cache = $cache;
     $this->state = $state;
     $this->moduleHandler = $module_handler;
     $this->account = $account;
-    $this->entityDefinitionUpdateManager = $entity_definition_update_manager;
     $this->bareHtmlPageRenderer = $bare_html_page_renderer;
   }
 
@@ -124,7 +113,6 @@ public static function create(ContainerInterface $container) {
       $container->get('state'),
       $container->get('module_handler'),
       $container->get('current_user'),
-      $container->get('entity.definition_update_manager'),
       $container->get('bare_html_page_renderer')
     );
   }
@@ -325,23 +313,6 @@ protected function selection(Request $request) {
       drupal_set_message($this->t('Some of the pending updates cannot be applied because their dependencies were not met.'), 'warning');
     }
 
-    // If there are entity definition updates, display their summary.
-    if ($this->entityDefinitionUpdateManager->needsUpdates()) {
-      $entity_build = array();
-      $summary = $this->entityDefinitionUpdateManager->getChangeSummary();
-      foreach ($summary as $entity_type_id => $items) {
-        $entity_update_key = 'entity_type_updates_' . $entity_type_id;
-        $entity_build[$entity_update_key] = array(
-          '#theme' => 'item_list',
-          '#items' => $items,
-          '#title' => $entity_type_id . ' entity type',
-        );
-        $count++;
-      }
-      // Display these above the module updates, since they will be run first.
-      $build['start'] = $entity_build + $build['start'];
-    }
-
     if (empty($count)) {
       drupal_set_message($this->t('No pending updates.'));
       unset($build);
@@ -600,16 +571,6 @@ protected function triggerBatch(Request $request) {
       }
     }
 
-    // Lastly, perform entity definition updates, which will update storage
-    // schema if needed. If module update functions need to work with specific
-    // entity schema they should call the entity update service for the specific
-    // update themselves.
-    // @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::applyEntityUpdate()
-    // @see \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface::applyFieldUpdate()
-    if ($this->entityDefinitionUpdateManager->needsUpdates()) {
-      $operations[] = array('update_entity_definitions', array());
-    }
-
     $batch['operations'] = $operations;
     $batch += array(
       'title' => $this->t('Updating'),
diff --git a/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php b/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php
index e5c8f4b..a9f2b73 100644
--- a/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php
+++ b/core/modules/system/src/Tests/Entity/EntityDefinitionTestTrait.php
@@ -126,6 +126,17 @@ protected function modifyBaseField() {
   }
 
   /**
+   * Promotes a field to an entity key.
+   */
+  protected function makeBaseFieldEntityKey() {
+    $entity_type = clone $this->entityManager->getDefinition('entity_test_update');
+    $entity_keys = $entity_type->getKeys();
+    $entity_keys['new_base_field'] = 'new_base_field';
+    $entity_type->set('entity_keys', $entity_keys);
+    $this->state->set('entity_test_update.entity_type', $entity_type);
+  }
+
+  /**
    * Removes the new base field from the 'entity_test_update' entity type.
    */
   protected function removeBaseField() {
diff --git a/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php b/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php
index 8ae7fa5..91df516 100644
--- a/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php
+++ b/core/modules/system/src/Tests/Entity/EntityDefinitionUpdateTest.php
@@ -14,10 +14,8 @@
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Entity\EntityTypeEvents;
 use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
-use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Field\FieldStorageDefinitionEvents;
 use Drupal\Core\Language\LanguageInterface;
-use Drupal\entity_test\FieldStorageDefinition;
 
 /**
  * Tests EntityDefinitionUpdateManager functionality.
@@ -671,4 +669,52 @@ public function testCreateIndexUsingEntityStorageSchemaWithData() {
     }
   }
 
+  /**
+   * Tests updating a base field when it has existing data.
+   */
+  public function testBaseFieldEntityKeyUpdateWithExistingData() {
+    // Add the base field and run the update.
+    $this->addBaseField();
+    $this->entityDefinitionUpdateManager->applyUpdates();
+
+    // Save an entity with the base field populated.
+    $this->entityManager->getStorage('entity_test_update')->create(['new_base_field' => $this->randomString()])->save();
+
+    // Save an entity with the base field not populated.
+    /** @var \Drupal\entity_test\Entity\EntityTestUpdate $entity */
+    $entity = $this->entityManager->getStorage('entity_test_update')->create();
+    $entity->save();
+
+    // Promote the base field to an entity key. This will trigger the addition
+    // of a NOT NULL constraint.
+    $this->makeBaseFieldEntityKey();
+
+    // Try to apply the update and verify they fail since we have a NULL value.
+    $message = 'An error occurs when trying to enabling NOT NULL constraints with NULL data.';
+    try {
+      $this->entityDefinitionUpdateManager->applyUpdates();
+      $this->fail($message);
+    }
+    catch (EntityStorageException $e) {
+      $this->pass($message);
+    }
+
+    // Check that the update is correctly applied when no NULL data is left.
+    $entity->set('new_base_field', $this->randomString());
+    $entity->save();
+    $this->entityDefinitionUpdateManager->applyUpdates();
+    $this->pass('The update is correctly performed when no NULL data exists.');
+
+    // Check that the update actually applied a NOT NULL constraint.
+    $entity->set('new_base_field', NULL);
+    $message = 'The NOT NULL constraint was correctly applied.';
+    try {
+      $entity->save();
+      $this->fail($message);
+    }
+    catch (EntityStorageException $e) {
+      $this->pass($message);
+    }
+  }
+
 }
diff --git a/core/modules/system/src/Tests/Entity/Update/SqlContentEntityStorageSchemaIndexTest.php b/core/modules/system/src/Tests/Entity/Update/SqlContentEntityStorageSchemaIndexTest.php
index 0e613f1..3481b55 100644
--- a/core/modules/system/src/Tests/Entity/Update/SqlContentEntityStorageSchemaIndexTest.php
+++ b/core/modules/system/src/Tests/Entity/Update/SqlContentEntityStorageSchemaIndexTest.php
@@ -19,11 +19,6 @@ class SqlContentEntityStorageSchemaIndexTest extends UpdatePathTestBase {
   /**
    * {@inheritdoc}
    */
-  protected static $modules = ['update_order_test'];
-
-  /**
-   * {@inheritdoc}
-   */
   public function setUp() {
     $this->databaseDumpFiles = [
       __DIR__ . '/../../../../tests/fixtures/update/drupal-8.bare.standard.php.gz',
@@ -35,9 +30,6 @@ public function setUp() {
    * Tests entity and field schema database updates and execution order.
    */
   public function testIndex() {
-    // Enable the hook implementations in the update_order_test module.
-    \Drupal::state()->set('update_order_test', TRUE);
-
     // The initial Drupal 8 database dump before any updates does not include
     // the entity ID in the entity field data table indices that were added in
     // https://www.drupal.org/node/2261669.
@@ -45,38 +37,12 @@ public function testIndex() {
     $this->assertFalse(db_index_exists('node_field_data', 'node__id__default_langcode__langcode'), 'Index node__id__default_langcode__langcode does not exist prior to running updates.');
     $this->assertFalse(db_index_exists('users_field_data', 'user__id__default_langcode__langcode'), 'Index users__id__default_langcode__langcode does not exist prior to running updates.');
 
-    // Running database updates should automatically update the entity schemata
-    // to add the indices from https://www.drupal.org/node/2261669.
+    // Running database updates should update the entity schemata to add the
+    // indices from https://www.drupal.org/node/2261669.
     $this->runUpdates();
     $this->assertFalse(db_index_exists('node_field_data', 'node__default_langcode'), 'Index node__default_langcode properly removed.');
     $this->assertTrue(db_index_exists('node_field_data', 'node__id__default_langcode__langcode'), 'Index node__id__default_langcode__langcode properly created on the node_field_data table.');
     $this->assertTrue(db_index_exists('users_field_data', 'user__id__default_langcode__langcode'), 'Index users__id__default_langcode__langcode properly created on the user_field_data table.');
-
-    // Ensure that hook_update_N() implementations were in the expected order
-    // relative to the entity and field updates. The expected order is:
-    // 1. Initial Drupal 8.0.0-beta12 installation with no indices.
-    // 2. update_order_test_update_8001() is invoked.
-    // 3. update_order_test_update_8002() is invoked.
-    // 4. update_order_test_update_8002() explicitly applies the updates for
-    //    the update_order_test_field storage. See update_order_test.module.
-    // 5. update_order_test_update_8002() explicitly applies the updates for
-    //    the node entity type indices listed above.
-    // 6. The remaining entity schema updates are applied automatically after
-    //    all update hook implementations have run, which applies the user
-    //    index update.
-   $this->assertTrue(\Drupal::state()->get('update_order_test_update_8001', FALSE), 'Index node__default_langcode still existed during update_order_test_update_8001(), indicating that it ran before the entity type updates.');
-
-    // Node updates were run during update_order_test_update_8002().
-    $this->assertFalse(\Drupal::state()->get('update_order_test_update_8002_node__default_langcode', TRUE), 'The node__default_langcode index was removed during update_order_test_update_8002().');
-    $this->assertTrue(\Drupal::state()->get('update_order_test_update_8002_node__id__default_langcode__langcode', FALSE), 'The node__id__default_langcode__langcode index was created during update_order_test_update_8002().');
-
-    // Ensure that the base field created by update_order_test_update_8002() is
-    // created when we expect.
-    $this->assertFalse(\Drupal::state()->get('update_order_test_update_8002_update_order_test_before', TRUE), 'The update_order_test field was not been created on Node before update_order_test_update_8002().');
-    $this->assertTrue(\Drupal::state()->get('update_order_test_update_8002_update_order_test_after', FALSE), 'The update_order_test field was created on Node by update_order_test_update_8002().');
-
-    // User update were not run during update_order_test_update_8002().
-    $this->assertFalse(\Drupal::state()->get('update_order_test_update_8002_user__id__default_langcode__langcode', TRUE));
   }
 
 }
diff --git a/core/modules/system/src/Tests/Entity/Update/UpdateApiEntityDefinitionUpdateTest.php b/core/modules/system/src/Tests/Entity/Update/UpdateApiEntityDefinitionUpdateTest.php
new file mode 100644
index 0000000..80b84bd
--- /dev/null
+++ b/core/modules/system/src/Tests/Entity/Update/UpdateApiEntityDefinitionUpdateTest.php
@@ -0,0 +1,207 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Entity\Update\UpdateApiEntityDefinitionUpdateTest.
+ */
+
+namespace Drupal\system\Tests\Entity\Update;
+
+use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\simpletest\WebTestBase;
+use Drupal\system\Tests\Update\DbUpdatesTrait;
+
+/**
+ * Tests performing entity updates through the Update API.
+ *
+ * @group Entity
+ */
+class UpdateApiEntityDefinitionUpdateTest extends WebTestBase {
+
+  use DbUpdatesTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['entity_test'];
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * The entity definition update manager.
+   *
+   * @var \Drupal\Core\Entity\EntityDefinitionUpdateManagerInterface
+   */
+  protected $updatesManager;
+
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->entityManager = $this->container->get('entity.manager');
+    $this->updatesManager = $this->container->get('entity.definition_update_manager');
+
+    $admin = $this->drupalCreateUser([], FALSE, TRUE);
+    $this->drupalLogin($admin);
+  }
+
+  /**
+   * Tests that individual updates applied sequentially work as expected.
+   */
+  public function testSingleUpdates() {
+    // Create a test entity.
+    $user_ids = [mt_rand(), mt_rand()];
+    $entity = EntityTest::create(['name' => $this->randomString(),  'user_id' => $user_ids]);
+    $entity->save();
+
+    // Check that only a single value is stored for 'user_id'.
+    $entity = $this->reloadEntity($entity);
+    $this->assertEqual(count($entity->user_id), 1);
+    $this->assertEqual($entity->user_id->target_id, $user_ids[0]);
+
+    // Make 'user_id' multiple by running updates.
+    $this->enableUpdates('entity_test', 'entity_definition_updates', 8001);
+    $this->runUpdates();
+
+    // Check that data was correctly migrated.
+    $entity = $this->reloadEntity($entity);
+    $this->assertEqual(count($entity->user_id), 1);
+    $this->assertEqual($entity->user_id->target_id, $user_ids[0]);
+
+    // Store multiple data and check it is correctly stored.
+    $entity->user_id = $user_ids;
+    $entity->save();
+    $entity = $this->reloadEntity($entity);
+    $this->assertEqual(count($entity->user_id), 2);
+    $this->assertEqual($entity->user_id[0]->target_id, $user_ids[0]);
+    $this->assertEqual($entity->user_id[1]->target_id, $user_ids[1]);
+
+    // Make 'user_id' single again by running updates.
+    $this->enableUpdates('entity_test', 'entity_definition_updates', 8002);
+    $this->runUpdates();
+
+    // Check that data was correctly migrated/dropped.
+    $entity = $this->reloadEntity($entity);
+    $this->assertEqual(count($entity->user_id), 1);
+    $this->assertEqual($entity->user_id->target_id, $user_ids[0]);
+  }
+
+  /**
+   * Tests that multiple updates applied in bulk work as expected.
+   */
+  public function testMultipleUpdates() {
+    // Create a test entity.
+    $user_ids = [mt_rand(), mt_rand()];
+    $entity = EntityTest::create(['name' => $this->randomString(),  'user_id' => $user_ids]);
+    $entity->save();
+
+    // Check that only a single value is stored for 'user_id'.
+    $entity = $this->reloadEntity($entity);
+    $this->assertEqual(count($entity->user_id), 1);
+    $this->assertEqual($entity->user_id->target_id, $user_ids[0]);
+
+    // Make 'user_id' multiple and then single again by running updates.
+    $this->enableUpdates('entity_test', 'entity_definition_updates', 8002);
+    $this->runUpdates();
+
+    // Check that data was correctly migrated back and forth.
+    $entity = $this->reloadEntity($entity);
+    $this->assertEqual(count($entity->user_id), 1);
+    $this->assertEqual($entity->user_id->target_id, $user_ids[0]);
+
+    // Check that only a single value is stored for 'user_id' again.
+    $entity->user_id = $user_ids;
+    $entity->save();
+    $entity = $this->reloadEntity($entity);
+    $this->assertEqual(count($entity->user_id), 1);
+    $this->assertEqual($entity->user_id[0]->target_id, $user_ids[0]);
+  }
+
+  /**
+   * Tests that entity updates are correctly reported in the status report page.
+   */
+  function testStatusReport() {
+    // Make sure there are no pending updates.
+    $this->updatesManager->applyUpdates();
+
+    // Create a test entity.
+    $entity = EntityTest::create(['name' => $this->randomString(),  'user_id' => mt_rand()]);
+    $entity->save();
+
+    // Check that the status report initially displays no error.
+    $this->drupalGet('admin/reports/status');
+    $this->assertNoRaw('Out of date');
+    $this->assertNoRaw('Mismatch detected');
+
+    // Enable an entity update and check that we have a dedicated status report
+    // item.
+    $this->container->get('state')->set('entity_test.remove_name_field', TRUE);
+    $this->drupalGet('admin/reports/status');
+    $this->assertNoRaw('Out of date');
+    $this->assertRaw('Mismatch detected');
+
+    // Enable a db update and check that now the entity update status report
+    // item is no longer displayed. We assume an update function will fix the
+    // mismatch.
+    $this->enableUpdates('entity_test', 'status_report', 8001);
+    $this->drupalGet('admin/reports/status');
+    $this->assertRaw('Out of date');
+    $this->assertNoRaw('Mismatch detected');
+
+    // Run db updates and check that entity updates were not applied.
+    $this->runUpdates();
+    $this->drupalGet('admin/reports/status');
+    $this->assertNoRaw('Out of date');
+    $this->assertRaw('Mismatch detected');
+
+    // Check that en exception would be triggered when trying to apply them with
+    // existing data.
+    $message = 'Entity updates cannot run if entity data exists.';
+    try {
+      $this->updatesManager->applyUpdates();
+      $this->fail($message);
+    }
+    catch (FieldStorageDefinitionUpdateForbiddenException $e) {
+      $this->pass($message);
+    }
+
+    // Check the status report is the same after trying to apply updates.
+    $this->drupalGet('admin/reports/status');
+    $this->assertNoRaw('Out of date');
+    $this->assertRaw('Mismatch detected');
+
+    // Delete entity data, enable a new update, run updates again and check that
+    // entity updates were not applied even when no data exists.
+    $entity->delete();
+    $this->enableUpdates('entity_test', 'status_report', 8002);
+    $this->runUpdates();
+    $this->drupalGet('admin/reports/status');
+    $this->assertNoRaw('Out of date');
+    $this->assertRaw('Mismatch detected');
+  }
+
+  /**
+   * Reloads the specified entity.
+   *
+   * @param \Drupal\entity_test\Entity\EntityTest $entity
+   *   An entity object.
+   *
+   * @return \Drupal\entity_test\Entity\EntityTest
+   *   The reloaded entity object.
+   */
+  protected function reloadEntity(EntityTest $entity) {
+    $this->entityManager->useCaches(FALSE);
+    $this->entityManager->getStorage('entity_test')->resetCache([$entity->id()]);
+    return EntityTest::load($entity->id());
+  }
+
+}
diff --git a/core/modules/system/src/Tests/Update/DbUpdatesTrait.php b/core/modules/system/src/Tests/Update/DbUpdatesTrait.php
new file mode 100644
index 0000000..1a1c04c
--- /dev/null
+++ b/core/modules/system/src/Tests/Update/DbUpdatesTrait.php
@@ -0,0 +1,55 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Update\DbUpdatesTrait.
+ */
+
+namespace Drupal\system\Tests\Update;
+
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Url;
+
+/**
+ * Provides methods to conditionally enable db update functions and run updates.
+ */
+trait DbUpdatesTrait {
+
+  use StringTranslationTrait;
+
+  /**
+   * Enables db updates until the specified index.
+   *
+   * @param string $module
+   *   The name of the module defining the update functions.
+   * @param $index
+   *   The index of the last update function to run.
+   */
+  protected function enableUpdates($module, $key, $index) {
+    $this->container->get('state')->set($module . '.db_updates.' . $key, $index);
+  }
+
+  /**
+   * Runs DB updates.
+   */
+  protected function runUpdates() {
+    $this->drupalGet(Url::fromRoute('system.db_update'));
+    $this->clickLink($this->t('Continue'));
+    $this->clickLink($this->t('Apply pending updates'));
+  }
+
+  /**
+   * Conditionally load Update API functions for the specified group.
+   *
+   * @param string $module
+   *   The name of the module defining the update functions.
+   * @param string $group
+   *   A name identifying the group of update functions to enable.
+   */
+  public static function includeUpdates($module, $group) {
+    if ($index = \Drupal::state()->get($module . '.db_updates.' . $group)) {
+      module_load_include('inc', $module, 'update/' . $group . '_' . $index);
+    }
+  }
+
+}
diff --git a/core/modules/system/src/Tests/Update/UpdatePathTestBase.php b/core/modules/system/src/Tests/Update/UpdatePathTestBase.php
index 8aca979..9c8ce33 100644
--- a/core/modules/system/src/Tests/Update/UpdatePathTestBase.php
+++ b/core/modules/system/src/Tests/Update/UpdatePathTestBase.php
@@ -240,6 +240,9 @@ protected function runUpdates() {
       $config = $this->config($name);
       $this->assertConfigSchema($typed_config, $name, $config->get());
     }
+
+    // Ensure that the update hooks updated all entity schema.
+    $this->assertFalse(\Drupal::service('entity.definition_update_manager')->needsUpdates(), 'After all updates ran, entity schema is up to date.');
   }
 
   /**
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index f38af8a..6aedeb6 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -588,10 +588,15 @@ function system_requirements($phase) {
         }
       }
     }
+
+    // Verify that no entity updates are pending after running every DB update.
     if (!isset($requirements['update']['severity']) && \Drupal::service('entity.definition_update_manager')->needsUpdates()) {
-      $requirements['update']['severity'] = REQUIREMENT_ERROR;
-      $requirements['update']['value'] = t('Out of date');
-      $requirements['update']['description'] = t('Some modules have database schema updates to install. You should run the <a href="@update">database update script</a> immediately.', array('@update' => \Drupal::url('system.db_update')));
+      $requirements['entity_update'] = array(
+        'title' => t('Entity/field definitions'),
+        'value' => t('Mismatch detected'),
+        'severity' => REQUIREMENT_ERROR,
+        'description' => t('A mismatch in the installed entity/field definitions was detected. If new modules were installed, the problematic one(s) should be uninstalled. If new module versions were installed, the problematic one(s) should be reverted to the previous version.'),
+      );
     }
   }
 
@@ -1241,3 +1246,19 @@ function system_update_8003() {
     ]
   );
 }
+
+/**
+ * Add a (id, default_langcode, langcode) composite index to entities.
+ */
+function system_update_8004() {
+  // \Drupal\Core\Entity\Sql\SqlContentEntityStorageSchema was changed in
+  // https://www.drupal.org/node/2261669 to include a (id, default_langcode,
+  // langcode) compound index, but this update function wasn't added until
+  // https://www.drupal.org/node/2542748. Regenerate the related schemas to
+  // ensure they match the currently expected status.
+  $entity_manager = \Drupal::entityManager();
+  foreach (array_keys($entity_manager->getDefinitions()) as $entity_type_id) {
+    $entity_type = $entity_manager->getLastInstalledDefinition($entity_type_id);
+    $entity_manager->onEntityTypeUpdate($entity_type, $entity_type);
+  }
+}
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 1a6d9a2..a9ff423 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.install
+++ b/core/modules/system/tests/modules/entity_test/entity_test.install
@@ -5,6 +5,8 @@
  * Install, update and uninstall functions for the entity_test module.
  */
 
+use Drupal\system\Tests\Update\DbUpdatesTrait;
+
 /**
  * Implements hook_install().
  */
@@ -49,3 +51,6 @@ function entity_test_schema() {
   );
   return $schema;
 }
+
+DbUpdatesTrait::includeUpdates('entity_test', 'entity_definition_updates');
+DbUpdatesTrait::includeUpdates('entity_test', 'status_report');
diff --git a/core/modules/system/tests/modules/entity_test/entity_test.module b/core/modules/system/tests/modules/entity_test/entity_test.module
index 8bddd32..9847b9c 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.module
+++ b/core/modules/system/tests/modules/entity_test/entity_test.module
@@ -12,6 +12,7 @@
 use Drupal\Core\Field\BaseFieldDefinition;
 use Drupal\Core\Field\FieldDefinitionInterface;
 use Drupal\Core\Field\FieldItemListInterface;
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Entity\Entity\EntityFormDisplay;
@@ -119,14 +120,25 @@ function entity_test_entity_base_field_info(EntityTypeInterface $entity_type) {
  * Implements hook_entity_base_field_info_alter().
  */
 function entity_test_entity_base_field_info_alter(&$fields, EntityTypeInterface $entity_type) {
-  if ($entity_type->id() == 'entity_test_mulrev' && ($names = \Drupal::state()->get('entity_test.field_definitions.translatable'))) {
+  $state = \Drupal::state();
+  if ($entity_type->id() == 'entity_test_mulrev' && ($names = $state->get('entity_test.field_definitions.translatable'))) {
     foreach ($names as $name => $value) {
       $fields[$name]->setTranslatable($value);
     }
   }
-  if ($entity_type->id() == 'node' && Drupal::state()->get('entity_test.node_remove_status_field')) {
+  if ($entity_type->id() == 'node' && $state->get('entity_test.node_remove_status_field')) {
     unset($fields['status']);
   }
+  if ($entity_type->id() == 'entity_test' && $state->get('entity_test.remove_name_field')) {
+    unset($fields['name']);
+  }
+  // In 8001 we are assuming that a new definition with multiple cardinality has
+  // been deployed.
+  // @todo Remove this if we end up using state definitions at runtime.
+  //   See TODO.
+  if ($entity_type->id() == 'entity_test' && $state->get('entity_test.db_updates.entity_definition_updates') == 8001) {
+    $fields['user_id']->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
+  }
 }
 
 /**
diff --git a/core/modules/system/tests/modules/entity_test/update/entity_definition_updates_8001.inc b/core/modules/system/tests/modules/entity_test/update/entity_definition_updates_8001.inc
new file mode 100644
index 0000000..91a3f8e
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/update/entity_definition_updates_8001.inc
@@ -0,0 +1,43 @@
+<?php
+
+/**
+ * @file
+ * Defines the 8001 db update for the "entity_definition_updates" test key.
+ */
+
+use Drupal\Core\Field\FieldStorageDefinitionInterface;
+
+/**
+ * Makes the 'user_id' field multiple and migrate its data.
+ */
+function entity_test_update_8001() {
+  $connection = \Drupal::database();
+
+  // Retrieve existing entity data.
+  $user_ids = $connection->select('entity_test', 'et')
+    ->fields('et', ['id', 'user_id'])
+    ->execute()
+    ->fetchAllKeyed();
+
+  // Remove data from the storage.
+  $connection->update('entity_test')
+    ->fields(['user_id' => NULL])
+    ->execute();
+
+  // Update definitions and schema.
+  $entity_manager = \Drupal::entityManager();
+  $storage_definitions = $entity_manager->getLastInstalledFieldStorageDefinitions('entity_test');
+  $original = $storage_definitions['user_id'];
+  /** @var \Drupal\Core\Field\BaseFieldDefinition $storage_definition */
+  $storage_definition = clone $original;
+  $storage_definition->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
+  $entity_manager->onFieldStorageDefinitionUpdate($storage_definition, $original);
+
+  // Restore entity data in the new schema.
+  $insert_query = $connection->insert('entity_test__user_id')
+    ->fields(['bundle', 'deleted', 'entity_id', 'revision_id', 'langcode', 'delta', 'user_id_target_id']);
+  foreach ($user_ids as $id => $user_id) {
+    $insert_query->values(['entity_test', 0, $id, $id, 'en', 0, $user_id]);
+  }
+  $insert_query->execute();
+}
diff --git a/core/modules/system/tests/modules/entity_test/update/entity_definition_updates_8002.inc b/core/modules/system/tests/modules/entity_test/update/entity_definition_updates_8002.inc
new file mode 100644
index 0000000..aea934e
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/update/entity_definition_updates_8002.inc
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Defines the 8002 db update for the "entity_definition_updates" test key.
+ */
+
+require_once 'entity_definition_updates_8001.inc';
+
+/**
+ * Makes the 'user_id' field single and migrate its data.
+ */
+function entity_test_update_8002() {
+  $connection = \Drupal::database();
+
+  // Retrieve existing entity data.
+  $query = $connection->select('entity_test__user_id', 'et')
+    ->fields('et', ['entity_id', 'user_id_target_id']);
+  $query->condition('et.delta', 0);
+  $user_ids = $query->execute()->fetchAllKeyed();
+
+  // Remove data from the storage.
+  $connection->truncate('entity_test__user_id')->execute();
+
+  // Update definitions and schema.
+  $entity_manager = \Drupal::entityManager();
+  $storage_definitions = $entity_manager->getLastInstalledFieldStorageDefinitions('entity_test');
+  $original = $storage_definitions['user_id'];
+  /** @var \Drupal\Core\Field\BaseFieldDefinition $storage_definition */
+  $storage_definition = clone $original;
+  $storage_definition->setCardinality(1);
+  $entity_manager->onFieldStorageDefinitionUpdate($storage_definition, $original);
+
+  // Restore entity data in the new schema.
+  foreach ($user_ids as $id => $user_id) {
+    $connection->update('entity_test')
+      ->fields(['user_id' => $user_id])
+      ->condition('id', $id)
+      ->execute();
+  }
+}
diff --git a/core/modules/system/tests/modules/entity_test/update/status_report_8001.inc b/core/modules/system/tests/modules/entity_test/update/status_report_8001.inc
new file mode 100644
index 0000000..13da10e
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/update/status_report_8001.inc
@@ -0,0 +1,13 @@
+<?php
+
+/**
+ * @file
+ * Defines the 8001 db update for the "status_report" test key.
+ */
+
+/**
+ * Test update.
+ */
+function entity_test_update_8001() {
+  // Empty update, we just want to trigger an error in the status report.
+}
diff --git a/core/modules/system/tests/modules/entity_test/update/status_report_8002.inc b/core/modules/system/tests/modules/entity_test/update/status_report_8002.inc
new file mode 100644
index 0000000..a29f42d
--- /dev/null
+++ b/core/modules/system/tests/modules/entity_test/update/status_report_8002.inc
@@ -0,0 +1,15 @@
+<?php
+
+/**
+ * @file
+ * Defines the 8002 db update for the "status_report" test key.
+ */
+
+require_once 'status_report_8001.inc';
+
+/**
+ * Test update.
+ */
+function entity_test_update_8002() {
+  // Empty update, we just want to trigger an update run.
+}
