diff --git a/search_api.routing.yml b/search_api.routing.yml
index 04a0005..34c3448 100644
--- a/search_api.routing.yml
+++ b/search_api.routing.yml
@@ -114,8 +114,8 @@ entity.search_api_index.disable:
 entity.search_api_index.fields:
   path: '/admin/config/search/search-api/index/{search_api_index}/fields'
   options:
-    search_api_index:
-      view:
+    parameters:
+      search_api_index:
         tempstore: TRUE
         type: 'entity:search_api_index'
   defaults:
@@ -126,8 +126,8 @@ entity.search_api_index.fields:
 entity.search_api_index.add_fields:
   path: '/admin/config/search/search-api/index/{search_api_index}/fields/add'
   options:
-    search_api_index:
-      view:
+    parameters:
+      search_api_index:
         tempstore: TRUE
         type: 'entity:search_api_index'
   defaults:
@@ -138,8 +138,8 @@ entity.search_api_index.add_fields:
 entity.search_api_index.field_config:
   path: '/admin/config/search/search-api/index/{search_api_index}/fields/{field_id}/edit'
   options:
-    search_api_index:
-      view:
+    parameters:
+      search_api_index:
         tempstore: TRUE
         type: 'entity:search_api_index'
   defaults:
@@ -151,8 +151,8 @@ entity.search_api_index.field_config:
 entity.search_api_index.remove_field:
   path: '/admin/config/search/search-api/index/{search_api_index}/fields/{field_id}/remove'
   options:
-    search_api_index:
-      view:
+    parameters:
+      search_api_index:
         tempstore: TRUE
         type: 'entity:search_api_index'
   defaults:
diff --git a/src/Entity/Index.php b/src/Entity/Index.php
index 1b710d8..b04628c 100644
--- a/src/Entity/Index.php
+++ b/src/Entity/Index.php
@@ -693,6 +693,8 @@ public function addField(FieldInterface $field) {
       throw new SearchApiException("'$field_id' is a reserved value and cannot be used as the machine name of a normal field.");
     }
 
+    // This will automatically call getFields(), thus initializing
+    // $this->fieldInstances, if that hasn't been done yet.
     $old_field = $this->getField($field_id);
     if ($old_field && $old_field != $field) {
       throw new SearchApiException("Cannot add field with machine name '$field_id': machine name is already taken.");
@@ -1186,12 +1188,31 @@ public function preSave(EntityStorageInterface $storage) {
       $processor->preIndexSave();
     }
 
+    // Write the field and plugin settings to the persistent *_settings
+    // properties.
+    $this->writeChangesToSettings();
+
+    // Since we change dependency-relevant data in this method, we can only call
+    // the parent method at the end (or we'd need to re-calculate the
+    // dependencies).
+    parent::preSave($storage);
+  }
+
+  /**
+   * Prepares for changes to this index to be persisted.
+   *
+   * To this end, the settings for all loaded field and plugin objects are
+   * written back to the corresponding *_settings properties.
+   *
+   * @return $this
+   */
+  protected function writeChangesToSettings() {
     // Calculate field dependencies and save field settings containing them.
     $fields = $this->getFields();
     $field_dependencies = $this->getFieldDependencies();
     $field_dependencies += array_fill_keys(array_keys($fields), array());
     $this->field_settings = array();
-    foreach ($this->getFields() as $field_id => $field) {
+    foreach ($fields as $field_id => $field) {
       $field->setDependencies($field_dependencies[$field_id]);
       $this->field_settings[$field_id] = $field->getSettings();
     }
@@ -1225,10 +1246,7 @@ public function preSave(EntityStorageInterface $storage) {
       );
     }
 
-    // Since we change dependency-relevant data in this method, we can only call
-    // the parent method at the end (or we'd need to re-calculate the
-    // dependencies).
-    parent::preSave($storage);
+    return $this;
   }
 
   /**
@@ -1835,6 +1853,14 @@ protected function getAllPlugins() {
    * Prevents the instantiated plugins and fields from being serialized.
    */
   public function __sleep() {
+    // First, write our changes to the persistent *_settings properties so they
+    // won't be discarded. Make sure we have a container to do this. This is
+    // important to correctly display test failures.
+    if (\Drupal::hasContainer()) {
+      $this->writeChangesToSettings();
+    }
+
+    // Then, return a list of all properties that don't contain objects.
     $properties = get_object_vars($this);
     unset($properties['datasourceInstances']);
     unset($properties['trackerInstance']);
diff --git a/src/Form/FieldConfigurationForm.php b/src/Form/FieldConfigurationForm.php
index 21c1f9e..a23425a 100644
--- a/src/Form/FieldConfigurationForm.php
+++ b/src/Form/FieldConfigurationForm.php
@@ -2,15 +2,22 @@
 
 namespace Drupal\search_api\Form;
 
+use Drupal\Core\Datetime\DateFormatter;
 use Drupal\Core\Entity\EntityForm;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Render\RendererInterface;
 use Drupal\search_api\Processor\ConfigurablePropertyInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\RequestStack;
 
 /**
  * Defines a form for changing a field's configuration.
  */
 class FieldConfigurationForm extends EntityForm {
 
+  use UnsavedConfigurationFormTrait;
+
   /**
    * The index for which the fields are configured.
    *
@@ -33,6 +40,37 @@ public function getFormId() {
   }
 
   /**
+   * Constructs a FieldConfigurationForm object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
+   *   The entity manager.
+   * @param \Drupal\Core\Render\RendererInterface $renderer
+   *   The renderer to use.
+   * @param \Drupal\Core\Datetime\DateFormatter $date_formatter
+   *   The date formatter.
+   * @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
+   *   The request stack.
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer, DateFormatter $date_formatter, RequestStack $request_stack) {
+    $this->entityTypeManager = $entity_type_manager;
+    $this->renderer = $renderer;
+    $this->dateFormatter = $date_formatter;
+    $this->requestStack = $request_stack;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    $entity_type_manager = $container->get('entity_type.manager');
+    $renderer = $container->get('renderer');
+    $date_formatter = $container->get('date.formatter');
+    $request_stack = $container->get('request_stack');
+
+    return new static($entity_type_manager, $renderer, $date_formatter, $request_stack);
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
@@ -73,6 +111,8 @@ public function form(array $form, FormStateInterface $form_state) {
 
     $form = $property->buildConfigurationForm($field, $form, $form_state);
 
+    $this->checkEntityEditable($form, $this->entity);
+
     return $form;
   }
 
diff --git a/src/Form/IndexAddFieldsForm.php b/src/Form/IndexAddFieldsForm.php
index 9236f39..0f18c57 100644
--- a/src/Form/IndexAddFieldsForm.php
+++ b/src/Form/IndexAddFieldsForm.php
@@ -13,11 +13,8 @@
 use Drupal\Core\TypedData\ComplexDataDefinitionInterface;
 use Drupal\Core\Url;
 use Drupal\search_api\Datasource\DatasourceInterface;
-use Drupal\search_api\DataType\DataTypePluginManager;
 use Drupal\search_api\Processor\ConfigurablePropertyInterface;
-use Drupal\search_api\UnsavedConfigurationInterface;
 use Drupal\search_api\Utility\Utility;
-use Drupal\user\SharedTempStoreFactory;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
@@ -25,6 +22,8 @@
  */
 class IndexAddFieldsForm extends EntityForm {
 
+  use UnsavedConfigurationFormTrait;
+
   /**
    * The index for which the fields are configured.
    *
@@ -33,41 +32,6 @@ class IndexAddFieldsForm extends EntityForm {
   protected $entity;
 
   /**
-   * The shared temporary storage for unsaved search indexes.
-   *
-   * @var \Drupal\user\SharedTempStore
-   */
-  protected $tempStore;
-
-  /**
-   * The entity manager.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
-   */
-  protected $entityTypeManager;
-
-  /**
-   * The data type plugin manager.
-   *
-   * @var \Drupal\search_api\DataType\DataTypePluginManager
-   */
-  protected $dataTypePluginManager;
-
-  /**
-   * The renderer.
-   *
-   * @var \Drupal\Core\Render\RendererInterface
-   */
-  protected $renderer;
-
-  /**
-   * The date formatter.
-   *
-   * @var \Drupal\Core\Datetime\DateFormatter
-   */
-  protected $dateFormatter;
-
-  /**
    * The parameters of the current page request.
    *
    * @var array
@@ -101,12 +65,8 @@ public function getBaseFormId() {
   /**
    * Constructs an IndexAddFieldsForm object.
    *
-   * @param \Drupal\user\SharedTempStoreFactory $temp_store_factory
-   *   The factory for shared temporary storages.
    * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
    *   The entity manager.
-   * @param \Drupal\search_api\DataType\DataTypePluginManager $data_type_plugin_manager
-   *   The data type plugin manager.
    * @param \Drupal\Core\Render\RendererInterface $renderer
    *   The renderer to use.
    * @param \Drupal\Core\Datetime\DateFormatter $date_formatter
@@ -114,10 +74,8 @@ public function getBaseFormId() {
    * @param array $parameters
    *   The parameters for this page request.
    */
-  public function __construct(SharedTempStoreFactory $temp_store_factory, EntityTypeManagerInterface $entity_type_manager, DataTypePluginManager $data_type_plugin_manager, RendererInterface $renderer, DateFormatter $date_formatter, array $parameters) {
-    $this->tempStore = $temp_store_factory->get('search_api_index');
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, RendererInterface $renderer, DateFormatter $date_formatter, array $parameters) {
     $this->entityTypeManager = $entity_type_manager;
-    $this->dataTypePluginManager = $data_type_plugin_manager;
     $this->renderer = $renderer;
     $this->dateFormatter = $date_formatter;
     $this->parameters = $parameters;
@@ -127,56 +85,15 @@ public function __construct(SharedTempStoreFactory $temp_store_factory, EntityTy
    * {@inheritdoc}
    */
   public static function create(ContainerInterface $container) {
-    $temp_store_factory = $container->get('user.shared_tempstore');
     $entity_type_manager = $container->get('entity_type.manager');
-    $data_type_plugin_manager = $container->get('plugin.manager.search_api.data_type');
     $renderer = $container->get('renderer');
     $date_formatter = $container->get('date.formatter');
     $request_stack = $container->get('request_stack');
     $parameters = $request_stack->getCurrentRequest()->query->all();
 
-    return new static($temp_store_factory, $entity_type_manager, $data_type_plugin_manager, $renderer, $date_formatter, $parameters);
+    return new static($entity_type_manager, $renderer, $date_formatter, $parameters);
   }
 
-  /**
-   * Retrieves the entity manager.
-   *
-   * @return \Drupal\Core\Entity\EntityTypeManagerInterface
-   *   The entity manager.
-   */
-  protected function getEntityTypeManager() {
-    return $this->entityTypeManager;
-  }
-
-  /**
-   * Retrieves the data type plugin manager.
-   *
-   * @return \Drupal\search_api\DataType\DataTypePluginManager
-   *   The data type plugin manager.
-   */
-  public function getDataTypePluginManager() {
-    return $this->dataTypePluginManager;
-  }
-
-  /**
-   * Retrieves the renderer.
-   *
-   * @return \Drupal\Core\Render\RendererInterface
-   *   The renderer.
-   */
-  public function getRenderer() {
-    return $this->renderer;
-  }
-
-  /**
-   * Retrieves the date formatter.
-   *
-   * @return \Drupal\Core\Datetime\DateFormatter
-   *   The date formatter.
-   */
-  public function getDateFormatter() {
-    return $this->dateFormatter;
-  }
 
   /**
    * Retrieves a single page request parameter.
@@ -203,32 +120,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     // \Drupal\views_ui\ViewEditForm::form().
     $form_state->disableCache();
 
-    if ($index instanceof UnsavedConfigurationInterface && $index->hasChanges()) {
-      if ($index->isLocked()) {
-        $form['#disabled'] = TRUE;
-        $username = array(
-          '#theme' => 'username',
-          '#account' => $index->getLockOwner($this->entityTypeManager),
-        );
-        $lock_message_substitutions = array(
-          '@user' => $this->getRenderer()->render($username),
-          '@age' => $this->dateFormatter->formatTimeDiffSince($index->getLastUpdated()),
-          ':url' => $index->toUrl('break-lock-form')->toString(),
-        );
-        $form['locked'] = array(
-          '#type' => 'container',
-          '#attributes' => array(
-            'class' => array(
-              'index-locked',
-              'messages',
-              'messages--warning',
-            ),
-          ),
-          '#children' => $this->t('This index is being edited by user @user, and is therefore locked from editing by others. This lock is @age old. Click here to <a href=":url">break this lock</a>.', $lock_message_substitutions),
-          '#weight' => -10,
-        );
-      }
-    }
+    $this->checkEntityEditable($form, $index);
 
     $args['%index'] = $index->label();
     $form['#title'] = $this->t('Add fields to index %index', $args);
diff --git a/src/Form/IndexBreakLockForm.php b/src/Form/IndexBreakLockForm.php
index fda1faf..da363f9 100644
--- a/src/Form/IndexBreakLockForm.php
+++ b/src/Form/IndexBreakLockForm.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\Entity\EntityConfirmFormBase;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Form\EnforcedResponseException;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\user\SharedTempStoreFactory;
@@ -120,7 +121,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
   public function submitForm(array &$form, FormStateInterface $form_state) {
     $this->tempStore->delete($this->entity->id());
     $form_state->setRedirectUrl($this->entity->toUrl('fields'));
-    drupal_set_message($this->t('The lock has been broken and you may now edit this search index.'));
+    drupal_set_message($this->t('The lock has been broken. You may now edit this search index.'));
   }
 
 }
diff --git a/src/Form/IndexFieldsForm.php b/src/Form/IndexFieldsForm.php
index 17f683e..c4ee3fe 100644
--- a/src/Form/IndexFieldsForm.php
+++ b/src/Form/IndexFieldsForm.php
@@ -21,6 +21,8 @@
  */
 class IndexFieldsForm extends EntityForm {
 
+  use UnsavedConfigurationFormTrait;
+
   /**
    * The index for which the fields are configured.
    *
@@ -36,13 +38,6 @@ class IndexFieldsForm extends EntityForm {
   protected $tempStore;
 
   /**
-   * The entity type manager.
-   *
-   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
-   */
-  protected $entityTypeManager;
-
-  /**
    * The data type plugin manager.
    *
    * @var \Drupal\search_api\DataType\DataTypePluginManager
@@ -50,20 +45,6 @@ class IndexFieldsForm extends EntityForm {
   protected $dataTypePluginManager;
 
   /**
-   * The renderer.
-   *
-   * @var \Drupal\Core\Render\RendererInterface
-   */
-  protected $renderer;
-
-  /**
-   * The date formatter.
-   *
-   * @var \Drupal\Core\Datetime\DateFormatter
-   */
-  protected $dateFormatter;
-
-  /**
    * {@inheritdoc}
    */
   public function getFormId() {
@@ -113,16 +94,6 @@ public static function create(ContainerInterface $container) {
   }
 
   /**
-   * Retrieves the entity type manager.
-   *
-   * @return \Drupal\Core\Entity\EntityTypeManagerInterface
-   *   The entity type manager.
-   */
-  protected function getEntityTypeManager() {
-    return $this->entityTypeManager;
-  }
-
-  /**
    * Retrieves the data type plugin manager.
    *
    * @return \Drupal\search_api\DataType\DataTypePluginManager
@@ -133,26 +104,6 @@ public function getDataTypePluginManager() {
   }
 
   /**
-   * Retrieves the renderer.
-   *
-   * @return \Drupal\Core\Render\RendererInterface
-   *   The renderer.
-   */
-  public function getRenderer() {
-    return $this->renderer;
-  }
-
-  /**
-   * Retrieves the date formatter.
-   *
-   * @return \Drupal\Core\Datetime\DateFormatter
-   *   The date formatter.
-   */
-  public function getDateFormatter() {
-    return $this->dateFormatter;
-  }
-
-  /**
    * {@inheritdoc}
    */
   public function buildForm(array $form, FormStateInterface $form_state) {
@@ -162,46 +113,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
     // \Drupal\views_ui\ViewEditForm::form().
     $form_state->disableCache();
 
-    if ($index instanceof UnsavedConfigurationInterface && $index->hasChanges()) {
-      if ($index->isLocked()) {
-        $form['#disabled'] = TRUE;
-        $username = array(
-          '#theme' => 'username',
-          '#account' => $index->getLockOwner($this->entityTypeManager),
-        );
-        $lock_message_substitutions = array(
-          '@user' => $this->getRenderer()->render($username),
-          '@age' => $this->dateFormatter->formatTimeDiffSince($index->getLastUpdated()),
-          ':url' => $index->toUrl('break-lock-form')->toString(),
-        );
-        $form['locked'] = array(
-          '#type' => 'container',
-          '#attributes' => array(
-            'class' => array(
-              'index-locked',
-              'messages',
-              'messages--warning',
-            ),
-          ),
-          '#children' => $this->t('This index is being edited by user @user, and is therefore locked from editing by others. This lock is @age old. Click here to <a href=":url">break this lock</a>.', $lock_message_substitutions),
-          '#weight' => -10,
-        );
-      }
-      else {
-        $form['changed'] = array(
-          '#type' => 'container',
-          '#attributes' => array(
-            'class' => array(
-              'index-changed',
-              'messages',
-              'messages--warning',
-            ),
-          ),
-          '#children' => $this->t('You have unsaved changes.'),
-          '#weight' => -10,
-        );
-      }
-    }
+    $this->checkEntityEditable($form, $index, TRUE);
 
     // Set an appropriate page title.
     $form['#title'] = $this->t('Manage fields for search index %label', array('%label' => $index->label()));
@@ -487,28 +399,16 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
    */
   public function save(array $form, FormStateInterface $form_state) {
     $index = $this->entity;
-    $changes = TRUE;
     if ($index instanceof UnsavedConfigurationInterface) {
-      if ($index->hasChanges()) {
-        $index->savePermanent();
-      }
-      else {
-        $index->discardChanges();
-        $changes = FALSE;
-      }
+      $index->savePermanent($this->getEntityTypeManager());
     }
     else {
       $index->save();
     }
 
-    if ($changes) {
-      drupal_set_message($this->t('The changes were successfully saved.'));
-      if ($this->entity->isReindexing()) {
-        drupal_set_message(t('All content was scheduled for reindexing so the new settings can take effect.'));
-      }
-    }
-    else {
-      drupal_set_message($this->t('No values were changed.'));
+    drupal_set_message($this->t('The changes were successfully saved.'));
+    if ($this->entity->isReindexing()) {
+      drupal_set_message(t('All content was scheduled for reindexing so the new settings can take effect.'));
     }
 
     return SAVED_UPDATED;
diff --git a/src/Form/UnsavedConfigurationFormTrait.php b/src/Form/UnsavedConfigurationFormTrait.php
new file mode 100644
index 0000000..7f519cc
--- /dev/null
+++ b/src/Form/UnsavedConfigurationFormTrait.php
@@ -0,0 +1,123 @@
+<?php
+
+namespace Drupal\search_api\Form;
+
+use Drupal\search_api\UnsavedConfigurationInterface;
+
+/**
+ * Provides a helper methods for forms to correctly treat unsaved configuration.
+ */
+trait UnsavedConfigurationFormTrait {
+
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+
+  /**
+   * The renderer.
+   *
+   * @var \Drupal\Core\Render\RendererInterface
+   */
+  protected $renderer;
+
+  /**
+   * The date formatter.
+   *
+   * @var \Drupal\Core\Datetime\DateFormatter
+   */
+  protected $dateFormatter;
+
+  /**
+   * Retrieves the entity type manager.
+   *
+   * @return \Drupal\Core\Entity\EntityTypeManagerInterface
+   *   The entity type manager.
+   */
+  protected function getEntityTypeManager() {
+    return $this->entityTypeManager;
+  }
+
+  /**
+   * Retrieves the renderer.
+   *
+   * @return \Drupal\Core\Render\RendererInterface
+   *   The renderer.
+   */
+  public function getRenderer() {
+    return $this->renderer;
+  }
+
+  /**
+   * Retrieves the date formatter.
+   *
+   * @return \Drupal\Core\Datetime\DateFormatter
+   *   The date formatter.
+   */
+  public function getDateFormatter() {
+    return $this->dateFormatter;
+  }
+
+  /**
+   * Checks whether the given entity contains unsaved changes.
+   *
+   * If this is the case and the changes were made by a different user, the form
+   * is disabled and a message displayed.
+   *
+   * Optionally, if there are unsaved changes by the current user, a different
+   * message can be displayed.
+   *
+   * @param array $form
+   *   The form structure, passed by reference.
+   * @param object $entity
+   *   The entity in question.
+   * @param bool $reportChanged
+   *   (optional) If TRUE, also show a message for unsaved changes by the
+   *   current user.
+   */
+  protected function checkEntityEditable(array &$form, $entity, $reportChanged = FALSE) {
+    if ($entity instanceof UnsavedConfigurationInterface && $entity->hasChanges()) {
+      if ($entity->isLocked()) {
+        $form['#disabled'] = TRUE;
+        $username = array(
+          '#theme' => 'username',
+          '#account' => $entity->getLockOwner($this->entityTypeManager),
+        );
+        $lockMessageSubstitutions = array(
+          '@user' => $this->renderer->render($username),
+          '@age' => $this->dateFormatter->formatTimeDiffSince($entity->getLastUpdated()),
+          ':url' => $entity->toUrl('break-lock-form')->toString(),
+        );
+        $form['locked'] = array(
+          '#type' => 'container',
+          '#attributes' => array(
+            'class' => array(
+              'index-locked',
+              'messages',
+              'messages--warning',
+            ),
+          ),
+          '#children' => $this->t('This index is being edited by user @user, and is therefore locked from editing by others. This lock is @age old. Click here to <a href=":url">break this lock</a>.', $lockMessageSubstitutions),
+          '#weight' => -10,
+        );
+      }
+      elseif ($reportChanged) {
+        $form['changed'] = array(
+          '#type' => 'container',
+          '#attributes' => array(
+            'class' => array(
+              'index-changed',
+              'messages',
+              'messages--warning',
+            ),
+          ),
+          '#children' => $this->t('You have unsaved changes.'),
+          '#weight' => -10,
+        );
+      }
+    }
+  }
+
+}
diff --git a/src/Item/Field.php b/src/Item/Field.php
index bf09974..b8ce003 100644
--- a/src/Item/Field.php
+++ b/src/Item/Field.php
@@ -217,6 +217,7 @@ public function setIndex(IndexInterface $index) {
       throw new \InvalidArgumentException('Attempted to change the index of a field object.');
     }
     $this->index = $index;
+    $this->datasource = NULL;
     return $this;
   }
 
@@ -259,6 +260,9 @@ public function getSettings() {
       'property_path' => $this->getPropertyPath(),
       'type' => $this->getType(),
     );
+    if ($this->getDatasourceId() === NULL) {
+      unset($settings['datasource_id']);
+    }
     if ($this->getBoost() != 1.0) {
       $settings['boost'] = $this->getBoost();
     }
@@ -301,6 +305,9 @@ public function getDatasource() {
    * {@inheritdoc}
    */
   public function setDatasourceId($datasource_id) {
+    if ($this->datasourceId != $datasource_id) {
+      $this->datasource = NULL;
+    }
     $this->datasourceId = $datasource_id;
     return $this;
   }
@@ -640,7 +647,9 @@ public function __sleep() {
    * Implements the magic __wakeup() method to control object unserialization.
    */
   public function __wakeup() {
-    if ($this->indexId) {
+    // Make sure we have a container to do this. This is important to correctly
+    // display test failures.
+    if ($this->indexId && \Drupal::hasContainer()) {
       $this->index = Index::load($this->indexId);
       unset($this->indexId);
     }
diff --git a/src/ParamConverter/SearchApiConverter.php b/src/ParamConverter/SearchApiConverter.php
index f484041..510d227 100644
--- a/src/ParamConverter/SearchApiConverter.php
+++ b/src/ParamConverter/SearchApiConverter.php
@@ -67,7 +67,7 @@ public function convert($value, $definition, $name, array $defaults) {
     $current_user_id = $this->currentUser->id() ?: session_id();
     /** @var \Drupal\search_api\IndexInterface|\Drupal\search_api\UnsavedConfigurationInterface $index */
     if ($index = $store->get($value)) {
-      $index->setCurrentUserId($current_user_id);
+      $index = new UnsavedIndexConfiguration($index, $store, $current_user_id);
       $index->setLockInformation($store->getMetadata($value));
     }
     // Otherwise, create a new temporary copy of the search index.
diff --git a/src/ProxyClass/ParamConverter/SearchApiConverter.php b/src/ProxyClass/ParamConverter/SearchApiConverter.php
index 1e0951a..85e0dc8 100644
--- a/src/ProxyClass/ParamConverter/SearchApiConverter.php
+++ b/src/ProxyClass/ParamConverter/SearchApiConverter.php
@@ -16,7 +16,7 @@
 /**
  * Provides a proxy class for \Drupal\search_api\ParamConverter\SearchApiConverter.
  *
- * @see \Drupal\Component\ProxyBuilder
+ * @see \Drupal\Component\ProxyBuilder\ProxyBuilder
  */
 class SearchApiConverter implements ParamConverterInterface {
 
diff --git a/src/Tests/IntegrationTest.php b/src/Tests/IntegrationTest.php
index 8277d55..b014596 100644
--- a/src/Tests/IntegrationTest.php
+++ b/src/Tests/IntegrationTest.php
@@ -25,6 +25,13 @@ class IntegrationTest extends WebTestBase {
   use PluginTestTrait;
 
   /**
+   * An admin user used for this test.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $adminUser2;
+
+  /**
    * The ID of the search server used for this test.
    *
    * @var string
@@ -57,14 +64,16 @@ public function setUp() {
     parent::setUp();
     $this->indexStorage = \Drupal::entityTypeManager()->getStorage('search_api_index');
 
-    $this->adminUser = $this->drupalCreateUser(array(
+    $permissions = array(
       'administer search_api',
       'access administration pages',
       'administer nodes',
       'bypass node access',
       'administer content types',
       'administer node fields',
-    ));
+    );
+    $this->adminUser = $this->drupalCreateUser($permissions);
+    $this->adminUser2 = $this->drupalCreateUser($permissions);
     $this->drupalLogin($this->adminUser);
   }
 
@@ -150,6 +159,7 @@ public function testIntegerIndex() {
     $this->addFieldsWithDependenciesToIndex();
     $this->removeFieldsDependencies();
     $this->removeFieldsFromIndex();
+    $this->checkUnsavedChanges();
 
     $this->configureFilter();
     $this->configureFilterPage();
@@ -717,6 +727,13 @@ protected function addFieldsToIndex() {
     $index = $this->getIndex(TRUE);
     $fields = $index->getFields();
 
+    $this->assertTrue(empty($fields['nid']), 'Field changes have not been persisted.');
+    $this->drupalPostForm($this->getIndexPath('fields'), array(), $this->t('Save changes'));
+    $this->assertText($this->t('The changes were successfully saved.'));
+
+    $index = $this->getIndex(TRUE);
+    $fields = $index->getFields();
+
     $this->assertTrue(!empty($fields['nid']), 'nid field is indexed.');
 
     // Ensure that we aren't offered to index properties of the "Content type"
@@ -880,6 +897,7 @@ protected function addFieldsWithDependenciesToIndex() {
     foreach ($fields as $property_path => $label) {
       $this->addField('entity:node', $property_path, $label);
     }
+    $this->drupalPostForm($this->getIndexPath('fields'), array(), $this->t('Save changes'));
 
     // Check that index configuration is updated with dependencies.
     $field_dependencies = (array) \Drupal::config('search_api.index.' . $this->indexId)->get('dependencies.config');
@@ -921,13 +939,10 @@ protected function removeFieldsDependencies() {
   protected function removeFieldsFromIndex() {
     // Find the "Remove" link for the "body" field.
     $links = $this->xpath('//a[@data-drupal-selector=:id]', array(':id' => 'edit-fields-body-remove'));
-    if (empty($links)) {
-      $this->fail('Found "Remove" link for body field');
-    }
-    else {
+    if ($this->assertTrue($links, 'Found "Remove" link for body field')) {
       $url_target = $this->getAbsoluteUrl($links[0]['href']);
-      $this->pass('Found "Remove" link for body field');
       $this->drupalGet($url_target);
+      $this->drupalPostForm($this->getIndexPath('fields'), array(), $this->t('Save changes'));
     }
 
     $index = $this->getIndex(TRUE);
@@ -936,6 +951,66 @@ protected function removeFieldsFromIndex() {
   }
 
   /**
+   * Tests whether unsaved fields changes work correctly.
+   */
+  protected function checkUnsavedChanges() {
+    $this->addField('entity:node', 'changed', $this->t('Changed'));
+    $this->drupalGet($this->getIndexPath('fields'));
+    $this->assertText($this->t('You have unsaved changes.'));
+
+    // Log in a different admin user.
+    $this->drupalLogin($this->adminUser2);
+
+    // Construct the message that should be displayed.
+    $username = array(
+      '#theme' => 'username',
+      '#account' => $this->adminUser,
+    );
+    $args = array(
+      '@user' => \Drupal::getContainer()->get('renderer')->renderPlain($username),
+      ':url' => $this->getIndex()->toUrl('break-lock-form')->toString(),
+    );
+    $message = (string) $this->t('This index is being edited by user @user, and is therefore locked from editing by others. This lock is @age old. Click here to <a href=":url">break this lock</a>.', $args);
+    // Since we can't predict the age that will be shown, just check for
+    // everything else.
+    $message_parts = explode('@age', $message);
+
+    $this->drupalGet($this->getIndexPath('fields/add'));
+    $this->assertRaw($message_parts[0]);
+    $this->assertRaw($message_parts[1]);
+    $this->assertFalse($this->xpath('//input[not(@disabled)]'));
+    $this->drupalGet($this->getIndexPath('fields/rendered_item/edit'));
+    $this->assertRaw($message_parts[0]);
+    $this->assertRaw($message_parts[1]);
+    $this->assertFalse($this->xpath('//input[not(@disabled)]'));
+    $this->drupalGet($this->getIndexPath('fields'));
+    $this->assertRaw($message_parts[0]);
+    $this->assertRaw($message_parts[1]);
+    $this->assertFalse($this->xpath('//input[not(@disabled)]'));
+    if ($this->assertTrue(preg_match('#fields/break-lock">([^<>]*?)</a>#', $message, $m))) {
+      $this->clickLink($m[1]);
+    }
+    $this->assertRaw($this->t('By breaking this lock, any unsaved changes made by @user will be lost.', $args));
+    $this->drupalPostForm(NULL, array(), $this->t('Break lock'));
+    $this->assertText($this->t('The lock has been broken. You may now edit this search index.'));
+    // Make sure the field has not been added to the index.
+    $index = $this->getIndex(TRUE);
+    $fields = $index->getFields();
+    $this->assertTrue(!isset($fields['changed']), 'The changed field has not been added to the index.');
+
+    // Find the "Remove" link for the "title" field.
+    $links = $this->xpath('//a[@data-drupal-selector=:id]', array(':id' => 'edit-fields-title-remove'));
+    if ($this->assertTrue($links, 'Found "Remove" link for title field')) {
+      $url_target = $this->getAbsoluteUrl($links[0]['href']);
+      $this->drupalGet($url_target);
+    }
+    $this->assertText($this->t('You have unsaved changes.'));
+    $this->drupalPostForm(NULL, array(), $this->t('Cancel'));
+
+    $this->assertTrue(!empty($fields['title']), 'The title field has not been removed from the index.');
+  }
+
+  /**
    * Tests if non-base fields of referenced entities can be added.
    */
   protected function checkReferenceFieldsNonBaseFields() {
@@ -969,6 +1044,7 @@ protected function checkReferenceFieldsNonBaseFields() {
     $node_label = $this->getIndex()->getDatasource('entity:node')->label();
     $field_label = "$field_label » $node_label » $field_label";
     $this->addField('entity:node', 'field__reference_field_:entity:field__reference_field_', $field_label);
+    $this->drupalPostForm($this->getIndexPath('fields'), array(), $this->t('Save changes'));
 
     $this->drupalGet('node/2/edit');
     $edit = array('field__reference_field_[0][target_id]' => 'Something (2)');
@@ -1049,14 +1125,13 @@ protected function changeProcessorFieldBoost() {
     $this->addField(NULL, 'search_api_url', $this->t('URI'));
 
     // Change the boost of the field.
-    $this->drupalGet($this->getIndexPath('fields'));
-    $this->drupalPostForm(NULL, array('fields[url][boost]' => '8.0'), $this->t('Save changes'));
+    $fields_path = $this->getIndexPath('fields');
+    $this->drupalPostForm($fields_path, array('fields[url][boost]' => '8.0'), $this->t('Save changes'));
     $this->assertText('The changes were successfully saved.');
     $this->assertOptionSelected('edit-fields-url-boost', '8.0', 'Boost is correctly saved.');
 
     // Change the type of the field.
-    $this->drupalGet($this->getIndexPath('fields'));
-    $this->drupalPostForm(NULL, array('fields[url][type]' => 'text'), $this->t('Save changes'));
+    $this->drupalPostForm($fields_path, array('fields[url][type]' => 'text'), $this->t('Save changes'));
     $this->assertText('The changes were successfully saved.');
     $this->assertOptionSelected('edit-fields-url-type', 'text', 'Type is correctly saved.');
   }
diff --git a/src/UnsavedIndexConfiguration.php b/src/UnsavedIndexConfiguration.php
index c0e6f94..4e2a803 100644
--- a/src/UnsavedIndexConfiguration.php
+++ b/src/UnsavedIndexConfiguration.php
@@ -5,12 +5,17 @@
 namespace Drupal\search_api;
 
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Core\Session\AccountInterface;
+use Drupal\search_api\Datasource\DatasourceInterface;
+use Drupal\search_api\Entity\Index;
 use Drupal\search_api\Item\FieldInterface;
+use Drupal\search_api\Processor\ProcessorInterface;
 use Drupal\search_api\Query\QueryInterface;
 use Drupal\search_api\Query\ResultSetInterface;
+use Drupal\search_api\Tracker\TrackerInterface;
 use Drupal\user\SharedTempStore;
 
 /**
@@ -50,13 +55,6 @@ class UnsavedIndexConfiguration implements IndexInterface, UnsavedConfigurationI
   protected $lock;
 
   /**
-   * The properties changed in this copy compared to the original.
-   *
-   * @var string[]
-   */
-  protected $changedProperties = array();
-
-  /**
    * Constructs a new UnsavedIndexConfiguration.
    *
    * @param \Drupal\search_api\IndexInterface $index
@@ -121,21 +119,29 @@ public function getLastUpdated() {
    */
   public function setLockInformation($lock = NULL) {
     $this->lock = $lock;
+    return $this;
   }
 
   /**
    * {@inheritdoc}
    */
   public function savePermanent(EntityTypeManagerInterface $entity_type_manager = NULL) {
-    // Make sure to overwrite only those properties that were changed in this
-    // copy. Unlike in the Views UI, we have several edit pages for indexes
-    // ("Edit", "Fields", "Processors") and only one of them is locked, so this
-    // is necessary.
+    if (!$entity_type_manager) {
+      $entity_type_manager = \Drupal::entityTypeManager();
+    }
+    // Make sure to overwrite only the index's fields, not just all properties.
+    // Unlike the Views UI, we have several separate pages for editing index
+    // entities, and only one of them is locked. Therefore, this extra step is
+    // necessary, we can't just call $this->entity->save().
     /** @var \Drupal\search_api\IndexInterface $original */
-    $original = $entity_type_manager->getStorage($this->entity->getEntityTypeId())->loadUnchanged($this->entity->id());
-    foreach ($this->changedProperties as $property) {
-      $original->set($property, $this->entity->get($property));
+    $storage = $entity_type_manager->getStorage($this->entity->getEntityTypeId());
+    $original = $storage->loadUnchanged($this->entity->id());
+    $fields = $this->entity->getFields();
+    // Set the correct index object on the field objects.
+    foreach ($fields as $field) {
+      $field->setIndex($original);
     }
+    $original->setFields($fields);
     $original->save();
     // Setting the saved entity as the wrapped one is important if methods like
     // isReindexing() are called on the object afterwards.
@@ -182,16 +188,37 @@ public function getOptions() {
    * {@inheritdoc}
    */
   public function setOption($name, $option) {
-    $this->changedProperties['options'] = 'options';
-    return $this->entity->setOption($name, $option);
+    $this->entity->setOption($name, $option);
+    return $this;
   }
 
   /**
    * {@inheritdoc}
    */
   public function setOptions(array $options) {
-    $this->changedProperties['options'] = 'options';
-    return $this->entity->setOptions($options);
+    $this->entity->setOptions($options);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createPlugin($type, $plugin_id, $configuration = array()) {
+    return $this->entity->createPlugin($type, $plugin_id, $configuration);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createPlugins($type, array $plugin_ids = NULL, $configurations = array()) {
+    return $this->entity->createPlugins($type, $plugin_ids, $configurations);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDatasources() {
+    return $this->entity->getDatasources();
   }
 
   /**
@@ -218,8 +245,25 @@ public function getDatasource($datasource_id) {
   /**
    * {@inheritdoc}
    */
-  public function getDatasources() {
-    return $this->entity->getDatasources();
+  public function addDatasource(DatasourceInterface $datasource) {
+    $this->entity->addDatasource($datasource);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function removeDatasource($datasource_id) {
+    $this->entity->removeDatasource($datasource_id);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setDatasources(array $datasources) {
+    $this->entity->setDatasources($datasources);
+    return $this;
   }
 
   /**
@@ -246,6 +290,14 @@ public function getTrackerInstance() {
   /**
    * {@inheritdoc}
    */
+  public function setTracker(TrackerInterface $tracker) {
+    $this->entity->setTracker($tracker);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function hasValidServer() {
     return $this->entity->hasValidServer();
   }
@@ -275,7 +327,8 @@ public function getServerInstance() {
    * {@inheritdoc}
    */
   public function setServer(ServerInterface $server = NULL) {
-    return $this->entity->setServer($server);
+    $this->entity->setServer($server);
+    return $this;
   }
 
   /**
@@ -295,22 +348,99 @@ public function getProcessorsByStage($stage) {
   /**
    * {@inheritdoc}
    */
-  public function preprocessIndexItems(array &$items) {
-    return $this->entity->preprocessIndexItems($items);
+  public function isValidProcessor($processor_id) {
+    return $this->entity->isValidProcessor($processor_id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getProcessor($processor_id) {
+    return $this->entity->getProcessor($processor_id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addProcessor(ProcessorInterface $processor) {
+    $this->entity->addProcessor($processor);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function removeProcessor($processor_id) {
+    $this->entity->removeProcessor($processor_id);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setProcessors(array $processors) {
+    $this->entity->setProcessors($processors);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function alterIndexedItems(array &$items) {
+    $this->entity->alterIndexedItems($items);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function preprocessIndexItems(array $items) {
+    $this->entity->preprocessIndexItems($items);
   }
 
   /**
    * {@inheritdoc}
    */
   public function preprocessSearchQuery(QueryInterface $query) {
-    return $this->entity->preprocessSearchQuery($query);
+    $this->entity->preprocessSearchQuery($query);
   }
 
   /**
    * {@inheritdoc}
    */
   public function postprocessSearchResults(ResultSetInterface $results) {
-    return $this->entity->postprocessSearchResults($results);
+    $this->entity->postprocessSearchResults($results);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function addField(FieldInterface $field) {
+    $this->entity->addField($field);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function renameField($old_field_id, $new_field_id) {
+    $this->entity->renameField($old_field_id, $new_field_id);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function removeField($field_id) {
+    $this->entity->removeField($field_id);
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setFields(array $fields) {
+    $this->entity->setFields($fields);
+    return $this;
   }
 
   /**
@@ -344,6 +474,13 @@ public function getFulltextFields() {
   /**
    * {@inheritdoc}
    */
+  public function getFieldRenames() {
+    return $this->entity->getFieldRenames();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getPropertyDefinitions($datasource_id) {
     return $this->entity->getPropertyDefinitions($datasource_id);
   }
@@ -379,36 +516,59 @@ public function indexSpecificItems(array $search_objects) {
   /**
    * {@inheritdoc}
    */
+  public function isBatchTracking() {
+    return $this->entity->isBatchTracking();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function startBatchTracking() {
+    $this->entity->startBatchTracking();
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function stopBatchTracking() {
+    $this->entity->stopBatchTracking();
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function trackItemsInserted($datasource_id, array $ids) {
-    return $this->entity->trackItemsInserted($datasource_id, $ids);
+    $this->entity->trackItemsInserted($datasource_id, $ids);
   }
 
   /**
    * {@inheritdoc}
    */
   public function trackItemsUpdated($datasource_id, array $ids) {
-    return $this->entity->trackItemsUpdated($datasource_id, $ids);
+    $this->entity->trackItemsUpdated($datasource_id, $ids);
   }
 
   /**
    * {@inheritdoc}
    */
   public function trackItemsDeleted($datasource_id, array $ids) {
-    return $this->entity->trackItemsDeleted($datasource_id, $ids);
+    $this->entity->trackItemsDeleted($datasource_id, $ids);
   }
 
   /**
    * {@inheritdoc}
    */
   public function reindex() {
-    return $this->entity->reindex();
+    $this->entity->reindex();
   }
 
   /**
    * {@inheritdoc}
    */
   public function clear() {
-    return $this->entity->clear();
+    $this->entity->clear();
   }
 
   /**
@@ -429,29 +589,32 @@ public function query(array $options = array()) {
    * {@inheritdoc}
    */
   public function enable() {
-    return $this->entity->enable();
+    $this->entity->enable();
+    return $this;
   }
 
   /**
    * {@inheritdoc}
    */
   public function disable() {
-    return $this->entity->disable();
+    $this->entity->disable();
+    return $this;
   }
 
   /**
    * {@inheritdoc}
    */
   public function setStatus($status) {
-    $this->changedProperties['status'] = 'status';
-    return $this->entity->setStatus($status);
+    $this->entity->setStatus($status);
+    return $this;
   }
 
   /**
    * {@inheritdoc}
    */
   public function setSyncing($status) {
-    return $this->entity->setSyncing($status);
+    $this->entity->setSyncing($status);
+    return $this;
   }
 
   /**
@@ -486,15 +649,16 @@ public function get($property_name) {
    * {@inheritdoc}
    */
   public function set($property_name, $value) {
-    $this->changedProperties[$property_name] = $property_name;
-    return $this->entity->set($property_name, $value);
+    $this->entity->set($property_name, $value);
+    return $this;
   }
 
   /**
    * {@inheritdoc}
    */
   public function calculateDependencies() {
-    return $this->entity->calculateDependencies();
+    $this->entity->calculateDependencies();
+    return $this;
   }
 
   /**
@@ -522,7 +686,8 @@ public function isInstallable() {
    * {@inheritdoc}
    */
   public function trustData() {
-    return $this->entity->trustData();
+    $this->entity->trustData();
+    return $this;
   }
 
   /**
@@ -564,7 +729,8 @@ public function isNew() {
    * {@inheritdoc}
    */
   public function enforceIsNew($value = TRUE) {
-    return $this->entity->enforceIsNew($value);
+    $this->entity->enforceIsNew($value);
+    return $this;
   }
 
   /**
@@ -641,49 +807,52 @@ public function uriRelationships() {
    * {@inheritdoc}
    */
   public static function load($id) {
-    EntityInterface::load($id);
+    return Index::load($id);
   }
 
   /**
    * {@inheritdoc}
    */
   public static function loadMultiple(array $ids = NULL) {
-    EntityInterface::loadMultiple($ids);
+    return Index::loadMultiple($ids);
   }
 
   /**
    * {@inheritdoc}
    */
   public static function create(array $values = array()) {
-    EntityInterface::create($values);
+    return Index::create($values);
   }
 
   /**
    * {@inheritdoc}
    */
   public function save() {
-    return $this->tempStore->setIfOwner($this->entity->id(), $this->entity);
+    if ($this->tempStore->setIfOwner($this->entity->id(), $this->entity)) {
+      return SAVED_UPDATED;
+    }
+    throw new EntityStorageException('Cannot save temporary index configuration: currently being edited by someone else.');
   }
 
   /**
    * {@inheritdoc}
    */
   public function delete() {
-    return $this->entity->delete();
+    $this->entity->delete();
   }
 
   /**
    * {@inheritdoc}
    */
   public function preSave(EntityStorageInterface $storage) {
-    return $this->entity->preSave($storage);
+    $this->entity->preSave($storage);
   }
 
   /**
    * {@inheritdoc}
    */
   public function postSave(EntityStorageInterface $storage, $update = TRUE) {
-    return $this->entity->postSave($storage, $update);
+    $this->entity->postSave($storage, $update);
   }
 
   /**
@@ -697,7 +866,7 @@ public static function preCreate(EntityStorageInterface $storage, array &$values
    * {@inheritdoc}
    */
   public function postCreate(EntityStorageInterface $storage) {
-    return $this->entity->postCreate($storage);
+    $this->entity->postCreate($storage);
   }
 
   /**
@@ -725,7 +894,7 @@ public static function postLoad(EntityStorageInterface $storage, array &$entitie
    * {@inheritdoc}
    */
   public function createDuplicate() {
-    return $this->entity->createDuplicate();
+    return new UnsavedIndexConfiguration($this->entity->createDuplicate(), $this->tempStore, $this->currentUserId);
   }
 
   /**
@@ -760,7 +929,8 @@ public function getCacheTagsToInvalidate() {
    * {@inheritdoc}
    */
   public function setOriginalId($id) {
-    return $this->entity->setOriginalId($id);
+    $this->entity->setOriginalId($id);
+    return $this;
   }
 
   /**
@@ -830,36 +1000,40 @@ public function getCacheMaxAge() {
    * {@inheritdoc}
    */
   public function addCacheContexts(array $cache_contexts) {
-    return $this->entity->addCacheContexts($cache_contexts);
+    $this->entity->addCacheContexts($cache_contexts);
+    return $this;
   }
 
   /**
    * {@inheritdoc}
    */
   public function addCacheTags(array $cache_tags) {
-    return $this->entity->addCacheTags($cache_tags);
+    $this->entity->addCacheTags($cache_tags);
+    return $this;
   }
 
   /**
    * {@inheritdoc}
    */
   public function mergeCacheMaxAge($max_age) {
-    return $this->entity->mergeCacheMaxAge($max_age);
+    $this->entity->mergeCacheMaxAge($max_age);
+    return $this;
   }
 
   /**
    * {@inheritdoc}
    */
   public function addCacheableDependency($other_object) {
-    return $this->entity->addCacheableDependency($other_object);
+    $this->entity->addCacheableDependency($other_object);
+    return $this;
   }
 
   /**
    * {@inheritdoc}
    */
   public function setThirdPartySetting($module, $key, $value) {
-    $this->changedProperties['third_party_settings'] = 'third_party_settings';
-    return $this->entity->setThirdPartySetting($module, $key, $value);
+    $this->entity->setThirdPartySetting($module, $key, $value);
+    return $this;
   }
 
   /**
@@ -880,7 +1054,6 @@ public function getThirdPartySettings($module) {
    * {@inheritdoc}
    */
   public function unsetThirdPartySetting($module, $key) {
-    $this->changedProperties['third_party_settings'] = 'third_party_settings';
     return $this->entity->unsetThirdPartySetting($module, $key);
   }
 
@@ -891,55 +1064,4 @@ public function getThirdPartyProviders() {
     return $this->entity->getThirdPartyProviders();
   }
 
-  /**
-   * Adds a field to this index.
-   *
-   * If the field is already present (with the same datasource and property
-   * path) its settings will be updated.
-   *
-   * @param \Drupal\search_api\Item\FieldInterface $field
-   *   The field to add, or update.
-   *
-   * @throws \Drupal\search_api\SearchApiException
-   *   Thrown if the field could not be added, either because a different field
-   *   with the same field ID would be overwritten, or because the field
-   *   identifier is one of the pseudo-fields that can be used in search
-   *   queries.
-   */
-  public function addField(FieldInterface $field) {
-    // @todo Implement addField() method.
-  }
-
-  /**
-   * Changes the field ID of a field.
-   *
-   * @param string $old_field_id
-   *   The old ID of the field.
-   * @param string $new_field_id
-   *   The new ID of the field.
-   *
-   * @throws \Drupal\search_api\SearchApiException
-   *   Thrown if no field with the old ID exists, or because the new ID is
-   *   already taken, or because the new field ID is one of the pseudo-fields
-   *   that can be used in search queries.
-   */
-  public function renameField($old_field_id, $new_field_id) {
-    // @todo Implement renameField() method.
-  }
-
-  /**
-   * Removes a field from the index.
-   *
-   * If the field doesn't exist, the call will fail silently.
-   *
-   * @param string $field_id
-   *   The ID of the field to remove.
-   *
-   * @throws \Drupal\search_api\SearchApiException
-   *   Thrown if the field is locked.
-   */
-  public function removeField($field_id) {
-    // @todo Implement removeField() method.
-  }
-
 }
diff --git a/tests/src/Unit/EntitySerializationTest.php b/tests/src/Kernel/EntitySerializationTest.php
similarity index 55%
rename from tests/src/Unit/EntitySerializationTest.php
rename to tests/src/Kernel/EntitySerializationTest.php
index cab8598..10d588a 100644
--- a/tests/src/Unit/EntitySerializationTest.php
+++ b/tests/src/Kernel/EntitySerializationTest.php
@@ -1,33 +1,29 @@
 <?php
 
-namespace Drupal\Tests\search_api\Unit;
+namespace Drupal\Tests\search_api\Kernel;
 
 use Drupal\Component\Serialization\Yaml;
-use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\KernelTests\KernelTestBase;
 use Drupal\search_api\Entity\Index;
 use Drupal\search_api\Entity\Server;
-use Drupal\Tests\UnitTestCase;
 
 /**
  * Tests the serialization of the entities.
  *
  * @group search_api
  */
-class EntitySerializationTest extends UnitTestCase {
+class EntitySerializationTest extends KernelTestBase {
 
   /**
    * {@inheritdoc}
    */
-  public function setUp() {
-    $config = $this->getMockBuilder('Drupal\Core\Config\Config')
-      ->disableOriginalConstructor()
-      ->getMock();
-    $mock_factory = $this->getMock('Drupal\Core\Config\ConfigFactoryInterface');
-    $mock_factory->method('get')->willReturn($config);
-    $container = new ContainerBuilder();
-    $container->set('config.factory', $mock_factory);
-    \Drupal::setContainer($container);
-  }
+  public static $modules = array(
+    'search_api',
+    'search_api_test',
+    'node',
+    'user',
+    'system',
+  );
 
   /**
    * Tests that serialization of index entities doesn't lead to data loss.
@@ -38,10 +34,26 @@ public function testIndexSerialization() {
     $index_values = Yaml::decode(file_get_contents($path));
     $index = new Index($index_values, 'search_api_index');
 
+    // Make some changes to the index to ensure they're saved, too.
+    $field_helper = \Drupal::getContainer()->get('search_api.fields_helper');
+    $field_info = array(
+      'type' => 'date',
+      'datasource_id' => 'entity:node',
+      'property_path' => 'uid:entity:created',
+    );
+    $index->addField($field_helper->createField($index, 'test1', $field_info));
+    $index->addDatasource($index->createPlugin('datasource', 'entity:user'));
+    $index->addProcessor($index->createPlugin('processor', 'highlight'));
+    $index->setTracker($index->createPlugin('tracker', 'search_api_test'));
+
+    /** @var \Drupal\search_api\IndexInterface $serialized */
     $serialized = unserialize(serialize($index));
 
     $this->assertNotEmpty($serialized);
-    $this->assertEquals($index, $serialized);
+    $storage = \Drupal::entityTypeManager()->getStorage('search_api_index');
+    $index->preSave($storage);
+    $serialized->preSave($storage);
+    $this->assertEquals($index->toArray(), $serialized->toArray());
   }
 
   /**
