diff --git a/CHANGELOG.md b/CHANGELOG.md
index b5a0e729877076c966b2ffa207122ea46032b2bf..33b62a8f20eadc475ab5a95b30e9a6eb2b2e345d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,14 +8,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 ## [Unreleased] ##
 
 ### Added ###
+* `ParentList` and `PropertyList` now have a `filter` function that allows to select a subset of
+  the contained elements by ID and/or name.
 
 * Official support for Python 3.13
 * Optional `realm` argument for `linkahead_admin.py set_user_password`
   which defaults to `None`, i.e., the server's default realm.
 
 ### Changed ###
+* `compare_entities` is now case insensitive with respect to property and
+  recordtype names
+* `_ParentList` is now called `ParentList`
+* `_Properties` is now called `PropertyList`
+* `ParentList.remove` is now case insensitive when a name is used.
 
 ### Deprecated ###
+* the use of the arguments `old_entity` and `new_entity` in `compare_entities`
+  is now deprecated. Please use `entity0` and `entity1` respectively instead.
 
 ### Removed ###
 
diff --git a/src/doc/tutorials/complex_data_models.rst b/src/doc/tutorials/complex_data_models.rst
index 569acdae174a9df9d0d2b5eae9a0084d793cc90c..b33bc9ae49e9a850a73e32e396735652ecaab7d7 100644
--- a/src/doc/tutorials/complex_data_models.rst
+++ b/src/doc/tutorials/complex_data_models.rst
@@ -75,3 +75,58 @@ Examples
    b = input("Press any key to cleanup.")
    # cleanup everything after the user presses any button.
    c.delete()
+
+
+Finding parents and properties
+--------
+To find a specific parent or property of an Entity, its
+ParentList or PropertyList can be filtered using names, ids, or
+entities. A short example:
+
+.. code-block:: python3
+
+   import linkahead as db
+
+   # Setup a record with four properties
+   r = db.Record()
+   p1 = db.Property(id=101, name="Property 1")
+   p2 = db.Property(id=102, name="Property 2")
+   p3_1 = db.Property(id=103, name="Property 3")
+   p3_2 = db.Property(id=103, name="Property 3")
+   p4 = db.Property(name="Property")
+   p5 = db.Property(name="Property")
+   r.add_property(p1).add_property(p2).add_property(p3_1)
+   r.add_property(p3_2).add_property(p4).add_property(p5)
+   properties = r.properties
+
+   # As r only has one property with id 101, this returns a list containing only p1
+   properties.filter(pid=101)
+   # Result: [p1]
+
+   # Filtering with name="Property" returns both p4 and p5, as they share their name
+   properties.filter(name="Property")
+   # Result: [p4, p5]
+
+   # Filtering with name="Property 1" and id=102 returns both p1 and p2, because
+   # any property matching either criterion is returned:
+   properties.filter(name="Property 1", pid="102")
+   # Result: [p1, p2]
+
+   p6 = db.Property(name="Property 2")
+   r.add_property(p6)
+   # If we want to find properties matching one specific property, we can also filter using
+   # the entity itself. In this case, only properties matching both name and id are returned,
+   # as long as both are set.
+   properties.filter(p2)
+   # Result: [p2]
+   # As p6 does not have an id yet, both candidates matching its name are returned
+   properties.filter(p6)
+   # Result: [p2, p6]
+   # Similarly if we match using name and id parameters, all candidates matching either are returned
+   properties.filter(name=p2.name, pid=p2.id)
+   # Result: [p2, p6], because p2 and p6 share a name
+   # And if both name and id match, there may also be several results when matching an entity
+   properties.filter(p3_1)
+   # Result: [p3_1, p3_2], because they share both their name and id
+
+The filter function of ParentList works analogously.
diff --git a/src/linkahead/__init__.py b/src/linkahead/__init__.py
index cd54f8f4e05326579521fbbf226f027d32fa616e..567748e3b3a58fb73b91f652d82ed10f818d6014 100644
--- a/src/linkahead/__init__.py
+++ b/src/linkahead/__init__.py
@@ -42,7 +42,7 @@ from .common.datatype import (BOOLEAN, DATETIME, DOUBLE, FILE, INTEGER, LIST,
                               REFERENCE, TEXT)
 # Import of the basic  API classes:
 from .common.models import (ACL, ALL, FIX, NONE, OBLIGATORY, RECOMMENDED,
-                            SUGGESTED, Container, DropOffBox, Entity, File,
+                            SUGGESTED, Container, DropOffBox, Entity, File, Parent,
                             Info, Message, Permissions, Property, Query,
                             QueryTemplate, Record, RecordType, delete,
                             execute_query, get_global_acl,
diff --git a/src/linkahead/apiutils.py b/src/linkahead/apiutils.py
index 4307caa531f2a2a0d8e68dd4ced5240e0e2c5b83..d51171c7c59fd0ae8ee202db224f2597f3e9cdae 100644
--- a/src/linkahead/apiutils.py
+++ b/src/linkahead/apiutils.py
@@ -28,10 +28,11 @@
 
 """
 from __future__ import annotations
+
 import logging
 import warnings
 from collections.abc import Iterable
-from typing import Any, Union, Optional
+from typing import Any, Optional, Union
 
 from .common.datatype import is_reference
 from .common.models import (SPECIAL_ATTRIBUTES, Container, Entity, File,
@@ -179,19 +180,27 @@ def getCommitIn(folder):
     return get_commit_in(folder)
 
 
-def compare_entities(old_entity: Entity,
-                     new_entity: Entity,
-                     compare_referenced_records: bool = False
+def compare_entities(entity0: Optional[Entity] = None,
+                     entity1: Optional[Entity] = None,
+                     compare_referenced_records: bool = False,
+                     entity_name_id_equivalency: bool = False,
+                     old_entity: Optional[Entity] = None,
+                     new_entity: Optional[Entity] = None,
                      ) -> tuple[dict[str, Any], dict[str, Any]]:
-    """Compare two entites.
-
-    Return a tuple of dictionaries, the first index belongs to additional information for old
-    entity, the second index belongs to additional information for new entity.
-
-    Additional information means in detail:
-    - Additional parents (a list under key "parents")
-    - Information about properties:
-      - Each property lists either an additional property or a property with a changed:
+    """Compare two entities.
+
+    Returns two dicts listing the differences between the two entities. The
+    order of the two returned dicts corresponds to the two input entities.
+    The dicts contain two keys, 'parents' and 'properties'. The list saved
+    under the 'parents' key contains those parents of the respective entity
+    that are missing in the other entity, and the 'properties' dict contains
+    properties and SPECIAL_ATTRIBUTES if they are missing or different from
+    their counterparts in the other entity.
+
+    The value of the properties dict for each listed property is again a dict
+    detailing the differences between this property and its counterpart.
+    The characteristics that are checked to determine whether two properties
+    match are the following:
         - datatype
         - importance
         - value
@@ -202,152 +211,241 @@ def compare_entities(old_entity: Entity,
     value is not added to the dict.
     If a property is of type LIST, the comparison is order-sensitive.
 
-        In case of changed information the value listed under the respective key shows the
-        value that is stored in the respective entity.
+    Comparison of multi-properties is not yet supported, so should either
+    entity have several instances of one Property, the comparison is aborted
+    and an error is raised.
 
-    If `compare_referenced_records` is `True`, also referenced entities will be
-    compared using this function (which is then called with
-    `compare_referenced_records = False` to prevent infinite recursion in case
-    of circular references).
+    Two parents match if their name and id are the same, any further
+    differences are ignored.
 
-    Parameters
-    ----------
-    old_entity, new_entity : Entity
-        Entities to be compared
-    compare_referenced_records : bool, optional
-        Whether to compare referenced records in case of both, `old_entity` and
-        `new_entity`, have the same reference properties and both have a Record
-        object as value. If set to `False`, only the corresponding Python
-        objects are compared which may lead to unexpected behavior when
-        identical records are stored in different objects. Default is False.
+    Should records referenced in the value field not be checked for equality
+    between the entities but for equivalency, this is possible by setting the
+    parameter compare_referenced_records.
 
+    Params
+    ------
+    entity0                : Entity
+                                First entity to be compared.
+    entity1                : Entity
+                                Second entity to be compared.
+    compare_referenced_records: bool, default: False
+                                If set to True, values with referenced records
+                                are not checked for equality but for
+                                equivalency using this function.
+                                compare_referenced_records is set to False for
+                                these recursive calls, so references of
+                                references need to be equal. If set to `False`,
+                                only the Python objects are compared, which may
+                                lead to unexpected behavior.
+    entity_name_id_equivalency: bool, default: False
+                                If set to True, the comparison between an
+                                entity and an int or str also checks whether
+                                the int/str matches the name or id of the
+                                entity, so Entity(id=100) == 100 == "100".
     """
-    olddiff: dict[str, Any] = {"properties": {}, "parents": []}
-    newdiff: dict[str, Any] = {"properties": {}, "parents": []}
-
-    if old_entity is new_entity:
-        return (olddiff, newdiff)
-
-    if type(old_entity) is not type(new_entity):
+    # ToDo: Discuss intended behaviour
+    # Questions that need clarification:
+    #    - What is intended behaviour for multi-properties and multi-parents?
+    #    - Do different inheritance levels for parents count as a difference?
+    #    - Do we care about parents and properties of properties?
+    #    - Should there be a more detailed comparison of parents without id?
+    #    - Revisit filter - do we care about RecordType when matching?
+    #      How to treat None?
+    # Suggestions for enhancements:
+    #    - For the comparison of entities in value and properties, consider
+    #      keeping a list of traversed entities, not only look at first layer
+    #    - Make the empty_diff functionality faster by adding a parameter to
+    #      this function so that it returns after the first found difference?
+    #    - Add parameter to restrict diff to some characteristics
+    if entity0 is None and old_entity is None:
+        raise ValueError("Please provide the first entity as first argument (`entity0`)")
+    if entity1 is None and new_entity is None:
+        raise ValueError("Please provide the second entity as second argument (`entity1`)")
+    if old_entity is not None:
+        warnings.warn("Please use 'entity0' instead of 'old_entity'.", DeprecationWarning)
+        if entity0 is not None:
+            raise ValueError("You cannot use both, entity0 and old_entity")
+        entity0 = old_entity
+    if new_entity is not None:
+        warnings.warn("Please use 'entity1' instead of 'new_entity'.", DeprecationWarning)
+        if entity1 is not None:
+            raise ValueError("You cannot use both, entity1 and new_entity")
+        entity1 = new_entity
+
+    diff: tuple = ({"properties": {}, "parents": []},
+                   {"properties": {}, "parents": []})
+
+    if entity0 is entity1:
+        return diff
+
+    if type(entity0) is not type(entity1):
         raise ValueError(
             "Comparison of different Entity types is not supported.")
 
+    # compare special attributes
     for attr in SPECIAL_ATTRIBUTES:
-        try:
-            oldattr = old_entity.__getattribute__(attr)
-            old_entity_attr_exists = True
-        except BaseException:
-            old_entity_attr_exists = False
-        try:
-            newattr = new_entity.__getattribute__(attr)
-            new_entity_attr_exists = True
-        except BaseException:
-            new_entity_attr_exists = False
-
-        if old_entity_attr_exists and (oldattr == "" or oldattr is None):
-            old_entity_attr_exists = False
-
-        if new_entity_attr_exists and (newattr == "" or newattr is None):
-            new_entity_attr_exists = False
-
-        if not old_entity_attr_exists and not new_entity_attr_exists:
+        if attr == "value":
             continue
 
-        if ((old_entity_attr_exists ^ new_entity_attr_exists)
-                or (oldattr != newattr)):
+        attr0 = entity0.__getattribute__(attr)
+        # we consider "" and None to be nonexistent
+        attr0_unset = (attr0 == "" or attr0 is None)
 
-            if old_entity_attr_exists:
-                olddiff[attr] = oldattr
+        attr1 = entity1.__getattribute__(attr)
+        # we consider "" and None to be nonexistent
+        attr1_unset = (attr1 == "" or attr1 is None)
 
-            if new_entity_attr_exists:
-                newdiff[attr] = newattr
+        # in both entities the current attribute is not set
+        if attr0_unset and attr1_unset:
+            continue
 
-    # properties
+        # treat datatype separately if one datatype is an object and the other
+        # a string or int, and therefore may be a name or id
+        if attr == "datatype":
+            if not attr0_unset and not attr1_unset:
+                if isinstance(attr0, RecordType):
+                    if attr0.name == attr1:
+                        continue
+                    if str(attr0.id) == str(attr1):
+                        continue
+                if isinstance(attr1, RecordType):
+                    if attr1.name == attr0:
+                        continue
+                    if str(attr1.id) == str(attr0):
+                        continue
 
         # add to diff if attr has different values or is not set for one entity
         if (attr0_unset != attr1_unset) or (attr0 != attr1):
             diff[0][attr] = attr0
             diff[1][attr] = attr1
 
+    # compare value
+    ent0_val, ent1_val = entity0.value, entity1.value
+    if ent0_val != ent1_val:
+        same_value = False
+
+        # Surround scalar values with a list to avoid code duplication -
+        # this way, the scalar values can be checked against special cases
+        # (compare refs, entity id equivalency etc.) in the list loop
+        if not isinstance(ent0_val, list) and not isinstance(ent1_val, list):
+            ent0_val, ent1_val = [ent0_val], [ent1_val]
+
+        if isinstance(ent0_val, list) and isinstance(ent1_val, list):
+            # lists can't be the same if the lengths are different
+            if len(ent0_val) == len(ent1_val):
+                lists_match = True
+                for val0, val1 in zip(ent0_val, ent1_val):
+                    if val0 == val1:
+                        continue
+                    # Compare Entities
+                    if (compare_referenced_records and
+                            isinstance(val0, Entity) and isinstance(val1, Entity)):
+                        try:
+                            same = empty_diff(val0, val1, False,
+                                              entity_name_id_equivalency)
+                        except (ValueError, NotImplementedError):
+                            same = False
+                        if same:
+                            continue
+                    # Compare Entity name and id
+                    if entity_name_id_equivalency:
+                        if (isinstance(val0, Entity)
+                                and isinstance(val1, (int, str))):
+                            if (str(val0.id) == str(val1)
+                                    or str(val0.name) == str(val1)):
+                                continue
+                        if (isinstance(val1, Entity)
+                                and isinstance(val0, (int, str))):
+                            if (str(val1.id) == str(val0)
+                                    or str(val1.name) == str(val0)):
+                                continue
+                    # val0 and val1 could not be matched
+                    lists_match = False
+                    break
+                if lists_match:
+                    same_value = True
+
+        if not same_value:
+            diff[0]["value"] = entity0.value
+            diff[1]["value"] = entity1.value
+
+    # compare properties
+    for prop in entity0.properties:
+        matching = entity1.properties.filter(prop, check_wrapped=False)
         if len(matching) == 0:
-            olddiff["properties"][prop.name] = {}
+            # entity1 has prop, entity0 does not
+            diff[0]["properties"][prop.name] = {}
         elif len(matching) == 1:
-            newdiff["properties"][prop.name] = {}
-            olddiff["properties"][prop.name] = {}
-
-            if (old_entity.get_importance(prop.name) !=
-                    new_entity.get_importance(prop.name)):
-                olddiff["properties"][prop.name]["importance"] = \
-                    old_entity.get_importance(prop.name)
-                newdiff["properties"][prop.name]["importance"] = \
-                    new_entity.get_importance(prop.name)
-
-            if (prop.datatype != matching[0].datatype):
-                olddiff["properties"][prop.name]["datatype"] = prop.datatype
-                newdiff["properties"][prop.name]["datatype"] = \
-                    matching[0].datatype
-
-            if (prop.unit != matching[0].unit):
-                olddiff["properties"][prop.name]["unit"] = prop.unit
-                newdiff["properties"][prop.name]["unit"] = \
-                    matching[0].unit
-
-            if (prop.value != matching[0].value):
-                # basic comparison of value objects says they are different
-                same_value = False
-                if compare_referenced_records:
-                    # scalar reference
-                    if isinstance(prop.value, Entity) and isinstance(matching[0].value, Entity):
-                        # explicitely not recursive to prevent infinite recursion
-                        same_value = empty_diff(
-                            prop.value, matching[0].value, compare_referenced_records=False)
-                    # list of references
-                    elif isinstance(prop.value, list) and isinstance(matching[0].value, list):
-                        # all elements in both lists actually are entity objects
-                        # TODO: check, whether mixed cases can be allowed or should lead to an error
-                        if (all([isinstance(x, Entity) for x in prop.value])
-                                and all([isinstance(x, Entity) for x in matching[0].value])):
-                            # can't be the same if the lengths are different
-                            if len(prop.value) == len(matching[0].value):
-                                # do a one-by-one comparison:
-                                # the values are the same if all diffs are empty
-                                same_value = all(
-                                    [empty_diff(x, y, False) for x, y
-                                     in zip(prop.value, matching[0].value)])
-
-                if not same_value:
-                    olddiff["properties"][prop.name]["value"] = prop.value
-                    newdiff["properties"][prop.name]["value"] = \
-                        matching[0].value
-
-            if (len(newdiff["properties"][prop.name]) == 0
-                    and len(olddiff["properties"][prop.name]) == 0):
-                newdiff["properties"].pop(prop.name)
-                olddiff["properties"].pop(prop.name)
+            diff[0]["properties"][prop.name] = {}
+            diff[1]["properties"][prop.name] = {}
+            propdiff = (diff[0]["properties"][prop.name],
+                        diff[1]["properties"][prop.name])
+
+            # We should compare the wrapped properties instead of the
+            # wrapping entities if possible:
+            comp1, comp2 = prop, matching[0]
+            if (comp1._wrapped_entity is not None
+                    and comp2._wrapped_entity is not None):
+                comp1, comp2 = comp1._wrapped_entity, comp2._wrapped_entity
+            # Recursive call to determine the differences between properties
+            # Note: Can lead to infinite recursion if two properties have
+            # themselves or each other as subproperties
+            od, nd = compare_entities(comp1, comp2, compare_referenced_records,
+                                      entity_name_id_equivalency)
+            # We do not care about parents and properties here, discard
+            od.pop("parents")
+            od.pop("properties")
+            nd.pop("parents")
+            nd.pop("properties")
+            # use the remaining diff
+            propdiff[0].update(od)
+            propdiff[1].update(nd)
+
+            # As the importance of a property is an attribute of the record
+            # and not the property, it is not contained in the diff returned
+            # by compare_entities and needs to be added separately
+            if (entity0.get_importance(prop) !=
+                    entity1.get_importance(matching[0])):
+                propdiff[0]["importance"] = entity0.get_importance(prop)
+                propdiff[1]["importance"] = entity1.get_importance(matching[0])
+
+            # in case there is no difference, we remove the dict keys again
+            if len(propdiff[0]) == 0 and len(propdiff[1]) == 0:
+                diff[0]["properties"].pop(prop.name)
+                diff[1]["properties"].pop(prop.name)
 
         else:
             raise NotImplementedError(
                 "Comparison not implemented for multi-properties.")
 
-    for prop in new_entity.properties:
-        if len([0 for p in old_entity.properties if p.name == prop.name]) == 0:
-            newdiff["properties"][prop.name] = {}
-
-    # parents
-
-    for parent in old_entity.parents:
-        if len([0 for p in new_entity.parents if p.name == parent.name]) == 0:
-            olddiff["parents"].append(parent.name)
+    # we have not yet compared properties that do not exist in entity0
+    for prop in entity1.properties:
+        # check how often the property appears in entity0
+        num_prop_in_ent0 = len(entity0.properties.filter(prop))
+        if num_prop_in_ent0 == 0:
+            # property is only present in entity0 - add to diff
+            diff[1]["properties"][prop.name] = {}
+        if num_prop_in_ent0 > 1:
+            # Check whether the property is present multiple times in entity0
+            # and raise error - result would be incorrect
+            raise NotImplementedError(
+                "Comparison not implemented for multi-properties.")
 
-    for parent in new_entity.parents:
-        if len([0 for p in old_entity.parents if p.name == parent.name]) == 0:
-            newdiff["parents"].append(parent.name)
+    # compare parents
+    for index, parents, other_entity in [(0, entity0.parents, entity1),
+                                         (1, entity1.parents, entity0)]:
+        for parent in parents:
+            matching = other_entity.parents.filter(parent)
+            if len(matching) == 0:
+                diff[index]["parents"].append(parent.name)
+                continue
 
-    return (olddiff, newdiff)
+    return diff
 
 
 def empty_diff(old_entity: Entity, new_entity: Entity,
-               compare_referenced_records: bool = False) -> bool:
+               compare_referenced_records: bool = False,
+               entity_name_id_equivalency: bool = False) -> bool:
     """Check whether the `compare_entities` found any differences between
     old_entity and new_entity.
 
@@ -359,10 +457,13 @@ def empty_diff(old_entity: Entity, new_entity: Entity,
         Whether to compare referenced records in case of both, `old_entity` and
         `new_entity`, have the same reference properties and both have a Record
         object as value.
-
+    entity_name_id_equivalency : bool, optional
+        If set to True, the comparison between an entity and an int or str also
+        checks whether the int/str matches the name or id of the entity, so
+        Entity(id=100) == 100 == "100".
     """
-    olddiff, newdiff = compare_entities(
-        old_entity, new_entity, compare_referenced_records)
+    olddiff, newdiff = compare_entities(old_entity, new_entity,
+                                        compare_referenced_records, entity_name_id_equivalency)
     for diff in [olddiff, newdiff]:
         for key in ["parents", "properties"]:
             if len(diff[key]) > 0:
@@ -384,9 +485,9 @@ def merge_entities(entity_a: Entity,
                    ) -> Entity:
     """Merge entity_b into entity_a such that they have the same parents and properties.
 
-    datatype, unit, value, name and description will only be changed in entity_a
-    if they are None for entity_a and set for entity_b. If there is a
-    corresponding value for entity_a different from None, an
+    The attributes datatype, unit, value, name and description will only be changed
+    in entity_a if they are None for entity_a and set for entity_b. If one of those attributes is
+    set in both entities and they differ, then an
     EntityMergeConflictError will be raised to inform about an unresolvable merge
     conflict.
 
@@ -394,8 +495,6 @@ def merge_entities(entity_a: Entity,
 
     Returns entity_a.
 
-    WARNING: This function is currently experimental and insufficiently tested. Use with care.
-
     Parameters
     ----------
     entity_a, entity_b : Entity
@@ -428,12 +527,10 @@ def merge_entities(entity_a: Entity,
 
     """
 
-    logger.warning(
-        "This function is currently experimental and insufficiently tested. Use with care.")
-
     # Compare both entities:
-    diff_r1, diff_r2 = compare_entities(
-        entity_a, entity_b, compare_referenced_records=merge_references_with_empty_diffs)
+    diff_r1, diff_r2 = compare_entities(entity_a, entity_b,
+                                        entity_name_id_equivalency=merge_id_with_resolved_entity,
+                                        compare_referenced_records=merge_references_with_empty_diffs)
 
     # Go through the comparison and try to apply changes to entity_a:
     for key in diff_r2["parents"]:
@@ -453,7 +550,8 @@ def merge_entities(entity_a: Entity,
             for attribute in ("datatype", "unit", "value"):
                 if (attribute in diff_r2["properties"][key] and
                         diff_r2["properties"][key][attribute] is not None):
-                    if (diff_r1["properties"][key][attribute] is None):
+                    if (attribute not in diff_r1["properties"][key] or
+                            diff_r1["properties"][key][attribute] is None):
                         setattr(entity_a.get_property(key), attribute,
                                 diff_r2["properties"][key][attribute])
                     elif force:
@@ -521,6 +619,18 @@ def merge_entities(entity_a: Entity,
 
 
 def describe_diff(olddiff, newdiff, name=None, as_update=True):
+    """
+    This function generates a textual representation of the differences between two entities that have been generated
+    using compare_entities.
+
+    Arguments:
+    ----------
+    olddiff: The diff output for the entity marked as "old".
+    newdiff: The diff output for the entity marked as "new".
+
+    Example:
+    >>> describe_diff(*compare_entities(db.Record().add_property("P"), "value", db.Record()))
+    """
     description = ""
 
     for attr in list(set(list(olddiff.keys()) + list(newdiff.keys()))):
diff --git a/src/linkahead/cached.py b/src/linkahead/cached.py
index f9179e1a54997bf99f7158b9b46e2d1068a21e47..11cb959ba10fd507c39eb4d1ddd00bf478859852 100644
--- a/src/linkahead/cached.py
+++ b/src/linkahead/cached.py
@@ -116,7 +116,7 @@ def cached_query(query_string: str) -> Container:
     return result
 
 
-@ lru_cache(maxsize=DEFAULT_SIZE)
+@lru_cache(maxsize=DEFAULT_SIZE)
 def _cached_access(kind: AccessType, value: Union[str, int], unique: bool = True):
     # This is the function that is actually cached.
     # Due to the arguments, the cache has kind of separate sections for cached_query and
diff --git a/src/linkahead/common/models.py b/src/linkahead/common/models.py
index 5689e0799f51584a39c187fd106c4d1b09e9cfc7..dd0718c79717bb7a983d8da2b59b9c73ecbd96f3 100644
--- a/src/linkahead/common/models.py
+++ b/src/linkahead/common/models.py
@@ -37,8 +37,10 @@ from __future__ import annotations  # Can be removed with 3.10.
 
 import re
 import sys
+import warnings
 from builtins import str
 from copy import deepcopy
+from enum import Enum
 from datetime import date, datetime
 from functools import cmp_to_key
 from hashlib import sha512
@@ -46,7 +48,6 @@ from os import listdir
 from os.path import isdir
 from random import randint
 from tempfile import NamedTemporaryFile
-
 from typing import TYPE_CHECKING
 from typing import Any, Final, Literal, Optional, TextIO, Union
 
@@ -57,7 +58,6 @@ if TYPE_CHECKING:
     from os import PathLike
     QueryDict = dict[str, Optional[str]]
 
-
 from warnings import warn
 
 from lxml import etree
@@ -114,8 +114,8 @@ if TYPE_CHECKING:
     IMPORTANCE = Literal["OBLIGATORY", "RECOMMENDED", "SUGGESTED", "FIX", "NONE"]
     ROLE = Literal["Entity", "Record", "RecordType", "Property", "File"]
 
-SPECIAL_ATTRIBUTES = ["name", "role", "datatype", "description",
-                      "id", "path", "checksum", "size", "value"]
+SPECIAL_ATTRIBUTES = ["name", "role", "datatype", "description", "file",
+                      "id", "path", "checksum", "size", "value", "unit"]
 
 
 class Entity:
@@ -138,10 +138,10 @@ class Entity:
         description: Optional[str] = None,  # @ReservedAssignment
         datatype: Optional[DATATYPE] = None,
         value=None,
-        **kwargs,
+        role=None,
     ):
 
-        self.__role: Optional[ROLE] = kwargs["role"] if "role" in kwargs else None
+        self.__role: Optional[ROLE] = role
         self._checksum: Optional[str] = None
         self._size = None
         self._upload = None
@@ -156,8 +156,8 @@ class Entity:
         self.datatype: Optional[DATATYPE] = datatype
         self.value = value
         self.messages = Messages()
-        self.properties = _Properties()
-        self.parents = _ParentList()
+        self.properties = PropertyList()
+        self.parents = ParentList()
         self.path: Optional[str] = None
         self.file: Optional[File] = None
         self.unit: Optional[str] = None
@@ -922,7 +922,7 @@ class Entity:
     def get_parents(self):
         """Get all parents of this entity.
 
-        @return: _ParentList(list)
+        @return: ParentList(list)
         """
 
         return self.parents
@@ -1022,7 +1022,7 @@ class Entity:
     def get_properties(self):
         """Get all properties of this entity.
 
-        @return: _Properties(list)
+        @return: PropertyList(list)
         """
 
         return self.properties
@@ -2422,11 +2422,14 @@ class File(Record):
             value=value, unit=unit, importance=importance, inheritance=inheritance)
 
 
-class _Properties(list):
-    """FIXME: Add docstring."""
+class PropertyList(list):
+    """A list class for Property objects
+
+    This class provides addional functionality like get/set_importance or get_by_name.
+    """
 
     def __init__(self):
-        list.__init__(self)
+        super().__init__()
         self._importance: dict[Entity, IMPORTANCE] = dict()
         self._inheritance: dict[Entity, INHERITANCE] = dict()
         self._element_by_name: dict[str, Entity] = dict()
@@ -2519,6 +2522,47 @@ class _Properties(list):
 
         return xml2str(xml)
 
+    def filter(self, prop: Optional[Property] = None, pid: Union[None, str, int] = None,
+               name: Optional[str] = None, check_wrapped: bool = True) -> list:
+        """
+        Return all Properties from the given PropertyList that match the
+        selection criteria.
+
+        You can provide name or ID and all matching elements will be returned.
+        If both name and ID are given, elements matching either criterion will
+        be returned.
+
+        If a Property is given, neither name nor ID may be set. In this case,
+        only elements matching both name and ID of the Property are returned.
+
+        Also checks the original Properties wrapped within the elements of
+        PropertyList and will return the original Property if both wrapper and
+        original match.
+
+        Params
+        ------
+        listobject        : Iterable(Property)
+                            List to be filtered
+        prop              : Property
+                            Property to match name and ID with. Cannot be
+                            set simultaneously with ID or name.
+        pid               : str, int
+                            Property ID to match
+        name              : str
+                            Property name to match
+        check_wrapped     : bool, default: True
+                            If set to False, only the wrapper elements
+                            contained in the given PropertyList will be
+                            checked, not the original Properties they wrap.
+
+        Returns
+        -------
+        matches          : list
+                           List containing all matching Properties
+        """
+        return _filter_entity_list(self, pid=pid, name=name, entity=prop,
+                                   check_wrapped=check_wrapped)
+
     def _get_entity_by_cuid(self, cuid: str):
         '''
         Get the first entity which has the given cuid.
@@ -2576,9 +2620,7 @@ class _Properties(list):
         raise KeyError(str(prop) + " not found.")
 
 
-class _ParentList(list):
-    # TODO unclear why this class is private. Isn't it use full for users?
-
+class ParentList(list):
     def _get_entity_by_cuid(self, cuid):
         '''
         Get the first entity which has the given cuid.
@@ -2593,8 +2635,8 @@ class _ParentList(list):
                     return e
         raise KeyError("No entity with that cuid in this container.")
 
-    def __init__(self):
-        list.__init__(self)
+    def __init__(self, *args, **kwargs):
+        super().__init__(*args, **kwargs)
         self._element_by_name = dict()
         self._element_by_id = dict()
 
@@ -2607,15 +2649,9 @@ class _ParentList(list):
         if isinstance(parent, list):
             for p in parent:
                 self.append(p)
-
             return
 
         if isinstance(parent, Entity):
-            if parent.id:
-                self._element_by_id[str(parent.id)] = parent
-
-            if parent.name:
-                self._element_by_name[parent.name] = parent
             list.append(self, parent)
         else:
             raise TypeError("Argument was not an Entity")
@@ -2657,7 +2693,62 @@ class _ParentList(list):
 
         return xml2str(xml)
 
+    def filter(self, parent: Optional[Parent] = None, pid: Union[None, str, int] = None,
+               name: Optional[str] = None, check_wrapped: bool = True) -> list:
+        """
+        Return all Parents from the given ParentList that match the selection
+        criteria.
+
+        You can provide name or ID and all matching elements will be returned.
+        If both name and ID are given, elements matching either criterion will
+        be returned.
+
+        If a Parent is given, neither name nor ID may be set. In this case,
+        only elements matching both name and ID of the Parent are returned.
+
+        Also checks the original Parents wrapped within the elements of
+        ParentList, will return the original Parent if both wrapper and
+        original match.
+
+        Params
+        ------
+        listobject        : Iterable(Parent)
+                            List to be filtered
+        parent            : Parent
+                            Parent to match name and ID with. Cannot be set
+        pid               : str, int
+                            Parent ID to match
+        name              : str
+                            Parent name to match
+                            simultaneously with ID or name.
+        check_wrapped     : bool, default: True
+                            If set to False, only the wrapper elements
+                            contained in the given ParentList will be
+                            checked, not the original Parents they wrap.
+
+        Returns
+        -------
+        matches          : list
+                           List containing all matching Parents
+        """
+        return _filter_entity_list(self, pid=pid, name=name, entity=parent,
+                                   check_wrapped=check_wrapped)
+
     def remove(self, parent: Union[Entity, int, str]):
+        """
+        Remove first occurrence of parent.
+
+        Parameters
+        ----------
+        parent: Union[Entity, int, str], the parent to be removed identified via ID or name. If a
+        Parent object is provided the ID and then the name is used to identify the parent to be
+        removed.
+
+        Returns
+        -------
+        None
+        """
+
         if isinstance(parent, Entity):
             if parent in self:
                 list.remove(self, parent)
@@ -2675,11 +2766,11 @@ class _ParentList(list):
                     # by name
 
                     for e in self:
-                        if e.name is not None and e.name == parent.name:
+                        if e.name is not None and e.name.lower() == parent.name.lower():
                             list.remove(self, e)
 
                             return
-        elif hasattr(parent, "encode"):
+        elif isinstance(parent, str):
             # by name
 
             for e in self:
@@ -2698,6 +2789,19 @@ class _ParentList(list):
         raise KeyError(str(parent) + " not found.")
 
 
+class _Properties(PropertyList):
+    def __init__(self, *args, **kwargs):
+        warnings.warn(DeprecationWarning("This class is deprecated. Please use PropertyList."))
+        super().__init__(*args, **kwargs)
+
+
+class _ParentList(ParentList):
+    def __init__(self, *args, **kwargs):
+        warnings.warn(DeprecationWarning("This class is deprecated. Please use ParentList "
+                                         "(without underscore)."))
+        super().__init__(*args, **kwargs)
+
+
 class Messages(list):
     """This specialization of list stores error, warning, info, and other
     messages. The mentioned three messages types play a special role.
@@ -5392,3 +5496,100 @@ def delete(ids: Union[list[int], range], raise_exception_on_error: bool = True):
         c.append(Entity(id=ids))
 
     return c.delete(raise_exception_on_error=raise_exception_on_error)
+
+
+def _filter_entity_list(listobject, entity: Optional[Entity] = None, pid: Union[None, str, int] = None,
+                        name: Optional[str] = None, check_wrapped: bool = True) -> list:
+    """
+    Return all elements from the given list that match the selection criteria.
+
+    You can provide name or ID and all matching elements will be returned.
+    If both name and ID are given, elements matching either criterion will be
+    returned.
+
+    If an Entity is given, neither name nor ID may be set. In this case, only
+    elements matching both name and ID of the Entity are returned, as long as
+    name and ID are both set.
+
+    In case the elements contained in the given list are wrapped, the function
+    in its default configuration checks both the wrapped and wrapper Entity
+    against the match criteria, and will return the wrapped Entity if both
+    match. Note that this is currently not iterative, meaning that only the
+    first layer of wrapped entity is considered.
+
+    Params
+    ------
+    listobject        : Iterable(Entity)
+                        List to be filtered
+    entity            : Entity
+                        Entity to match name and ID for. Cannot be set
+                        simultaneously with ID or name.
+    pid               : str, int
+                        Entity ID to match
+    name              : str
+                        Entity name to match
+    check_wrapped     : bool, default: True
+                        If set to False, only the wrapper elements
+                        contained in the given list will be checked and
+                        returned, not the original Entities they wrap.
+
+    Returns
+    -------
+    matches          : list
+                       A List containing all matching Entities
+    """
+    # Check correct input params and setup
+    match_entity = False
+    if entity is not None:
+        if pid is not None or name is not None:
+            raise ValueError("Please provide either Entity, pid or name.")
+        pid = entity.id
+        name = entity.name
+        match_entity = True
+
+    # Iterate through list and match based on given criteria
+    matches = []
+    potentials = list(zip(listobject.copy(), [False]*len(listobject)))
+    for candidate, wrapped_is_checked in potentials:
+        name_match, pid_match, original_candidate = False, False, None
+
+        # Parents/Properties may be wrapped - if wanted, try to match original
+        # Note: if we want to check all wrapped Entities, this should be
+        #       switched. First check the wrap, then append wrapped. In this
+        #       case we also don't need wrapped_checked, but preferentially
+        #       append the wrapper.
+        if check_wrapped and not wrapped_is_checked:
+            try:
+                if candidate._wrapped_entity is not None:
+                    original_candidate = candidate
+                    candidate = candidate._wrapped_entity
+            except AttributeError:
+                pass
+
+        # Check whether name/pid match
+        # pid and candidate.id might be int and str, so cast to str (None safe)
+        if pid is not None and str(candidate.id) == str(pid):
+            pid_match = True
+        elif match_entity and pid is None:
+            # Without a pid we cannot match
+            pid_match = True
+        if (name is not None and candidate.name is not None
+                and candidate.name.lower() == name.lower()):
+            name_match = True
+        elif match_entity and name is None:
+            # Without a name we cannot match
+            name_match = True
+
+        # If the criteria are satisfied, append the match. Otherwise, check
+        # the wrapper if applicable
+        # ToDo: Check whether it would make sense to also check the RecordType
+        #       for equality when match_entity is true to offset potentially
+        #       missing id
+        if name_match and pid_match:
+            matches.append(candidate)
+        elif not match_entity and (name_match or pid_match):
+            matches.append(candidate)
+        else:
+            if original_candidate is not None:
+                potentials.append((original_candidate, True))
+    return matches
diff --git a/unittests/test_apiutils.py b/unittests/test_apiutils.py
index 087880768516f0da97858d65a5d15247308dca2a..cdca1280b790e09225320330dd3e4a89989ef17b 100644
--- a/unittests/test_apiutils.py
+++ b/unittests/test_apiutils.py
@@ -25,6 +25,7 @@
 # Test apiutils
 # A. Schlemmer, 02/2018
 
+from io import StringIO
 
 import linkahead as db
 import linkahead.apiutils
@@ -96,6 +97,7 @@ def test_resolve_reference():
 
 
 def test_compare_entities():
+    # test compare of parents, properties
     r1 = db.Record()
     r2 = db.Record()
     r1.add_parent("bla")
@@ -144,9 +146,6 @@ def test_compare_entities():
     # test compare units of properties
     r1 = db.Record()
     r2 = db.Record()
-    r1.add_parent("bla")
-    r2.add_parent("bla")
-    r1.add_parent("lopp")
     r1.add_property("test", value=2, unit="cm")
     r2.add_property("test", value=2, unit="m")
     r1.add_property("tests", value=3, unit="cm")
@@ -158,8 +157,6 @@ def test_compare_entities():
 
     diff_r1, diff_r2 = compare_entities(r1, r2)
 
-    assert len(diff_r1["parents"]) == 1
-    assert len(diff_r2["parents"]) == 0
     assert len(diff_r1["properties"]) == 4
     assert len(diff_r2["properties"]) == 4
 
@@ -176,14 +173,207 @@ def test_compare_entities():
     assert diff_r2["properties"]["test"]["unit"] == "m"
 
 
+def test_compare_entities_battery():
+    par1, par2, par3 = db.Record(), db.Record(), db.RecordType()
+    r1, r2, r3 = db.Record(), db.Record(), db.Record()
+    prop1 = db.Property()
+    prop2 = db.Property(name="Property 2")
+    prop3 = db.Property()
+
+    # Basic tests for Properties
+    prop_settings = {"datatype": db.REFERENCE, "description": "desc of prop",
+                     "value": db.Record().add_parent(par3), "unit": '°'}
+    t1 = db.Record().add_parent(db.RecordType())
+    t2 = db.Record().add_parent(db.RecordType())
+    # Change datatype
+    t1.add_property(db.Property(name="datatype", **prop_settings))
+    prop_settings["datatype"] = par3
+    t2.add_property(db.Property(name="datatype", **prop_settings))
+    # Change description
+    t1.add_property(db.Property(name="description", **prop_settings))
+    prop_settings["description"] = "diff desc"
+    t2.add_property(db.Property(name="description", **prop_settings))
+    # Change value to copy
+    t1.add_property(db.Property(name="value copy", **prop_settings))
+    prop_settings["value"] = db.Record().add_parent(par3)
+    t2.add_property(db.Property(name="value copy", **prop_settings))
+    # Change value to something different
+    t1.add_property(db.Property(name="value", **prop_settings))
+    prop_settings["value"] = db.Record(name="n").add_parent(par3)
+    t2.add_property(db.Property(name="value", **prop_settings))
+    # Change unit
+    t1.add_property(db.Property(name="unit", **prop_settings))
+    prop_settings["unit"] = db.Property(unit='°')
+    t2.add_property(db.Property(name="unit", **prop_settings))
+    # Change unit again
+    t1.add_property(db.Property(name="unit 2", **prop_settings))
+    prop_settings["unit"] = db.Property()
+    t2.add_property(db.Property(name="unit 2", **prop_settings))
+    # Compare
+    diff_0 = compare_entities(t1, t2)
+    diff_1 = compare_entities(t1, t2, compare_referenced_records=True)
+    # Check correct detection of changes
+    assert diff_0[0]["properties"]["datatype"] == {"datatype": db.REFERENCE}
+    assert diff_0[1]["properties"]["datatype"] == {"datatype": par3}
+    assert diff_0[0]["properties"]["description"] == {"description": "desc of prop"}
+    assert diff_0[1]["properties"]["description"] == {"description": "diff desc"}
+    assert "value" in diff_0[0]["properties"]["value copy"]
+    assert "value" in diff_0[1]["properties"]["value copy"]
+    assert "value" in diff_0[0]["properties"]["value"]
+    assert "value" in diff_0[1]["properties"]["value"]
+    assert "unit" in diff_0[0]["properties"]["unit"]
+    assert "unit" in diff_0[1]["properties"]["unit"]
+    assert "unit" in diff_0[0]["properties"]["unit 2"]
+    assert "unit" in diff_0[1]["properties"]["unit 2"]
+    # Check correct result for compare_referenced_records=True
+    assert "value copy" not in diff_1[0]["properties"]
+    assert "value copy" not in diff_1[1]["properties"]
+    diff_0[0]["properties"].pop("value copy")
+    diff_0[1]["properties"].pop("value copy")
+    assert diff_0 == diff_1
+
+    # Basic tests for Parents
+    t3 = db.Record().add_parent(db.RecordType("A")).add_parent(db.Record("B"))
+    t4 = db.Record().add_parent(db.RecordType("A"))
+    assert compare_entities(t3, t4)[0]['parents'] == ['B']
+    assert len(compare_entities(t3, t4)[1]['parents']) == 0
+    t4.add_parent(db.Record("B"))
+    assert empty_diff(t3, t4)
+    # The two following assertions document current behaviour but do not make a
+    # lot of sense
+    t4.add_parent(db.Record("B"))
+    assert empty_diff(t3, t4)
+    t3.add_parent(db.RecordType("A")).add_parent(db.Record("B"))
+    t4.add_parent(db.RecordType("B")).add_parent(db.Record("A"))
+    assert empty_diff(t3, t4)
+
+    # Basic tests for special attributes
+    prop_settings = {"id": 42, "name": "Property",
+                     "datatype": db.LIST(db.REFERENCE), "value": [db.Record()],
+                     "unit": '€', "description": "desc of prop"}
+    alt_settings = {"id": 64, "name": "Property 2",
+                    "datatype": db.LIST(db.TEXT), "value": [db.RecordType()],
+                    "unit": '€€', "description": " ę Ě ப ཾ ཿ ∛ ∜ ㅿ ㆀ 값 "}
+    t5 = db.Property(**prop_settings)
+    t6 = db.Property(**prop_settings)
+    assert empty_diff(t5, t6)
+    # ID
+    t5.id = alt_settings['id']
+    diff = compare_entities(t5, t6)
+    assert diff[0] == {'properties': {}, 'parents': [], 'id': alt_settings['id']}
+    assert diff[1] == {'properties': {}, 'parents': [], 'id': prop_settings['id']}
+    t6.id = alt_settings['id']
+    assert empty_diff(t5, t6)
+    # Name
+    t5.name = alt_settings['name']
+    diff = compare_entities(t5, t6)
+    assert diff[0] == {'properties': {}, 'parents': [], 'name': alt_settings['name']}
+    assert diff[1] == {'properties': {}, 'parents': [], 'name': prop_settings['name']}
+    t6.name = alt_settings['name']
+    assert empty_diff(t5, t6)
+    # Description
+    t6.description = alt_settings['description']
+    diff = compare_entities(t5, t6)
+    assert diff[0] == {'properties': {}, 'parents': [], 'description': prop_settings['description']}
+    assert diff[1] == {'properties': {}, 'parents': [], 'description': alt_settings['description']}
+    t5.description = alt_settings['description']
+    assert empty_diff(t5, t6)
+    # Unit
+    t5.unit = alt_settings['unit']
+    diff = compare_entities(t5, t6)
+    assert diff[0] == {'properties': {}, 'parents': [], 'unit': alt_settings['unit']}
+    assert diff[1] == {'properties': {}, 'parents': [], 'unit': prop_settings['unit']}
+    t6.unit = alt_settings['unit']
+    assert empty_diff(t5, t6)
+    # Value
+    t6.value = alt_settings['value']
+    diff = compare_entities(t5, t6)
+    assert diff[0] == {'properties': {}, 'parents': [], 'value': prop_settings['value']}
+    assert diff[1] == {'properties': {}, 'parents': [], 'value': alt_settings['value']}
+    t5.value = alt_settings['value']
+    assert empty_diff(t5, t6)
+    # Datatype
+    t6.datatype = alt_settings['datatype']
+    diff = compare_entities(t5, t6)
+    assert diff[0] == {'properties': {}, 'parents': [], 'datatype': prop_settings['datatype']}
+    assert diff[1] == {'properties': {}, 'parents': [], 'datatype': alt_settings['datatype']}
+    t5.datatype = alt_settings['datatype']
+    assert empty_diff(t5, t6)
+    # All at once
+    diff = compare_entities(db.Property(**prop_settings), db.Property(**alt_settings))
+    assert diff[0] == {'properties': {}, 'parents': [], **prop_settings}
+    assert diff[1] == {'properties': {}, 'parents': [], **alt_settings}
+    # Entity Type
+    diff = compare_entities(db.Property(value=db.Property(id=101)),
+                            db.Property(value=db.Record(id=101)))
+    assert "value" in diff[0]
+    assert "value" in diff[1]
+    diff = compare_entities(db.Property(value=db.Record(id=101)),
+                            db.Property(value=db.Record(id=101)))
+    assert "value" in diff[0]
+    assert "value" in diff[1]
+    assert empty_diff(db.Property(value=db.Record(id=101)),
+                      db.Property(value=db.Record(id=101)),
+                      compare_referenced_records=True)
+
+    # Special cases
+    # Files
+    assert not empty_diff(db.File(path='ABC', file=StringIO("ABC")),
+                          db.File(path='ABC', file=StringIO("Other")))
+    # Importance
+    assert empty_diff(db.Property().add_property(prop1),
+                      db.Property().add_property(prop1))
+    assert not empty_diff(db.Property().add_property(prop1, importance=db.SUGGESTED),
+                          db.Property().add_property(prop1, importance=db.OBLIGATORY))
+    # Mixed Lists
+    assert empty_diff(db.Property(value=[1, 2, 'a', r1]),
+                      db.Property(value=[1, 2, 'a', r1]))
+    # entity_name_id_equivalency
+    assert not empty_diff(db.Property(value=[1, db.Record(id=2), 3, db.Record(id=4)]),
+                          db.Property(value=[db.Record(id=1), 2, db.Record(id=3), 4]))
+    assert empty_diff(db.Property(value=[1, db.Record(id=2), 3, db.Record(id=4)]),
+                      db.Property(value=[db.Record(id=1), 2, db.Record(id=3), 4]),
+                      entity_name_id_equivalency=True)
+    assert empty_diff(db.Property(value=1), db.Property(value=db.Record(id=1)),
+                      entity_name_id_equivalency=True)
+    # entity_name_id_equivalency
+    prop4 = db.Property(**prop_settings).add_parent(par1).add_property(prop2)
+    prop4_c = db.Property(**prop_settings).add_parent(par1).add_property(prop2)
+    prop4.value = db.Record(id=12)
+    prop4_c.value = '12'
+    prop4.add_property(db.Property(name="diff", datatype=db.LIST(db.REFERENCE),
+                                   value=[12, db.Record(id=13), par1, "abc%"]))
+    prop4_c.add_property(db.Property(name="diff", datatype=db.LIST(db.REFERENCE),
+                                     value=[db.Record(id=12), "13", par1, "abc%"]))
+    assert not empty_diff(prop4, prop4_c, entity_name_id_equivalency=False)
+    assert empty_diff(prop4, prop4_c, entity_name_id_equivalency=True)
+    # Order invariance
+    t7 = db.Property(**prop_settings).add_parent(par1).add_property(prop1)
+    t8 = db.Property(**alt_settings).add_parent(par3).add_property(prop3)
+    diffs_0 = compare_entities(t7, t8), compare_entities(t7, t8, True)
+    diffs_1 = compare_entities(t8, t7)[::-1], compare_entities(t8, t7, True)[::-1]
+    assert diffs_0 == diffs_1
+    prop_settings = {"datatype": db.REFERENCE, "description": "desc of prop",
+                     "value": db.Record().add_parent(par3), "unit": '°'}
+    t1.add_property(db.Property(name="description", **prop_settings))
+    t2.add_property(db.Property(name="description", **prop_settings))
+    try:
+        diffs_0 = compare_entities(t1, t2), compare_entities(t1, t2, True)
+    except Exception as e:
+        diffs_0 = type(e)
+    try:
+        diffs_1 = compare_entities(t2, t1)[::-1], compare_entities(t2, t1, True)[::-1]
+    except Exception as e:
+        diffs_1 = type(e)
+    assert diffs_0 == diffs_1
+
+
 def test_compare_special_properties():
     # Test for all known special properties:
-    SPECIAL_PROPERTIES = ("description", "name",
-                          "checksum", "size", "path", "id")
     INTS = ("size", "id")
     HIDDEN = ("checksum", "size")
 
-    for key in SPECIAL_PROPERTIES:
+    for key in SPECIAL_ATTRIBUTES:
         set_key = key
         if key in HIDDEN:
             set_key = "_" + key
@@ -221,8 +411,7 @@ def test_compare_special_properties():
         assert len(diff_r1["properties"]) == 0
         assert len(diff_r2["properties"]) == 0
 
-
-def test_compare_properties():
+    # compare Property objects
     p1 = db.Property()
     p2 = db.Property()
 
@@ -473,10 +662,10 @@ def test_empty_diff():
     rec_a.remove_property("RefType")
     rec_b.remove_property("RefType")
     assert empty_diff(rec_a, rec_b)
-    rec_a.add_property(name="RefType", datatype=db.LIST(
-        "RefType"), value=[ref_rec_a, ref_rec_a])
-    rec_b.add_property(name="RefType", datatype=db.LIST(
-        "RefType"), value=[ref_rec_b, ref_rec_b])
+    rec_a.add_property(name="RefType", datatype=db.LIST("RefType"),
+                       value=[ref_rec_a, ref_rec_a])
+    rec_b.add_property(name="RefType", datatype=db.LIST("RefType"),
+                       value=[ref_rec_b, ref_rec_b])
     assert not empty_diff(rec_a, rec_b)
     assert empty_diff(rec_a, rec_b, compare_referenced_records=True)
 
@@ -607,13 +796,12 @@ def test_merge_id_with_resolved_entity():
 
     # Overwrite from right to left in both cases
     merge_entities(recA, recB, merge_id_with_resolved_entity=True)
-    assert recA.get_property(rtname).value == ref_id
-    assert recA.get_property(rtname).value == recB.get_property(rtname).value
+    assert recA.get_property(rtname).value == ref_rec
 
     recA = db.Record().add_property(name=rtname, value=ref_rec)
     merge_entities(recB, recA, merge_id_with_resolved_entity=True)
-    assert recB.get_property(rtname).value == ref_rec
-    assert recA.get_property(rtname).value == recB.get_property(rtname).value
+    assert recB.get_property(rtname).value == ref_id
+    assert recA.get_property(rtname).value == ref_rec
 
     # id mismatches
     recB = db.Record().add_property(name=rtname, value=ref_id*2)
@@ -629,7 +817,8 @@ def test_merge_id_with_resolved_entity():
     # also works in lists:
     recA = db.Record().add_property(
         name=rtname, datatype=db.LIST(rtname), value=[ref_rec, ref_id*2])
-    recB = db.Record().add_property(name=rtname, datatype=db.LIST(rtname), value=[ref_id, ref_id*2])
+    recB = db.Record().add_property(
+        name=rtname, datatype=db.LIST(rtname), value=[ref_id, ref_id*2])
     merge_entities(recA, recB, merge_id_with_resolved_entity=True)
-    assert recA.get_property(rtname).value == [ref_id, ref_id*2]
-    assert recA.get_property(rtname).value == recB.get_property(rtname).value
+    assert recA.get_property(rtname).value == [ref_rec, ref_id*2]
+    assert recB.get_property(rtname).value == [ref_id, ref_id*2]
diff --git a/unittests/test_entity.py b/unittests/test_entity.py
index abf82f0a9b557cf9d1d2365e01fedaa4eae0c565..66cffbefe207820ddde1463f62a88788beb7df9a 100644
--- a/unittests/test_entity.py
+++ b/unittests/test_entity.py
@@ -22,14 +22,17 @@
 # ** end header
 #
 """Tests for the Entity class."""
+import os
 # pylint: disable=missing-docstring
 import unittest
-from lxml import etree
+from pytest import raises
 
-import os
-from linkahead import (INTEGER, Entity, Property, Record, RecordType,
+import linkahead
+from linkahead import (INTEGER, Entity, Property, Record, RecordType, Parent,
                        configure_connection)
+from linkahead.common.models import SPECIAL_ATTRIBUTES
 from linkahead.connection.mockup import MockUpServerConnection
+from lxml import etree
 
 UNITTESTDIR = os.path.dirname(os.path.abspath(__file__))
 
@@ -82,7 +85,13 @@ class TestEntity(unittest.TestCase):
         self.assertEqual(entity.to_xml().tag, "Property")
 
     def test_instantiation(self):
-        self.assertRaises(Exception, Entity())
+        e = Entity()
+        for attr in SPECIAL_ATTRIBUTES:
+            assert hasattr(e, attr)
+
+    def test_instantiation_bad_argument(self):
+        with self.assertRaises(Exception):
+            Entity(rol="File")
 
     def test_parse_role(self):
         """During parsing, the role of an entity is set explicitely. All other
@@ -97,3 +106,135 @@ class TestEntity(unittest.TestCase):
         # test whether the __role property of this object has explicitely been
         # set.
         self.assertEqual(getattr(entity, "_Entity__role"), "Record")
+
+
+def test_parent_list():
+    p1 = RecordType(name="A")
+    pl = linkahead.common.models.ParentList([p1])
+    assert p1 in pl
+    assert pl.index(p1) == 0
+    assert RecordType(name="A") not in pl
+    assert RecordType(id=101) not in pl
+    p2 = RecordType(id=101)
+    pl.append(p2)
+    assert p2 in pl
+    assert len(pl) == 2
+    p3 = RecordType(id=103, name='B')
+    pl.append(p3)
+    assert len(pl) == 3
+
+    # test removal
+    # remove by id only, even though element in parent list has name and id
+    pl.remove(RecordType(id=103))
+    assert len(pl) == 2
+    assert p3 not in pl
+    assert p2 in pl
+    assert p1 in pl
+    # Same for removal by name
+    pl.append(p3)
+    assert len(pl) == 3
+    pl.remove(RecordType(name='B'))
+    assert len(pl) == 2
+    assert p3 not in pl
+    # And an error if no suitable element can be found
+    with raises(KeyError) as ve:
+        pl.remove(RecordType(id=105, name='B'))
+    assert "not found" in str(ve.value)
+    assert len(pl) == 2
+
+    # TODO also check pl1 == pl2
+
+
+def test_property_list():
+    # TODO: Resolve parent-list TODOs, then transfer to here.
+    # TODO: What other considerations have to be done with properties?
+    p1 = Property(name="A")
+    pl = linkahead.common.models.PropertyList()
+    pl.append(p1)
+    assert p1 in pl
+    assert Property(id=101) not in pl
+    p2 = Property(id=101)
+    pl.append(p2)
+    assert p1 in pl
+    assert p2 in pl
+    p3 = Property(id=103, name='B')
+    pl.append(p3)
+
+
+def test_filter():
+    rt1 = RecordType(id=100)
+    rt2 = RecordType(id=101, name="RT")
+    rt3 = RecordType()
+    p1 = Property(id=100)
+    p2 = Property(id=100)
+    p3 = Property(id=101, name="RT")
+    p4 = Property(id=102, name="P")
+    p5 = Property(id=103, name="P")
+    p6 = Property()
+    r1 = Record(id=100)
+    r2 = Record(id=100)
+    r3 = Record(id=101, name="RT")
+    r4 = Record(id=101, name="R")
+    r5 = Record(id=104, name="R")
+    r6 = Record(id=105, name="R")
+    test_ents = [rt1, rt2, rt3, p1, p2, p3, p4, p5, p6, r1, r2, r3, r4, r5, r6]
+
+    # Setup
+    t1 = Property()
+    t1_props, t1_pars = t1.properties, t1.parents
+    t2 = Record()
+    t2_props, t2_pars = t2.properties, t2.parents
+    t3 = RecordType()
+    t3_props, t3_pars = t3.properties, t3.parents
+    test_colls = [t1_props, t1_pars, t2_props, t2_pars, t3_props, t3_pars]
+    for coll in test_colls:
+        for ent in test_ents:
+            assert ent not in coll
+            assert ent not in coll.filter(ent)
+
+    # Checks with each type
+    for t, t_props, t_pars in [(t1, t1_props, t1_pars), (t2, t2_props, t2_pars),
+                               (t3, t3_props, t3_pars)]:
+        # Properties
+        # Basic Checks
+        t.add_property(p1)
+        t.add_property(p3)
+        assert p1 in t_props.filter(pid=100)
+        assert p1 in t_props.filter(pid="100")
+        assert p1 not in t_props.filter(pid=101, name="RT")
+        for entity in [rt1, p2, r1, r2]:
+            assert entity not in t_props.filter(pid=100)
+            assert p1 in t_props.filter(entity)
+        # Check that direct addition (not wrapped) works
+        t_props.append(p2)
+        assert p2 in t_props.filter(pid=100)
+        assert p2 not in t_props.filter(pid=101, name="RT")
+        for entity in [rt1, r1, r2]:
+            assert entity not in t_props.filter(pid=100)
+            assert p2 in t_props.filter(entity)
+
+        # Parents
+        # Filtering with both name and id
+        t.add_parent(r3)
+        t.add_parent(r5)
+        assert r3 in t_pars.filter(pid=101)
+        assert r5 not in t_pars.filter(pid=101)
+        assert r3 not in t_pars.filter(name="R")
+        assert r5 in t_pars.filter(name="R")
+        assert r3 in t_pars.filter(pid=101, name="R")
+        assert r5 in t_pars.filter(pid=101, name="R")
+        assert r3 in t_pars.filter(pid=104, name="RT")
+        assert r5 in t_pars.filter(pid=104, name="RT")
+        assert r3 not in t_pars.filter(pid=105, name="T")
+        assert r5 not in t_pars.filter(pid=105, name="T")
+        # Works also without id / name and with duplicate parents
+        for ent in test_ents:
+            t.add_parent(ent)
+        for ent in test_ents:
+            assert ent in t_pars.filter(ent)
+        for ent in [rt1, p1, p2, r1, r2]:
+            filtered = t_pars.filter(ent)
+            for ent2 in [rt1, p1, p2, r1, r2]:
+                assert ent2 in filtered
+            assert ent in t_pars.filter(pid=100)
+            assert ent in t_pars.filter(pid="100")