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/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..9a83a7b 100644
--- a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
+++ b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php
@@ -10,6 +10,7 @@
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Database\Connection;
 use Drupal\Core\Database\Database;
+use Drupal\Core\Database\SchemaException;
 use Drupal\Core\Entity\ContentEntityInterface;
 use Drupal\Core\Entity\ContentEntityStorageBase;
 use Drupal\Core\Entity\EntityBundleListenerInterface;
@@ -1351,7 +1352,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 +1367,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 +1393,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 +1432,27 @@ 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) {
+    try {
+      $callback();
+    }
+    catch (SchemaException $e) {
+      throw new EntityStorageException('Exception thrown while performing a schema update.', 0, $e);
+    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php b/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorageSchema.php
index b92636c..829bf13 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;
@@ -194,6 +195,11 @@ public function requiresFieldStorageSchemaChanges(FieldStorageDefinitionInterfac
       return FALSE;
     }
 
+//    if ($this->getSchemaFromStorageDefinition($storage_definition) != $this->loadFieldSchemaData($original)) {
+//      debug($this->getSchemaFromStorageDefinition($storage_definition));
+//      debug($this->loadFieldSchemaData($original));
+//    }
+
     return $this->getSchemaFromStorageDefinition($storage_definition) != $this->loadFieldSchemaData($original);
   }
 
@@ -1391,6 +1397,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 +1443,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 +1843,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 +1859,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..cb7137e 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,121 @@ 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() {
+  $db_schema = \Drupal::database()->schema();
+  $entity_installed_schema = \Drupal::keyValue('entity.storage_schema.sql');
+
+  // 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. 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).
+  // Depending on what contributed or custom modules are installed, those
+  // interim updates could have also customized the schema handler. Therefore,
+  // we check for whether the schema data, tables, and indexes exist before
+  // modifying them. If any of it doesn't, we assume the interim updates or
+  // the customizing modules took care of the necessary updates.
+  $entity_schema_data = $entity_installed_schema->get('node.entity_schema_data');
+  $langcode_field_schema_data = $entity_installed_schema->get('node.field_schema_data.langcode');
+  if ($entity_schema_data && $langcode_field_schema_data) {
+    foreach (['node_field_data', 'node_field_revision'] as $table_name) {
+      if ($db_schema->tableExists($table_name)) {
+        if (isset($entity_schema_data[$table_name]['indexes']['node__default_langcode'])) {
+          $db_schema->dropIndex($table_name, 'node__default_langcode');
+          unset($entity_schema_data[$table_name]['indexes']['node__default_langcode']);
+          if (empty($entity_schema_data[$table_name]['indexes'])) {
+            unset($entity_schema_data[$table_name]['indexes']);
+          }
+        }
+        if (isset($langcode_field_schema_data[$table_name]['indexes']['node_field__langcode'])) {
+          $db_schema->dropIndex($table_name, 'node__default_langcode');
+          unset($langcode_field_schema_data[$table_name]['indexes']['node_field__langcode']);
+          if (empty($langcode_field_schema_data[$table_name]['indexes'])) {
+            unset($langcode_field_schema_data[$table_name]['indexes']);
+          }
+        }
+      }
+    }
+    $entity_installed_schema->set('node.entity_schema_data', $entity_schema_data);
+    $entity_installed_schema->set('node.field_schema_data.langcode', $langcode_field_schema_data);
+  }
+}
+
+/**
+ * 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/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..620b73b
--- /dev/null
+++ b/core/modules/system/src/Tests/Entity/Update/UpdateApiEntityDefinitionUpdateTest.php
@@ -0,0 +1,235 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Tests\Entity\Update\UpdateApiEntityDefinitionUpdateTest.
+ */
+
+namespace Drupal\system\Tests\Entity\Update;
+
+use Drupal\Core\Entity\Exception\FieldStorageDefinitionUpdateForbiddenException;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\Url;
+use Drupal\entity_test\Entity\EntityTest;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests performing entity updates through the Update API.
+ *
+ * @group Entity
+ */
+class UpdateApiEntityDefinitionUpdateTest extends WebTestBase {
+
+  use StringTranslationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected static $modules = ['entity_test'];
+
+  /**
+   * The entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManagerInterface
+   */
+  protected $entityManager;
+
+  /**
+   * The current state.
+   *
+   * @var \Drupal\Core\State\StateInterface
+   */
+  protected $state;
+
+  /**
+   * 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->state = $this->container->get('state');
+    $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_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_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_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->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('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('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());
+  }
+
+  /**
+   * Enables db updates until the specified index.
+   *
+   * @param $index
+   *   The index of the last update function to run.
+   */
+  protected function enableUpdates($key, $index) {
+    $this->state->set('entity_test.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'));
+  }
+
+}
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..d324163 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -588,10 +588,16 @@ 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')));
+      debug(\Drupal::service('entity.definition_update_manager')->getChangeSummary());
+      $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 +1247,64 @@ function system_update_8003() {
     ]
   );
 }
+
+/**
+ * Add a (id, default_langcode, langcode) composite index to entities.
+ */
+function system_update_8004() {
+  $db_schema = \Drupal::database()->schema();
+  $entity_manager = \Drupal::entityManager();
+
+  // \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. 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).
+  // For sites that did not perform those interim updates, update the existing
+  // schema to match that change. Note that if these indexes were added as an
+  // implementation of a changed entity type or field storage definition, we
+  // would invoke the higher-level $entity_manager->onEntityTypeUpdate() or
+  // $entity_manager->onFieldStorageDefinitionUpdate() functions and let the
+  // schema handler handle the database details, but since the indexes were
+  // added by a code change of SqlContentEntityStorageSchema itself, this
+  // update function needs to operate at this lower level.
+  $entity_installed_schema = \Drupal::keyValue('entity.storage_schema.sql');
+  foreach (array_keys($entity_manager->getDefinitions()) as $entity_type_id) {
+    if (($schema_data = $entity_installed_schema->get($entity_type_id . '.entity_schema_data')) && ($entity_type = $entity_manager->getLastInstalledDefinition($entity_type_id))) {
+      $id_field_name = $entity_type->getKey('id');
+      $default_langcode_field_name = $entity_type->getKey('default_langcode');
+      $langcode_field_name = $entity_type->getKey('langcode');
+
+      $index_name = $entity_type_id . '__id__default_langcode__langcode';
+      $index_fields = [$id_field_name, $default_langcode_field_name, $langcode_field_name];
+
+      // \Drupal\Core\Database\Schema::addIndex() requires some schema
+      // information about the fields being indexed in order to ensure that the
+      // index length isn't longer than the database allows. Here, we assume
+      // that for all entity types whose schema is managed by
+      // SqlContentEntityStorageSchema, that the field type plugin is:
+      // - 'integer': for the 'id' entity key field.
+      // - 'boolean': for the 'default_langcode' entity key field.
+      // - 'language': for the 'langcode' entity key field.
+      // The values below are what was returned by those field type plugins'
+      // schema() method at the time this update function was written.
+      $index_fields_spec = [
+        $id_field_name => ['type' => 'int', 'size' => 'normal'],
+        $default_langcode_field_name => ['type' => 'int', 'size' => 'tiny'],
+        $langcode_field_name => ['type' => 'varchar', 'length' => 12, 'is_ascii' => TRUE],
+      ];
+
+      $new_schema_data = $schema_data;
+      foreach ([$entity_type->getDataTable() ?: $entity_type_id . '_field_data', $entity_type->getRevisionDataTable() ?: $entity_type_id . '_field_revision'] as $table_name) {
+        if (isset($schema_data[$table_name]) && !isset($schema_data[$table_name]['indexes'][$index_name]) && $db_schema->tableExists($table_name)) {
+          $db_schema->addIndex($table_name, $index_name, $index_fields, ['fields' => $index_fields_spec]);
+          $new_schema_data[$table_name]['indexes'][$index_name] = $index_fields;
+        }
+      }
+      if ($new_schema_data != $schema_data) {
+        $entity_installed_schema->set($entity_type_id . '.entity_schema_data', $new_schema_data);
+      }
+    }
+  }
+}
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..0836c4b 100644
--- a/core/modules/system/tests/modules/entity_test/entity_test.install
+++ b/core/modules/system/tests/modules/entity_test/entity_test.install
@@ -49,3 +49,14 @@ function entity_test_schema() {
   );
   return $schema;
 }
+
+// Conditionally load Update API functions for entity definition updates.
+if ($index = \Drupal::state()->get('entity_test.db_updates.entity_definition_updates')) {
+  module_load_include('inc', 'entity_test', 'update/entity_definition_updates_' . $index);
+}
+
+// Conditionally load Update API functions for entity definition updates status
+// report test.
+if ($index = \Drupal::state()->get('entity_test.db_updates.status_report')) {
+  module_load_include('inc', 'entity_test', 'update/status_report_' . $index);
+}
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 1e7fa2b..90b5c73 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.
+}
