diff --git a/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTest.php b/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..6c95f75dedfa5d8b5c2b35f0f1e7073b2c33e7f5
--- /dev/null
+++ b/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTest.php
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\entity_test\EntityTest.
+ */
+
+namespace Drupal\entity_test;
+
+use InvalidArgumentException;
+
+use Drupal\entity\Entity;
+
+/**
+ * Defines the test entity class.
+ */
+class EntityTest extends Entity {
+
+  /**
+   * An array keyed by language code where the entity properties are stored.
+   *
+   * @var array
+   */
+  protected $properties;
+
+  /**
+   * An array of allowed language codes.
+   *
+   * @var array
+   */
+  protected static $langcodes;
+
+  /**
+   * Constructs a new entity object.
+   */
+  public function __construct(array $values, $entity_type) {
+    parent::__construct($values, $entity_type);
+
+    if (!isset(self::$langcodes)) {
+      // The allowed languages are simply all the available ones in the system.
+      self::$langcodes = drupal_map_assoc(array_keys(language_list(LANGUAGE_ALL)));
+    }
+
+    // Initialize the original entity language with the provided value or fall
+    // back to LANGUAGE_NOT_SPECIFIED if none was specified. We do not check
+    // against allowed languages here, since throwing an exception would make an
+    // entity created in a subsequently uninstalled language not instantiable.
+    $this->langcode = !empty($values['langcode']) ? $values['langcode'] : LANGUAGE_NOT_SPECIFIED;
+
+    // Set initial values ensuring that only real properties are stored.
+    // @todo For now we have no way to mark a property as multlingual hence we
+    // just assume that all of them are.
+    unset($values['id'], $values['uuid'], $values['default_langcode']);
+    $this->setProperties($values, $this->langcode);
+  }
+
+  /**
+   * Sets the entity original langcode.
+   *
+   * @param $langcode
+   */
+  public function setLangcode($langcode) {
+    // If the original language is changed the related properties must change
+    // their language accordingly.
+    $prev_langcode = $this->langcode;
+    if (isset($this->properties[$prev_langcode])) {
+      $this->properties[$langcode] = $this->properties[$prev_langcode];
+      unset($this->properties[$prev_langcode]);
+    }
+    $this->langcode = $langcode;
+  }
+
+  /**
+   * Overrides EntityInterface::get().
+   */
+  public function get($property_name, $langcode = NULL) {
+    $langcode = !empty($langcode) ? $langcode : $this->langcode;
+    $entity_info = $this->entityInfo();
+    if ($entity_info['fieldable'] && field_info_instance($this->entityType, $property_name, $this->bundle())) {
+      return parent::get($property_name, $langcode);
+    }
+    elseif (isset($this->properties[$langcode][$property_name])) {
+      return $this->properties[$langcode][$property_name];
+    }
+    else {
+      // @todo Remove this. All properties should be stored in the $properties
+      // array once we have a Property API in place.
+      return property_exists($this, $property_name) ? $this->{$property_name} : NULL;
+    }
+  }
+
+  /**
+   * Overrides EntityInterface::set().
+   */
+  public function set($property_name, $value, $langcode = NULL) {
+    $langcode = !empty($langcode) ? $langcode : $this->langcode;
+    if (!isset(self::$langcodes[$langcode])) {
+      throw new InvalidArgumentException("Detected an invalid language '$langcode' while setting '$property_name' to '$value'.");
+    }
+    $entity_info = $this->entityInfo();
+    if ($entity_info['fieldable'] && field_info_instance($this->entityType, $property_name, $this->bundle())) {
+      parent::set($property_name, $value, $langcode);
+    }
+    else {
+      $this->properties[$langcode][$property_name] = $value;
+    }
+  }
+
+  /**
+   * Overrides EntityInterface::translations().
+   */
+  public function translations() {
+    $translations = !empty($this->properties) ? $this->properties : array();
+    $languages = array_intersect_key(self::$langcodes, $translations);
+    unset($languages[$this->langcode]);
+    return $languages + parent::translations();
+  }
+
+  /**
+   * Returns the property array for the given language.
+   *
+   * @param string $langcode
+   *   (optional) The language code to be used to retrieve the properties.
+   */
+  public function getProperties($langcode = NULL) {
+    $langcode = !empty($langcode) ? $langcode : $this->langcode;
+    return isset($this->properties[$langcode]) ? $this->properties[$langcode] : array();
+  }
+
+  /**
+   * Sets the property array for the given language.
+   *
+   * @param array $properties
+   *   A keyed array of properties to be set with their 'langcode' as one of the
+   *   keys. If no language is provided the entity language is used.
+   * @param string $langcode
+   *   (optional) The language code to be used to set the properties.
+   */
+  public function setProperties(array $properties, $langcode = NULL) {
+    $langcode = !empty($langcode) ? $langcode : $this->langcode;
+    $this->properties[$langcode] = $properties;
+  }
+}
diff --git a/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php b/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php
new file mode 100644
index 0000000000000000000000000000000000000000..147f030647a818c95b67f7134bc1ef943f4de9bc
--- /dev/null
+++ b/core/modules/entity/tests/modules/entity_test/lib/Drupal/entity_test/EntityTestStorageController.php
@@ -0,0 +1,123 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\entity_test\EntityTestStorageController.
+ */
+
+namespace Drupal\entity_test;
+
+use PDO;
+
+use Drupal\entity\EntityInterface;
+use Drupal\entity\DatabaseStorageController;
+
+/**
+ * Defines the controller class for the test entity.
+ *
+ * This extends the Drupal\entity\DatabaseStorageController class, adding
+ * required special handling for test entities.
+ */
+class EntityTestStorageController extends DatabaseStorageController {
+
+  /**
+   * Overrides Drupal\entity\DatabaseStorageController::buildQuery().
+   */
+  protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
+    $query = parent::buildQuery($ids, $conditions, $revision_id);
+
+    if ($conditions) {
+      // Reset conditions as the default storage controller applies them to the
+      // base table.
+      $query_conditions = &$query->conditions();
+      $query_conditions = array('#conjunction' => 'AND');
+
+      // Restore id conditions.
+      if ($ids) {
+        $query->condition("base.{$this->idKey}", $ids, 'IN');
+      }
+
+      // Conditions need to be applied the property data table.
+      $query->addJoin('inner', 'entity_test_property_data', 'data', "base.{$this->idKey} = data.{$this->idKey}");
+      $query->distinct(TRUE);
+
+      // @todo We should not be using a condition to specify whether conditions
+      // apply to the default language or not. We need to move this to a
+      // separate parameter during the following API refactoring.
+      // Default to the original entity language if not explicitly specified
+      // otherwise.
+      if (!array_key_exists('default_langcode', $conditions)) {
+        $conditions['default_langcode'] = 1;
+      }
+      // If the 'default_langcode' flag is esplicitly not set, we do not care
+      // whether the queried values are in the original entity language or not.
+      elseif ($conditions['default_langcode'] === NULL) {
+        unset($conditions['default_langcode']);
+      }
+
+      foreach ($conditions as $field => $value) {
+        $query->condition('data.' . $field, $value);
+      }
+    }
+
+    return $query;
+  }
+
+  /**
+   * Overrides Drupal\entity\DatabaseStorageController::attachLoad().
+   */
+  protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
+    $data = db_select('entity_test_property_data', 'data', array('fetch' => PDO::FETCH_ASSOC))
+      ->fields('data')
+      ->condition('id', array_keys($queried_entities))
+      ->orderBy('data.id')
+      ->execute();
+
+    foreach ($data as $values) {
+      $entity = $queried_entities[$values['id']];
+      $langcode = $values['langcode'];
+      if (!empty($values['default_langcode'])) {
+        $entity->setLangcode($langcode);
+      }
+      // Make sure only real properties are stored.
+      unset($values['id'], $values['default_langcode']);
+      $entity->setProperties($values, $langcode);
+    }
+
+    parent::attachLoad($queried_entities, $revision_id);
+  }
+
+  /**
+   * Overrides Drupal\entity\DatabaseStorageController::postSave().
+   */
+  protected function postSave(EntityInterface $entity, $update) {
+    $default_langcode = ($language = $entity->language()) ? $language->langcode : LANGUAGE_NOT_SPECIFIED;
+    $langcodes = array_keys($entity->translations());
+    $langcodes[] = $default_langcode;
+
+    foreach ($langcodes as $langcode) {
+      $properties = $entity->getProperties($langcode);
+
+      $values = array(
+        'id' => $entity->id(),
+        'langcode' => $langcode,
+        'default_langcode' => intval($default_langcode == $langcode),
+      ) + $properties;
+
+      db_merge('entity_test_property_data')
+        ->fields($values)
+        ->condition('id', $values['id'])
+        ->condition('langcode', $values['langcode'])
+        ->execute();
+    }
+  }
+
+  /**
+   * Overrides Drupal\entity\DatabaseStorageController::postDelete().
+   */
+  protected function postDelete($entities) {
+    db_delete('entity_test_property_data')
+      ->condition('id', array_keys($entities))
+      ->execute();
+  }
+}