diff --git a/core/includes/common.inc b/core/includes/common.inc
index 668dc39..3435662 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -7032,9 +7032,18 @@ function drupal_write_record($table, &$record, $primary_keys = array()) {
       continue;
     }
 
+    // Skip fields that are not provided, default values are already known
+    // by the database.
+    // property_exists() allows to explicitly set a value to NULL.
     if (!property_exists($object, $field)) {
-      // Skip fields that are not provided, default values are already known
-      // by the database.
+      $default_fields[] = $field;
+      continue;
+    }
+    // However, if $object is an entity class instance, then class properties
+    // always exist, as they cannot be unset. Therefore, if $field is a serial
+    // type and the value is NULL, skip it.
+    // @see http://php.net/manual/en/function.property-exists.php
+    if ($info['type'] == 'serial' && !isset($object->$field)) {
       $default_fields[] = $field;
       continue;
     }
diff --git a/core/modules/entity/entity.class.inc b/core/modules/entity/entity.class.inc
index 1dec8ee..1f97acd 100644
--- a/core/modules/entity/entity.class.inc
+++ b/core/modules/entity/entity.class.inc
@@ -148,6 +148,20 @@ class Entity implements EntityInterface {
   protected $bundleKey;
 
   /**
+   * Boolean indicating whether the entity is new.
+   *
+   * @var bool
+   */
+  public $is_new;
+
+  /**
+   * The original entity instance loaded prior to saving.
+   *
+   * @var EntityInterface
+   */
+  public $original;
+
+  /**
    * Constructs a new entity object.
    */
   public function __construct(array $values = array(), $entity_type) {
@@ -179,9 +193,15 @@ class Entity implements EntityInterface {
    * Implements EntityInterface::isNew().
    */
   public function isNew() {
-    // We support creating entities with pre-defined IDs to ease migrations.
-    // For that the "is_new" property may be set to TRUE.
-    return !empty($this->is_new) || empty($this->{$this->idKey});
+    // We support creating entities with pre-defined IDs to ease migrations by
+    // pre-setting the "is_new" property to TRUE.
+    // Otherwise, set the "is_new" property based on whether there is an ID,
+    // so especially preSave() and postSave() can rely on the value during
+    // saving of the entity.
+    if (!isset($this->is_new)) {
+      $this->is_new = empty($this->{$this->idKey});
+    }
+    return $this->is_new;
   }
 
   /**
diff --git a/core/modules/entity/tests/entity_crud_hook_test.test b/core/modules/entity/tests/entity_crud_hook_test.test
index 2bb459f..ab28d03 100644
--- a/core/modules/entity/tests/entity_crud_hook_test.test
+++ b/core/modules/entity/tests/entity_crud_hook_test.test
@@ -239,20 +239,20 @@ class EntityCrudHookTestCase extends DrupalWebTestCase {
    * Test hook invocations for CRUD operations on taxonomy terms.
    */
   public function testTaxonomyTermHooks() {
-    $vocabulary = (object) array(
+    $vocabulary = entity_create('taxonomy_vocabulary', array(
       'name' => 'Test vocabulary',
       'machine_name' => 'test',
       'description' => NULL,
       'module' => 'entity_crud_hook_test',
-    );
+    ));
     taxonomy_vocabulary_save($vocabulary);
 
-    $term = (object) array(
+    $term = entity_create('taxonomy_term', array(
       'vid' => $vocabulary->vid,
       'name' => 'Test term',
       'description' => NULL,
       'format' => 1,
-    );
+    ));
     $_SESSION['entity_crud_hook_test'] = array();
     taxonomy_term_save($term);
 
@@ -297,12 +297,12 @@ class EntityCrudHookTestCase extends DrupalWebTestCase {
    * Test hook invocations for CRUD operations on taxonomy vocabularies.
    */
   public function testTaxonomyVocabularyHooks() {
-    $vocabulary = (object) array(
+    $vocabulary = entity_create('taxonomy_vocabulary', array(
       'name' => 'Test vocabulary',
       'machine_name' => 'test',
       'description' => NULL,
       'module' => 'entity_crud_hook_test',
-    );
+    ));
     $_SESSION['entity_crud_hook_test'] = array();
     taxonomy_vocabulary_save($vocabulary);
 
diff --git a/core/modules/field_ui/field_ui.test b/core/modules/field_ui/field_ui.test
index c2e3a98..951d23d 100644
--- a/core/modules/field_ui/field_ui.test
+++ b/core/modules/field_ui/field_ui.test
@@ -160,10 +160,10 @@ class FieldUIManageFieldsTestCase extends FieldUITestCase {
     $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
 
     // Create a vocabulary named "Tags".
-    $vocabulary = (object) array(
+    $vocabulary = entity_create('taxonomy_vocabulary', array(
       'name' => 'Tags',
       'machine_name' => 'tags',
-    );
+    ));
     taxonomy_vocabulary_save($vocabulary);
 
     $field = array(
diff --git a/core/modules/forum/forum.admin.inc b/core/modules/forum/forum.admin.inc
index 49c71d9..028a933 100644
--- a/core/modules/forum/forum.admin.inc
+++ b/core/modules/forum/forum.admin.inc
@@ -79,7 +79,7 @@ function forum_form_submit($form, &$form_state) {
     $type = t('forum');
   }
 
-  $term = (object) $form_state['values'];
+  $term = entity_create('taxonomy_term', $form_state['values']);
   $status = taxonomy_term_save($term);
   switch ($status) {
     case SAVED_NEW:
diff --git a/core/modules/forum/forum.install b/core/modules/forum/forum.install
index 2eebd7f..0b93f4f 100644
--- a/core/modules/forum/forum.install
+++ b/core/modules/forum/forum.install
@@ -30,15 +30,14 @@ function forum_enable() {
   // Create the forum vocabulary if it does not exist.
   $vocabulary = taxonomy_vocabulary_load(variable_get('forum_nav_vocabulary', 0));
   if (!$vocabulary) {
-    $edit = array(
+    $vocabulary = entity_create('taxonomy_vocabulary', array(
       'name' => t('Forums'),
       'machine_name' => 'forums',
       'description' => t('Forum navigation vocabulary'),
       'hierarchy' => 1,
       'module' => 'forum',
       'weight' => -10,
-    );
-    $vocabulary = (object) $edit;
+    ));
     taxonomy_vocabulary_save($vocabulary);
     variable_set('forum_nav_vocabulary', $vocabulary->vid);
   }
@@ -60,13 +59,12 @@ function forum_enable() {
     field_create_field($field);
 
     // Create a default forum so forum posts can be created.
-    $edit = array(
+    $term = entity_create('taxonomy_term', array(
       'name' => t('General discussion'),
       'description' => '',
       'parent' => array(0),
       'vid' => $vocabulary->vid,
-    );
-    $term = (object) $edit;
+    ));
     taxonomy_term_save($term);
 
     // Create the instance on the bundle.
diff --git a/core/modules/simpletest/tests/upgrade/upgrade.test b/core/modules/simpletest/tests/upgrade/upgrade.test
index 29793b2..432d795 100644
--- a/core/modules/simpletest/tests/upgrade/upgrade.test
+++ b/core/modules/simpletest/tests/upgrade/upgrade.test
@@ -296,6 +296,10 @@ abstract class UpgradePathTestCase extends DrupalWebTestCase {
       drupal_load('module', $module);
     }
 
+    // Re-register autoload functions.
+    spl_autoload_register('drupal_autoload_class');
+    spl_autoload_register('drupal_autoload_interface');
+
     // Reload hook implementations
     module_implements_reset();
 
diff --git a/core/modules/taxonomy/taxonomy.admin.inc b/core/modules/taxonomy/taxonomy.admin.inc
index 8541b78..b268b40 100644
--- a/core/modules/taxonomy/taxonomy.admin.inc
+++ b/core/modules/taxonomy/taxonomy.admin.inc
@@ -101,27 +101,22 @@ function theme_taxonomy_overview_vocabularies($variables) {
 /**
  * Form builder for the vocabulary editing form.
  *
+ * @param TaxonomyVocabulary|null $vocabulary
+ *   (optional) The TaxonomyVocabulary entity to edit. If NULL or omitted, the
+ *   form creates a new vocabulary.
+ *
  * @ingroup forms
  * @see taxonomy_form_vocabulary_submit()
  * @see taxonomy_form_vocabulary_validate()
  */
-function taxonomy_form_vocabulary($form, &$form_state, $edit = array()) {
+function taxonomy_form_vocabulary($form, &$form_state, $vocabulary = NULL) {
   // During initial form build, add the entity to the form state for use
   // during form building and processing. During a rebuild, use what is in the
   // form state.
   if (!isset($form_state['vocabulary'])) {
-    $vocabulary = is_object($edit) ? $edit : (object) $edit;
-    $defaults = array(
-      'name' => '',
-      'machine_name' => '',
-      'description' => '',
-      'hierarchy' => TAXONOMY_HIERARCHY_DISABLED,
-      'weight' => 0,
-    );
-    foreach ($defaults as $key => $value) {
-      if (!isset($vocabulary->$key)) {
-        $vocabulary->$key = $value;
-      }
+    // Create a new TaxonomyVocabulary entity for the add form.
+    if (!isset($vocabulary)) {
+      $vocabulary = entity_create('taxonomy_vocabulary', array());
     }
     $form_state['vocabulary'] = $vocabulary;
   }
@@ -632,31 +627,30 @@ function theme_taxonomy_overview_terms($variables) {
 /**
  * Form function for the term edit form.
  *
+ * @param TaxonomyTerm|null $term
+ *   (optional) The TaxonomyTerm entity to edit. If NULL or omitted, the
+ *   form creates a new term.
+ * @param TaxonomyVocabulary|null $vocabulary
+ *   (optional) A TaxonomyVocabulary entity to create the term in.
+ *
  * @ingroup forms
+ * @see taxonomy_form_term_validate()
  * @see taxonomy_form_term_submit()
  */
-function taxonomy_form_term($form, &$form_state, $edit = array(), $vocabulary = NULL) {
+function taxonomy_form_term($form, &$form_state, $term = NULL, $vocabulary = NULL) {
   // During initial form build, add the term entity to the form state for use
   // during form building and processing. During a rebuild, use what is in the
   // form state.
   if (!isset($form_state['term'])) {
-    $term = is_object($edit) ? $edit : (object) $edit;
+    // Create a new TaxonomyTerm entity for the add form.
+    if (!isset($term)) {
+      $term = entity_create('taxonomy_term', array(
+        'vocabulary_machine_name' => isset($vocabulary) ? $vocabulary->machine_name : NULL,
+      ));
+    }
     if (!isset($vocabulary) && isset($term->vid)) {
       $vocabulary = taxonomy_vocabulary_load($term->vid);
     }
-    $defaults = array(
-      'name' => '',
-      'description' => '',
-      'format' => NULL,
-      'vocabulary_machine_name' => isset($vocabulary) ? $vocabulary->machine_name : NULL,
-      'tid' => NULL,
-      'weight' => 0,
-    );
-    foreach ($defaults as $key => $value) {
-      if (!isset($term->$key)) {
-        $term->$key = $value;
-      }
-    }
     $form_state['term'] = $term;
   }
   else {
diff --git a/core/modules/taxonomy/taxonomy.entity.inc b/core/modules/taxonomy/taxonomy.entity.inc
new file mode 100644
index 0000000..ac656ff
--- /dev/null
+++ b/core/modules/taxonomy/taxonomy.entity.inc
@@ -0,0 +1,308 @@
+<?php
+
+/**
+ * @file
+ * Entity classes and controllers for Taxonomy module.
+ */
+
+/**
+ * Defines the taxonomy term entity.
+ */
+class TaxonomyTerm extends Entity {
+
+  /**
+   * The taxonomy term ID.
+   *
+   * @var integer
+   */
+  public $tid;
+
+  /**
+   * The taxonomy vocabulary ID this term belongs to.
+   *
+   * @var integer
+   */
+  public $vid;
+
+  /**
+   * Name of the term.
+   *
+   * @var string
+   */
+  public $name;
+
+  /**
+   * Description of the term.
+   *
+   * @var string
+   */
+  public $description;
+
+  /**
+   * The text format name for the term's description.
+   *
+   * @var string
+   */
+  public $format;
+
+  /**
+   * The weight of this term in relation to other terms of the same vocabulary.
+   *
+   * @var integer
+   */
+  public $weight = 0;
+}
+
+/**
+ * Controller class for taxonomy terms.
+ */
+class TaxonomyTermController extends EntityDatabaseStorageController {
+
+  /**
+   * Overrides EntityDatabaseStorageController::buildQuery().
+   */
+  protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
+    $query = parent::buildQuery($ids, $conditions, $revision_id);
+    $query->addTag('translatable');
+    $query->addTag('term_access');
+    // When name is passed as a condition use LIKE.
+    if (isset($conditions['name'])) {
+      $query_conditions = &$query->conditions();
+      foreach ($query_conditions as $key => $condition) {
+        if ($condition['field'] == 'base.name') {
+          $query_conditions[$key]['operator'] = 'LIKE';
+          $query_conditions[$key]['value'] = db_like($query_conditions[$key]['value']);
+        }
+      }
+    }
+    // Add the machine name field from the {taxonomy_vocabulary} table.
+    $query->innerJoin('taxonomy_vocabulary', 'v', 'base.vid = v.vid');
+    $query->addField('v', 'machine_name', 'vocabulary_machine_name');
+    return $query;
+  }
+
+  /**
+   * Overrides EntityDatabaseStorageController::cacheGet().
+   */
+  protected function cacheGet($ids, $conditions = array()) {
+    $terms = parent::cacheGet($ids, $conditions);
+    // Name matching is case insensitive, note that with some collations
+    // LOWER() and drupal_strtolower() may return different results.
+    foreach ($terms as $term) {
+      $term_values = (array) $term;
+      if (isset($conditions['name']) && drupal_strtolower($conditions['name'] != drupal_strtolower($term_values['name']))) {
+        unset($terms[$term->tid]);
+      }
+    }
+    return $terms;
+  }
+
+  /**
+   * Overrides EntityDatabaseStorageController::postDelete().
+   */
+  protected function postDelete($entities) {
+    parent::postDelete($entities);
+
+    // See if any of the term's children are about to be become orphans.
+    $orphans = array();
+    foreach (array_keys($entities) as $tid) {
+      if ($children = taxonomy_get_children($tid)) {
+        foreach ($children as $child) {
+          // If the term has multiple parents, we don't delete it.
+          $parents = taxonomy_get_parents($child->tid);
+          // Because the parent has already been deleted, the parent count might
+          // be 0.
+          if (count($parents) <= 1) {
+            $orphans[] = $child->tid;
+          }
+        }
+      }
+    }
+
+    // Delete term hierarchy information and static caches after looking up
+    // orphans but before deleting them so that their children/parent
+    // information is consistent.
+    db_delete('taxonomy_term_hierarchy')
+      ->condition('tid', array_keys($entities))
+      ->execute();
+    taxonomy_terms_static_reset();
+
+    if (!empty($orphans)) {
+      taxonomy_term_delete_multiple($orphans);
+    }
+  }
+
+  /**
+   * Overrides EntityDatabaseStorageController::preSave().
+   */
+  protected function preSave(EntityInterface $entity) {
+    parent::preSave($entity);
+
+    // Prevent leading and trailing spaces in term names.
+    $entity->name = trim($entity->name);
+    if (!isset($entity->vocabulary_machine_name)) {
+      $vocabulary = taxonomy_vocabulary_load($entity->vid);
+      $entity->vocabulary_machine_name = $vocabulary->machine_name;
+    }
+
+    // Initialize parent array if not set for new terms.
+    if ($entity->isNew() && !isset($entity->parent)) {
+      $entity->parent = array(0);
+    }
+  }
+
+  /**
+   * Overrides EntityDatabaseStorageController::postSave().
+   */
+  protected function postSave(EntityInterface $entity) {
+    parent::postSave($entity);
+
+    if (isset($entity->parent)) {
+      db_delete('taxonomy_term_hierarchy')
+        ->condition('tid', $entity->tid)
+        ->execute();
+
+      $query = db_insert('taxonomy_term_hierarchy')
+        ->fields(array('tid', 'parent'));
+      foreach ($entity->parent as $parent) {
+        if (is_array($parent)) {
+          // @todo Drop support for nested array parents?
+          foreach ($parent as $tid) {
+            $query->values(array(
+              'tid' => $entity->tid,
+              'parent' => $tid
+            ));
+          }
+        }
+        else {
+          $query->values(array(
+            'tid' => $entity->tid,
+            'parent' => $parent
+          ));
+        }
+      }
+      $query->execute();
+    }
+
+    // Reset the taxonomy term static variables.
+    taxonomy_terms_static_reset();
+  }
+
+}
+
+/**
+ * Defines the taxonomy vocabulary entity.
+ */
+class TaxonomyVocabulary extends Entity {
+
+  /**
+   * The taxonomy vocabulary ID.
+   *
+   * @var integer
+   */
+  public $vid;
+
+  /**
+   * Name of the vocabulary.
+   *
+   * @var string
+   */
+  public $name;
+
+  /**
+   * The vocabulary machine name.
+   *
+   * @var string
+   */
+  public $machine_name;
+
+  /**
+   * Description of the vocabulary.
+   *
+   * @var string
+   */
+  public $description;
+
+
+  /**
+   * The type of hierarchy allowed within the vocabulary.
+   *
+   * Possible values:
+   * - TAXONOMY_HIERARCHY_DISABLED: No parents.
+   * - TAXONOMY_HIERARCHY_SINGLE: Single parent.
+   * - TAXONOMY_HIERARCHY_MULTIPLE: Multiple parents.
+   *
+   * @var integer
+   */
+  public $hierarchy = TAXONOMY_HIERARCHY_DISABLED;
+
+  /**
+   * The weight of this vocabulary in relation to other vocabularies.
+   *
+   * @var integer
+   */
+  public $weight = 0;
+}
+
+/**
+ * Controller class for taxonomy vocabularies.
+ */
+class TaxonomyVocabularyController extends EntityDatabaseStorageController {
+
+  /**
+   * Overrides EntityDatabaseStorageController::buildQuery().
+   */
+  protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
+    $query = parent::buildQuery($ids, $conditions, $revision_id);
+    $query->addTag('translatable');
+    $query->orderBy('base.weight');
+    $query->orderBy('base.name');
+    return $query;
+  }
+
+  /**
+   * Overrides EntityDatabaseStorageController::preSave().
+   */
+  protected function preSave(EntityInterface $entity) {
+    parent::preSave($entity);
+    // Prevent leading and trailing spaces in vocabulary names.
+    if (!empty($entity->name)) {
+      $entity->name = trim($entity->name);
+    }
+  }
+
+  /**
+   * Overrides EntityDatabaseStorageController::postSave().
+   */
+  protected function postSave(EntityInterface $entity) {
+    parent::postSave($entity);
+    if ($entity->isNew()) {
+      taxonomy_vocabulary_static_reset();
+      field_attach_create_bundle('taxonomy_term', $entity->machine_name);
+    }
+    elseif ($entity->original->machine_name != $entity->machine_name) {
+      taxonomy_vocabulary_static_reset(array($entity->id()));
+      field_attach_rename_bundle('taxonomy_term', $entity->original->machine_name, $entity->machine_name);
+    }
+    cache_clear_all();
+  }
+
+  /**
+   * Overrides EntityDatabaseStorageController::preDelete().
+   */
+  protected function preDelete($entities) {
+    parent::preDelete($entities);
+
+    // Only load terms without a parent, child terms will get deleted too.
+    $tids = db_query('SELECT t.tid FROM {taxonomy_term_data} t INNER JOIN {taxonomy_term_hierarchy} th ON th.tid = t.tid WHERE t.vid IN (:vids) AND th.parent = 0', array(':vids' => array_keys($entities)))->fetchCol();
+    taxonomy_term_delete_multiple($tids);
+  }
+
+  /**
+   * Overrides EntityDatabaseStorageController::postDelete().
+   */
+  protected function postDelete($entities) {
+    parent::postDelete($entities);
+    taxonomy_vocabulary_static_reset();
+  }
+}
diff --git a/core/modules/taxonomy/taxonomy.info b/core/modules/taxonomy/taxonomy.info
index 6a13f81..197a2ae 100644
--- a/core/modules/taxonomy/taxonomy.info
+++ b/core/modules/taxonomy/taxonomy.info
@@ -5,6 +5,6 @@ version = VERSION
 core = 8.x
 dependencies[] = options
 dependencies[] = entity
-files[] = taxonomy.module
+files[] = taxonomy.entity.inc
 files[] = taxonomy.test
 configure = admin/structure/taxonomy
diff --git a/core/modules/taxonomy/taxonomy.module b/core/modules/taxonomy/taxonomy.module
index 581f440..9a6c0a1 100644
--- a/core/modules/taxonomy/taxonomy.module
+++ b/core/modules/taxonomy/taxonomy.module
@@ -107,6 +107,7 @@ function taxonomy_entity_info() {
   $return = array(
     'taxonomy_term' => array(
       'label' => t('Taxonomy term'),
+      'entity class' => 'TaxonomyTerm',
       'controller class' => 'TaxonomyTermController',
       'base table' => 'taxonomy_term_data',
       'uri callback' => 'taxonomy_term_uri',
@@ -142,6 +143,7 @@ function taxonomy_entity_info() {
   }
   $return['taxonomy_vocabulary'] = array(
     'label' => t('Taxonomy vocabulary'),
+    'entity class' => 'TaxonomyVocabulary',
     'controller class' => 'TaxonomyVocabularyController',
     'base table' => 'taxonomy_vocabulary',
     'entity keys' => array(
@@ -363,7 +365,7 @@ function taxonomy_menu() {
   $items['admin/structure/taxonomy/%taxonomy_vocabulary_machine_name/add'] = array(
     'title' => 'Add term',
     'page callback' => 'drupal_get_form',
-    'page arguments' => array('taxonomy_form_term', array(), 3),
+    'page arguments' => array('taxonomy_form_term', NULL, 3),
     'access arguments' => array('administer taxonomy'),
     'type' => MENU_LOCAL_ACTION,
     'file' => 'taxonomy.admin.inc',
@@ -399,112 +401,32 @@ function taxonomy_admin_vocabulary_title_callback($vocabulary) {
 /**
  * Saves a vocabulary.
  *
- * @param $vocabulary
- *   A vocabulary object with the following properties:
- *   - vid: The ID of the vocabulary.
- *   - name: The human-readable name of the vocabulary.
- *   - machine_name: The machine name of the vocabulary.
- *   - description: (optional) The vocabulary's description.
- *   - hierarchy: The hierarchy level of the vocabulary.
- *   - module: (optional) The module altering the vocabulary.
- *   - weight: (optional) The weight of this vocabulary in relation to other
- *     vocabularies.
- *   - original: (optional) The original vocabulary object before any changes
- *     are applied.
- *   - old_machine_name: (optional) The original machine name of the
- *     vocabulary.
- *
- * @return
- *   Status constant indicating whether the vocabulary was inserted (SAVED_NEW)
- *   or updated(SAVED_UPDATED).
+ * @param TaxonomyVocabulary $vocabulary
+ *   The TaxonomyVocabulary instance to be saved.
  */
 function taxonomy_vocabulary_save($vocabulary) {
-  // Prevent leading and trailing spaces in vocabulary names.
-  if (!empty($vocabulary->name)) {
-    $vocabulary->name = trim($vocabulary->name);
-  }
-  // Load the stored entity, if any.
-  if (!empty($vocabulary->vid)) {
-    if (!isset($vocabulary->original)) {
-      $vocabulary->original = entity_load_unchanged('taxonomy_vocabulary', $vocabulary->vid);
-    }
-    // Make sure machine name changes are easily detected.
-    // @todo: Remove in Drupal 8, as it is deprecated by directly reading from
-    // $vocabulary->original.
-    $vocabulary->old_machine_name = $vocabulary->original->machine_name;
-  }
-
-  module_invoke_all('taxonomy_vocabulary_presave', $vocabulary);
-  module_invoke_all('entity_presave', $vocabulary, 'taxonomy_vocabulary');
-
-  if (!empty($vocabulary->vid) && !empty($vocabulary->name)) {
-    $status = drupal_write_record('taxonomy_vocabulary', $vocabulary, 'vid');
-    taxonomy_vocabulary_static_reset(array($vocabulary->vid));
-    if ($vocabulary->old_machine_name != $vocabulary->machine_name) {
-      field_attach_rename_bundle('taxonomy_term', $vocabulary->old_machine_name, $vocabulary->machine_name);
-    }
-    module_invoke_all('taxonomy_vocabulary_update', $vocabulary);
-    module_invoke_all('entity_update', $vocabulary, 'taxonomy_vocabulary');
-  }
-  elseif (empty($vocabulary->vid)) {
-    $status = drupal_write_record('taxonomy_vocabulary', $vocabulary);
-    taxonomy_vocabulary_static_reset();
-    field_attach_create_bundle('taxonomy_term', $vocabulary->machine_name);
-    module_invoke_all('taxonomy_vocabulary_insert', $vocabulary);
-    module_invoke_all('entity_insert', $vocabulary, 'taxonomy_vocabulary');
-  }
-
-  unset($vocabulary->original);
-  cache_clear_all();
-
-  return $status;
+  return $vocabulary->save();
 }
 
 /**
- * Delete a vocabulary.
+ * Deletes a vocabulary.
  *
  * @param $vid
  *   A vocabulary ID.
- * @return
- *   Constant indicating items were deleted.
  *
- * @see hook_taxonomy_vocabulary_predelete()
- * @see hook_taxonomy_vocabulary_delete()
  */
 function taxonomy_vocabulary_delete($vid) {
-  $vocabulary = taxonomy_vocabulary_load($vid);
-
-  $transaction = db_transaction();
-  try {
-    // Allow modules to act before vocabulary deletion.
-    module_invoke_all('taxonomy_vocabulary_predelete', $vocabulary);
-    module_invoke_all('entity_predelete', $vocabulary, 'taxonomy_vocabulary');
-
-    // Only load terms without a parent, child terms will get deleted too.
-    $result = db_query('SELECT t.tid FROM {taxonomy_term_data} t INNER JOIN {taxonomy_term_hierarchy} th ON th.tid = t.tid WHERE t.vid = :vid AND th.parent = 0', array(':vid' => $vid))->fetchCol();
-    foreach ($result as $tid) {
-      taxonomy_term_delete($tid);
-    }
-    db_delete('taxonomy_vocabulary')
-      ->condition('vid', $vid)
-      ->execute();
-
-    field_attach_delete_bundle('taxonomy_term', $vocabulary->machine_name);
-
-    // Allow modules to respond to vocabulary deletion.
-    module_invoke_all('taxonomy_vocabulary_delete', $vocabulary);
-    module_invoke_all('entity_delete', $vocabulary, 'taxonomy_vocabulary');
-
-    cache_clear_all();
-    taxonomy_vocabulary_static_reset();
+  taxonomy_vocabulary_delete_multiple(array($vid));
+}
 
-    return SAVED_DELETED;
-  }
-  catch (Exception $e) {
-    $transaction->rollback();
-    watchdog_exception('taxonomy', $e);
-    throw $e;
-  }
+/**
+ * Deletes vocabularies.
+ *
+ * @param $vids
+ *   The vocabulary ids.
+ */
+function taxonomy_vocabulary_delete_multiple(array $vids) {
+  entity_delete_multiple('taxonomy_vocabulary', $vids);
 }
 
 /**
@@ -513,13 +435,13 @@ function taxonomy_vocabulary_delete($vid) {
 function taxonomy_taxonomy_vocabulary_update($vocabulary) {
   // Reflect machine name changes in the definitions of existing 'taxonomy'
   // fields.
-  if (!empty($vocabulary->old_machine_name) && $vocabulary->old_machine_name != $vocabulary->machine_name) {
+  if (!empty($vocabulary->original->machine_name) && $vocabulary->original->machine_name != $vocabulary->machine_name) {
     $fields = field_read_fields();
     foreach ($fields as $field_name => $field) {
       $update = FALSE;
       if ($field['type'] == 'taxonomy_term_reference') {
         foreach ($field['settings']['allowed_values'] as $key => &$value) {
-          if ($value['vocabulary'] == $vocabulary->old_machine_name) {
+          if ($value['vocabulary'] == $vocabulary->original->machine_name) {
             $value['vocabulary'] = $vocabulary->machine_name;
             $update = TRUE;
           }
@@ -543,7 +465,7 @@ function taxonomy_taxonomy_vocabulary_update($vocabulary) {
  * term has multiple parents then the vocabulary will be given a hierarchy of
  * TAXONOMY_HIERARCHY_MULTIPLE.
  *
- * @param $vocabulary
+ * @param TaxonomyVocabulary $vocabulary
  *   A vocabulary object.
  * @param $changed_term
  *   An array of the term structure that was updated.
@@ -580,28 +502,8 @@ function taxonomy_check_vocabulary_hierarchy($vocabulary, $changed_term) {
 /**
  * Saves a term object to the database.
  *
- * @param $term
- *   The taxonomy term object with the following properties:
- *   - vid: The ID of the vocabulary the term is assigned to.
- *   - name: The name of the term.
- *   - tid: (optional) The unique ID for the term being saved. If $term->tid is
- *     empty or omitted, a new term will be inserted.
- *   - description: (optional) The term's description.
- *   - format: (optional) The text format for the term's description.
- *   - weight: (optional) The weight of this term in relation to other terms
- *     within the same vocabulary.
- *   - parent: (optional) The parent term(s) for this term. This can be a single
- *     term ID or an array of term IDs. A value of 0 means this term does not
- *     have any parents. When omitting this variable during an update, the
- *     existing hierarchy for the term remains unchanged.
- *   - vocabulary_machine_name: (optional) The machine name of the vocabulary
- *     the term is assigned to. If not given, this value will be set
- *     automatically by loading the vocabulary based on $term->vid.
- *   - original: (optional) The original taxonomy term object before any changes
- *     were applied. When omitted, the unchanged taxonomy term object is
- *     loaded from the database and stored in this property.
- *   Since a taxonomy term is an entity, any fields contained in the term object
- *   are saved alongside the term object.
+ * @param TaxonomyTerm $term
+ *   The TaxonomyTerm object to be saved.
  *
  * @return
  *   Status constant indicating whether term was inserted (SAVED_NEW) or updated
@@ -609,138 +511,27 @@ function taxonomy_check_vocabulary_hierarchy($vocabulary, $changed_term) {
  *   term ID of the newly created term.
  */
 function taxonomy_term_save($term) {
-  // Prevent leading and trailing spaces in term names.
-  $term->name = trim($term->name);
-  if (!isset($term->vocabulary_machine_name)) {
-    $vocabulary = taxonomy_vocabulary_load($term->vid);
-    $term->vocabulary_machine_name = $vocabulary->machine_name;
-  }
-
-  // Load the stored entity, if any.
-  if (!empty($term->tid) && !isset($term->original)) {
-    $term->original = entity_load_unchanged('taxonomy_term', $term->tid);
-  }
-
-  field_attach_presave('taxonomy_term', $term);
-  module_invoke_all('taxonomy_term_presave', $term);
-  module_invoke_all('entity_presave', $term, 'taxonomy_term');
-
-  if (empty($term->tid)) {
-    $op = 'insert';
-    $status = drupal_write_record('taxonomy_term_data', $term);
-    field_attach_insert('taxonomy_term', $term);
-    if (!isset($term->parent)) {
-      $term->parent = array(0);
-    }
-  }
-  else {
-    $op = 'update';
-    $status = drupal_write_record('taxonomy_term_data', $term, 'tid');
-    field_attach_update('taxonomy_term', $term);
-    if (isset($term->parent)) {
-      db_delete('taxonomy_term_hierarchy')
-        ->condition('tid', $term->tid)
-        ->execute();
-    }
-  }
-
-  if (isset($term->parent)) {
-    if (!is_array($term->parent)) {
-      $term->parent = array($term->parent);
-    }
-    $query = db_insert('taxonomy_term_hierarchy')
-      ->fields(array('tid', 'parent'));
-    foreach ($term->parent as $parent) {
-      if (is_array($parent)) {
-        foreach ($parent as $tid) {
-          $query->values(array(
-            'tid' => $term->tid,
-            'parent' => $tid
-          ));
-        }
-      }
-      else {
-        $query->values(array(
-          'tid' => $term->tid,
-          'parent' => $parent
-        ));
-      }
-    }
-    $query->execute();
-  }
-
-  // Reset the taxonomy term static variables.
-  taxonomy_terms_static_reset();
-
-  // Invoke the taxonomy hooks.
-  module_invoke_all("taxonomy_term_$op", $term);
-  module_invoke_all("entity_$op", $term, 'taxonomy_term');
-  unset($term->original);
-
-  return $status;
+  return $term->save();
 }
 
 /**
- * Delete a term.
+ * Deletes a term.
  *
  * @param $tid
  *   The term ID.
- * @return
- *   Status constant indicating deletion.
- *
- * @see hook_taxonomy_term_predelete()
- * @see hook_taxonomy_term_delete()
  */
 function taxonomy_term_delete($tid) {
-  $transaction = db_transaction();
-  try {
-    $tids = array($tid);
-    while ($tids) {
-      $children_tids = $orphans = array();
-      foreach ($tids as $tid) {
-        // Allow modules to act before term deletion.
-        if ($term = taxonomy_term_load($tid)) {
-          module_invoke_all('taxonomy_term_predelete', $term);
-          module_invoke_all('entity_predelete', $term, 'taxonomy_term');
-        }
-
-        // See if any of the term's children are about to be become orphans:
-        if ($children = taxonomy_get_children($tid)) {
-          foreach ($children as $child) {
-            // If the term has multiple parents, we don't delete it.
-            $parents = taxonomy_get_parents($child->tid);
-            if (count($parents) == 1) {
-              $orphans[] = $child->tid;
-            }
-          }
-        }
-
-        if ($term) {
-          db_delete('taxonomy_term_data')
-            ->condition('tid', $tid)
-            ->execute();
-          db_delete('taxonomy_term_hierarchy')
-            ->condition('tid', $tid)
-            ->execute();
-
-          field_attach_delete('taxonomy_term', $term);
-
-          // Allow modules to respond to term deletion.
-          module_invoke_all('taxonomy_term_delete', $term);
-          module_invoke_all('entity_delete', $term, 'taxonomy_term');
-          taxonomy_terms_static_reset();
-        }
-      }
+  taxonomy_term_delete_multiple(array($tid));
+}
 
-      $tids = $orphans;
-    }
-    return SAVED_DELETED;
-  }
-  catch (Exception $e) {
-    $transaction->rollback();
-    watchdog_exception('taxonomy', $e);
-    throw $e;
-  }
+/**
+ * Deletes taxonomy terms.
+ *
+ * @param $tids
+ *   The term ids to be deleted.
+ */
+function taxonomy_term_delete_multiple(array $tids) {
+  entity_delete_multiple('taxonomy_term', $tids);
 }
 
 /**
@@ -858,9 +649,9 @@ function taxonomy_terms_static_reset() {
  * Clear all static cache variables for vocabularies.
  *
  * @param $ids
- * An array of ids to reset in entity controller cache.
+ *   An array of ids to reset in entity controller cache.
  */
-function taxonomy_vocabulary_static_reset($ids = NULL) {
+function taxonomy_vocabulary_static_reset(array $ids = NULL) {
   drupal_static_reset('taxonomy_vocabulary_get_names');
   entity_get_controller('taxonomy_vocabulary')->resetCache($ids);
 }
@@ -1125,65 +916,6 @@ function taxonomy_get_term_by_name($name, $vocabulary = NULL) {
 }
 
 /**
- * Controller class for taxonomy terms.
- *
- * This extends the DrupalDefaultEntityController class. Only alteration is
- * that we match the condition on term name case-independently.
- */
-class TaxonomyTermController extends DrupalDefaultEntityController {
-
-  protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
-    $query = parent::buildQuery($ids, $conditions, $revision_id);
-    $query->addTag('translatable');
-    $query->addTag('term_access');
-    // When name is passed as a condition use LIKE.
-    if (isset($conditions['name'])) {
-      $query_conditions = &$query->conditions();
-      foreach ($query_conditions as $key => $condition) {
-        if ($condition['field'] == 'base.name') {
-          $query_conditions[$key]['operator'] = 'LIKE';
-          $query_conditions[$key]['value'] = db_like($query_conditions[$key]['value']);
-        }
-      }
-    }
-    // Add the machine name field from the {taxonomy_vocabulary} table.
-    $query->innerJoin('taxonomy_vocabulary', 'v', 'base.vid = v.vid');
-    $query->addField('v', 'machine_name', 'vocabulary_machine_name');
-    return $query;
-  }
-
-  protected function cacheGet($ids, $conditions = array()) {
-    $terms = parent::cacheGet($ids, $conditions);
-    // Name matching is case insensitive, note that with some collations
-    // LOWER() and drupal_strtolower() may return different results.
-    foreach ($terms as $term) {
-      $term_values = (array) $term;
-      if (isset($conditions['name']) && drupal_strtolower($conditions['name'] != drupal_strtolower($term_values['name']))) {
-        unset($terms[$term->tid]);
-      }
-    }
-    return $terms;
-  }
-}
-
-/**
- * Controller class for taxonomy vocabularies.
- *
- * This extends the DrupalDefaultEntityController class, adding required
- * special handling for taxonomy vocabulary objects.
- */
-class TaxonomyVocabularyController extends DrupalDefaultEntityController {
-
-  protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) {
-    $query = parent::buildQuery($ids, $conditions, $revision_id);
-    $query->addTag('translatable');
-    $query->orderBy('base.weight');
-    $query->orderBy('base.name');
-    return $query;
-  }
-}
-
-/**
  * Load multiple taxonomy terms based on certain conditions.
  *
  * This function should be used whenever you need to load more than one term
@@ -1767,7 +1499,7 @@ function taxonomy_rdf_mapping() {
 function taxonomy_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
   foreach ($items as $delta => $item) {
     if ($item['tid'] == 'autocreate') {
-      $term = (object) $item;
+      $term = entity_create('taxonomy_term', $item);
       unset($term->tid);
       taxonomy_term_save($term);
       $items[$delta]['tid'] = $term->tid;
diff --git a/core/modules/taxonomy/taxonomy.test b/core/modules/taxonomy/taxonomy.test
index b992903..a7abf44 100644
--- a/core/modules/taxonomy/taxonomy.test
+++ b/core/modules/taxonomy/taxonomy.test
@@ -15,13 +15,14 @@ class TaxonomyWebTestCase extends DrupalWebTestCase {
    */
   function createVocabulary() {
     // Create a vocabulary.
-    $vocabulary = new stdClass();
-    $vocabulary->name = $this->randomName();
-    $vocabulary->description = $this->randomName();
-    $vocabulary->machine_name = drupal_strtolower($this->randomName());
-    $vocabulary->help = '';
-    $vocabulary->nodes = array('article' => 'article');
-    $vocabulary->weight = mt_rand(0, 10);
+    $vocabulary = entity_create('taxonomy_vocabulary', array(
+      'name' => $this->randomName(),
+      'description' => $this->randomName(),
+      'machine_name' => drupal_strtolower($this->randomName()),
+      'help' => '',
+      'nodes' => array('article' => 'article'),
+      'weight' => mt_rand(0, 10),
+    ));
     taxonomy_vocabulary_save($vocabulary);
     return $vocabulary;
   }
@@ -30,12 +31,13 @@ class TaxonomyWebTestCase extends DrupalWebTestCase {
    * Returns a new term with random properties in vocabulary $vid.
    */
   function createTerm($vocabulary) {
-    $term = new stdClass();
-    $term->name = $this->randomName();
-    $term->description = $this->randomName();
-    // Use the first available text format.
-    $term->format = db_query_range('SELECT format FROM {filter_format}', 0, 1)->fetchField();
-    $term->vid = $vocabulary->vid;
+    $term = entity_create('taxonomy_term', array(
+      'name' => $this->randomName(),
+      'description' => $this->randomName(),
+      // Use the first available text format.
+      'format' => db_query_range('SELECT format FROM {filter_format}', 0, 1)->fetchField(),
+      'vid' => $vocabulary->vid,
+    ));
     taxonomy_term_save($term);
     return $term;
   }
@@ -526,7 +528,6 @@ class TaxonomyTermTestCase extends TaxonomyWebTestCase {
 
     // Load and save a term, confirming that parents are still set.
     $term = taxonomy_term_load($term2->tid);
-    taxonomy_term_save($term);
     $parents = taxonomy_get_parents($term2->tid);
     $this->assertTrue(isset($parents[$term1->tid]), t('Parent found correctly.'));
 
@@ -1117,7 +1118,7 @@ class TaxonomyLoadMultipleUnitTest extends TaxonomyWebTestCase {
 
     // Load the terms by tid, with a condition on vid.
     $terms3 = taxonomy_term_load_multiple(array_keys($terms2), array('vid' => $vocabulary->vid));
-    $this->assertEqual($terms2, $terms3);
+    $this->assertEqual($terms2, $terms3, 'Same terms found when limiting load to vocabulary.');
 
     // Remove one term from the array, then delete it.
     $deleted = array_shift($terms3);
diff --git a/core/modules/trigger/trigger.test b/core/modules/trigger/trigger.test
index 9a9a4ba..89bfb1b 100644
--- a/core/modules/trigger/trigger.test
+++ b/core/modules/trigger/trigger.test
@@ -662,7 +662,7 @@ class TriggerOtherTestCase extends TriggerWebTestCase {
     // Create a taxonomy vocabulary and add a term to it.
 
     // Create a vocabulary.
-    $vocabulary = new stdClass();
+    $vocabulary = entity_create('taxonomy_vocabulary', array());
     $vocabulary->name = $this->randomName();
     $vocabulary->description = $this->randomName();
     $vocabulary->machine_name = drupal_strtolower($this->randomName());
@@ -671,7 +671,7 @@ class TriggerOtherTestCase extends TriggerWebTestCase {
     $vocabulary->weight = mt_rand(0, 10);
     taxonomy_vocabulary_save($vocabulary);
 
-    $term = new stdClass();
+    $term = entity_create('taxonomy_term', array());
     $term->name = $this->randomName();
     $term->vid = $vocabulary->vid;
     taxonomy_term_save($term);
diff --git a/core/scripts/generate-d7-content.sh b/core/scripts/generate-d7-content.sh
index e65c099..ccb0b46 100644
--- a/core/scripts/generate-d7-content.sh
+++ b/core/scripts/generate-d7-content.sh
@@ -66,16 +66,17 @@ $required  = array(0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1);
 $voc_id = 0;
 $term_id = 0;
 for ($i = 0; $i < 24; $i++) {
-  $vocabulary = new stdClass;
   ++$voc_id;
-  $vocabulary->name = "vocabulary $voc_id (i=$i)";
-  $vocabulary->machine_name = 'vocabulary_' . $voc_id . '_' . $i;
-  $vocabulary->description = "description of ". $vocabulary->name;
-  $vocabulary->multiple = $multiple[$i % 12];
-  $vocabulary->required = $required[$i % 12];
-  $vocabulary->relations = 1;
-  $vocabulary->hierarchy = $hierarchy[$i % 12];
-  $vocabulary->weight = $i;
+  $vocabulary = entity_create('taxonomy_vocabulary', array(
+    'name' => "vocabulary $voc_id (i=$i)",
+    'machine_name' => 'vocabulary_' . $voc_id . '_' . $i,
+    'description' => "description of " . $vocabulary->name,
+    'multiple' => $multiple[$i % 12],
+    'required' => $required[$i % 12],
+    'relations' => 1,
+    'hierarchy' => $hierarchy[$i % 12],
+    'weight' => $i,
+  ));
   taxonomy_vocabulary_save($vocabulary);
   $field = array(
     'field_name' => 'taxonomy_'. $vocabulary->machine_name,
@@ -139,16 +140,17 @@ for ($i = 0; $i < 24; $i++) {
   // one parent and one child term. Multiple parent vocabularies get three
   // terms: t0, t1, t2 where t0 is a parent of both t1 and t2.
   for ($j = 0; $j < $vocabulary->hierarchy + 1; $j++) {
-    $term = new stdClass;
-    $term->vocabulary_machine_name = $vocabulary->machine_name;
-    // For multiple parent vocabularies, omit the t0-t1 relation, otherwise
-    // every parent in the vocabulary is a parent.
-    $term->parent = $vocabulary->hierarchy == 2 && i == 1 ? array() : $parents;
     ++$term_id;
-    $term->name = "term $term_id of vocabulary $voc_id (j=$j)";
-    $term->description = 'description of ' . $term->name;
-    $term->format = 'filtered_html';
-    $term->weight = $i * 3 + $j;
+    $term = entity_create('taxonomy_term', array(
+      'vocabulary_machine_name' => $vocabulary->machine_name,
+      // For multiple parent vocabularies, omit the t0-t1 relation, otherwise
+      // every parent in the vocabulary is a parent.
+      'parent' => $vocabulary->hierarchy == 2 && i == 1 ? array() : $parents,
+      'name' => "term $term_id of vocabulary $voc_id (j=$j)",
+      'description' => 'description of ' . $term->name,
+      'format' => 'filtered_html',
+      'weight' => $i * 3 + $j,
+    ));
     taxonomy_term_save($term);
     $terms[] = $term->tid;
     $term_vocabs[$term->tid] = 'taxonomy_' . $vocabulary->machine_name;
diff --git a/profiles/standard/standard.install b/profiles/standard/standard.install
index e570c18..ac43779 100644
--- a/profiles/standard/standard.install
+++ b/profiles/standard/standard.install
@@ -279,13 +279,12 @@ function standard_install() {
   // Create a default vocabulary named "Tags", enabled for the 'article' content type.
   $description = st('Use tags to group articles on similar topics into categories.');
   $help = st('Enter a comma-separated list of words to describe your content.');
-  $vocabulary = (object) array(
+  $vocabulary = entity_create('taxonomy_vocabulary', array(
     'name' => st('Tags'),
     'description' => $description,
     'machine_name' => 'tags',
     'help' => $help,
-
-  );
+  ));
   taxonomy_vocabulary_save($vocabulary);
 
   $field = array(
