diff --git a/src/protean/core/entity.py b/src/protean/core/entity.py
index 5dc8f409..7779faf4 100644
--- a/src/protean/core/entity.py
+++ b/src/protean/core/entity.py
@@ -100,6 +100,10 @@ class QuerySet:
     without actually fetching data. No data fetch actually occurs until you do something
     to evaluate the queryset.
 
+    When being evaluated, a `QuerySet` typically caches its results. If the data in the database
+    might have changed, you can get updated results for the same query by calling `all()` on a
+    previously evaluated `QuerySet`.
+
     Attributes:
         page: The current page number of the records to be pulled
         per_page: The size of each page of the records to be pulled
@@ -133,6 +137,8 @@ class QuerySet:
         self._excludes = excludes_ or {}
         self._filters = filters
 
+        self._result_cache = None
+
     def _clone(self):
         """
         Return a copy of the current QuerySet.
@@ -199,6 +205,9 @@ class QuerySet:
         """
         logger.debug(f'Query `{self.__class__.__name__}` objects with filters {self}')
 
+        # Destroy any cached results
+        self._result_cache = None
+
         # Fetch Model class and connected-adapter from Repository Factory
         model_cls, adapter = self._retrieve_model()
 
@@ -215,6 +224,9 @@ class QuerySet:
             entity_items.append(model_cls.to_entity(item))
         results.items = entity_items
 
+        # Cache results
+        self._result_cache = results
+
         return results
 
     ###############################
@@ -223,14 +235,23 @@ class QuerySet:
 
     def __iter__(self):
         """Return results on iteration"""
+        if self._result_cache:
+            return iter(self._result_cache)
+
         return iter(self.all())
 
     def __len__(self):
         """Return length of results"""
+        if self._result_cache:
+            return self._result_cache.total
+
         return self.all().total
 
     def __bool__(self):
         """Return True if query results have items"""
+        if self._result_cache:
+            return bool(self._result_cache)
+
         return bool(self.all())
 
     def __repr__(self):
@@ -239,6 +260,9 @@ class QuerySet:
 
     def __getitem__(self, k):
         """Support slicing of results"""
+        if self._result_cache:
+            return self._result_cache.items[k]
+
         return self.all().items[k]
 
     #########################
@@ -248,26 +272,41 @@ class QuerySet:
     @property
     def total(self):
         """Return the total number of records"""
+        if self._result_cache:
+            return self._result_cache.total
+
         return self.all().total
 
     @property
     def items(self):
         """Return result values"""
+        if self._result_cache:
+            return self._result_cache.items
+
         return self.all().items
 
     @property
     def first(self):
         """Return the first result"""
+        if self._result_cache:
+            return self._result_cache.first
+
         return self.all().first
 
     @property
     def has_next(self):
         """Return True if there are more values present"""
+        if self._result_cache:
+            return self._result_cache.has_next
+
         return self.all().has_next
 
     @property
     def has_prev(self):
         """Return True if there are previous values present"""
+        if self._result_cache:
+            return self._result_cache.has_prev
+
         return self.all().has_prev
 
 
diff --git a/src/protean/core/repository/pagination.py b/src/protean/core/repository/pagination.py
index 916c6135..878bfb9a 100644
--- a/src/protean/core/repository/pagination.py
+++ b/src/protean/core/repository/pagination.py
@@ -30,7 +30,7 @@ class Pagination(object):
     @property
     def has_prev(self):
         """True if a previous page exists"""
-        return self.page > 1
+        return bool(self.items) and self.page > 1
 
     @property
     def has_next(self):

Cache query results as part of the QuerySet object
The results returned by a query should be cached as part of the QuerySet object so that subsequent requests do not fire on the repository. A new QuerySet object is returned anyway on a criteria change (or on the amendment of query preferences - page, per_page, order_by), so the result cache will be empty.
diff --git a/tests/core/test_entity.py b/tests/core/test_entity.py
index 6fd61850..392e2890 100644
--- a/tests/core/test_entity.py
+++ b/tests/core/test_entity.py
@@ -481,7 +481,7 @@ class TestQuerySet:
         query = Dog.query.filter(owner='John').order_by('age')
         assert repr(query) == ("<QuerySet: {'_entity_cls_name': 'Dog', '_page': 1, "
                                "'_per_page': 10, '_order_by': {'age'}, '_excludes': {}, "
-                               "'_filters': {'owner': 'John'}}>")
+                               "'_filters': {'owner': 'John'}, '_result_cache': None}>")
 
     def test_bool_false(self):
         """Test that `bool` returns `False` on no records"""
@@ -523,6 +523,43 @@ class TestQuerySet:
         sliced = query[1:]
         assert len(sliced) == 4
 
+    def test_caching(self):
+        """Test that results are cached after query is evaluated"""
+        # Add multiple entries to the DB
+        Dog.create(id=2, name='Murdock', age=7, owner='John')
+        Dog.create(id=3, name='Jean', age=3, owner='John')
+        Dog.create(id=4, name='Bart', age=6, owner='Carrie')
+
+        # Filter by Dog attributes
+        query = Dog.query.filter(owner='John').order_by('age')
+
+        # Result cache is empty to begin with
+        assert query._result_cache is None
+
+        # Total invokes an evaluation and a query
+        assert query.total == 2
+
+        # Result cache is now populated
+        assert query._result_cache is not None
+        assert query._result_cache.total == 2
+
+    def test_cache_reset(self):
+        """Test that results are cached after query is evaluated"""
+        # Add multiple entries to the DB
+        Dog.create(id=2, name='Murdock', age=7, owner='John')
+        Dog.create(id=3, name='Jean', age=3, owner='John')
+        Dog.create(id=4, name='Bart', age=6, owner='Carrie')
+
+        # Filter by Dog attributes
+        query = Dog.query.filter(owner='John').order_by('age')
+
+        # Total invokes an evaluation and a query
+        assert query.total == 2
+        assert query._result_cache.total == 2
+
+        query_dup = query.paginate(per_page=25)
+        assert query_dup._result_cache is None
+
     def test_total(self):
         """Test value of `total` results"""
         # Add multiple entries to the DB
@@ -534,6 +571,27 @@ class TestQuerySet:
         query = Dog.query.filter(owner='John').order_by('age')
         assert query.total == 2
 
+    def test_total_with_cache(self):
+        """Test value of `total` results without refresh"""
+        # Add multiple entries to the DB
+        Dog.create(id=2, name='Murdock', age=7, owner='John')
+        Dog.create(id=3, name='Jean', age=3, owner='John')
+        Dog.create(id=4, name='Bart', age=6, owner='Carrie')
+
+        # Filter by Dog attributes
+        query = Dog.query.filter(owner='John').order_by('age')
+        assert query.total == 2
+
+        Dog.create(id=5, name='Berry', age=1, owner='John')
+        assert query.total == 2
+        assert query._result_cache.total == 2
+
+        # Force a refresh
+        assert query.all().total == 3
+
+        # Result cache is now populated
+        assert query._result_cache.total == 3
+
     def test_items(self):
         """Test that items is retrieved from Pagination results"""
         # Add multiple entries to the DB
@@ -545,6 +603,22 @@ class TestQuerySet:
         query = Dog.query.filter(owner='John').order_by('age')
         assert query.items[0].id == query.all().items[0].id
 
+    def test_items_with_cache(self):
+        """Test that items is retrieved from Pagination results"""
+        # Add multiple entries to the DB
+        Dog.create(id=2, name='Murdock', age=7, owner='John')
+        Dog.create(id=3, name='Jean', age=3, owner='John')
+        Dog.create(id=4, name='Bart', age=6, owner='Carrie')
+
+        # Filter by Dog attributes
+        query = Dog.query.filter(owner='John').order_by('age')
+        assert query.items[0].id == 3
+
+        Dog.create(id=5, name='Berry', age=1, owner='John')
+        assert query.items[0].id == 3
+
+        assert query.all().items[0].id == 5
+
     def test_has_next(self):
         """Test if there are results after the current set"""
         # Add multiple entries to the DB
@@ -556,6 +630,22 @@ class TestQuerySet:
         query = Dog.query.paginate(page=1, per_page=2)
         assert query.has_next is True
 
+    def test_has_next_with_cache(self):
+        """Test if there are results after the current set"""
+        # Add multiple entries to the DB
+        Dog.create(id=2, name='Murdock', age=7, owner='John')
+        Dog.create(id=3, name='Jean', age=3, owner='John')
+        dog = Dog.create(id=4, name='Bart', age=6, owner='Carrie')
+
+        # Filter by Dog attributes
+        query = Dog.query.paginate(page=1, per_page=2)
+        assert query.has_next is True
+
+        dog.delete()
+
+        assert query.has_next is True
+        assert query.all().has_next is False
+
     def test_has_prev(self):
         """Test if there are results before the current set"""
         # Add multiple entries to the DB
@@ -567,6 +657,22 @@ class TestQuerySet:
         query = Dog.query.paginate(page=2, per_page=2)
         assert query.has_prev is True
 
+    def test_has_prev_with_cache(self):
+        """Test if there are results before the current set"""
+        # Add multiple entries to the DB
+        Dog.create(id=2, name='Murdock', age=7, owner='John')
+        Dog.create(id=3, name='Jean', age=3, owner='John')
+        dog = Dog.create(id=4, name='Bart', age=6, owner='Carrie')
+
+        # Filter by Dog attributes
+        query = Dog.query.paginate(page=2, per_page=2)
+        assert query.has_prev is True
+
+        dog.delete()
+
+        assert query.has_prev is True
+        assert query.all().has_prev is False
+
     def test_first(self):
         """Test that the first item is retrieved correctly from the resultset"""
         # Add multiple entries to the DB
@@ -577,3 +683,18 @@ class TestQuerySet:
         # Filter by Dog attributes
         query = Dog.query.order_by('-age')
         assert query.first.id == 2
+
+    def test_first_with_cache(self):
+        """Test that the first item is retrieved correctly from the resultset"""
+        # Add multiple entries to the DB
+        Dog.create(id=2, name='Murdock', age=7, owner='John')
+        Dog.create(id=3, name='Jean', age=3, owner='John')
+        Dog.create(id=4, name='Bart', age=6, owner='Carrie')
+
+        # Filter by Dog attributes
+        query = Dog.query.order_by('-age')
+        assert query.first.id == 2
+
+        Dog.create(id=5, name='Berry', age=8, owner='John')
+        assert query.first.id == 2
+        assert query.all().first.id == 5

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc
3.9
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
alabaster==0.7.16
astroid==3.3.9
babel==2.17.0
certifi==2025.1.31
charset-normalizer==3.4.1
check-manifest==0.37
Click==7.0
coverage==4.5.1
distlib==0.3.9
docutils==0.14
exceptiongroup==1.2.2
filelock==3.18.0
flake8==3.5.0
idna==3.10
imagesize==1.4.1
iniconfig==2.1.0
isort==6.0.1
Jinja2==3.1.6
MarkupSafe==3.0.2
mccabe==0.6.1
packaging==24.2
pkginfo==1.12.1.2
platformdirs==4.3.7
pluggy==1.5.0
-e git+https://github.com/proteanhq/protean.git@a21ebb70b299a56337c4af24fd8f118de787225d#egg=protean
py==1.11.0
pycodestyle==2.3.1
pyflakes==1.6.0
Pygments==2.19.1
pylint==2.0.0
pytest==8.3.5
python-dateutil==2.7.3
requests==2.32.3
requests-toolbelt==1.0.0
six==1.17.0
snowballstemmer==2.2.0
Sphinx==1.8.3
sphinxcontrib-serializinghtml==2.0.0
sphinxcontrib-websupport==1.2.4
tomli==2.2.1
tox==3.1.2
tqdm==4.67.1
twine==1.11.0
typing_extensions==4.13.0
urllib3==2.3.0
virtualenv==20.29.3

name: protean
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - alabaster==0.7.16
      - astroid==3.3.9
      - babel==2.17.0
      - certifi==2025.1.31
      - charset-normalizer==3.4.1
      - check-manifest==0.37
      - click==7.0
      - coverage==4.5.1
      - distlib==0.3.9
      - docutils==0.14
      - exceptiongroup==1.2.2
      - filelock==3.18.0
      - flake8==3.5.0
      - idna==3.10
      - imagesize==1.4.1
      - iniconfig==2.1.0
      - isort==6.0.1
      - jinja2==3.1.6
      - markupsafe==3.0.2
      - mccabe==0.6.1
      - packaging==24.2
      - pkginfo==1.12.1.2
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - py==1.11.0
      - pycodestyle==2.3.1
      - pyflakes==1.6.0
      - pygments==2.19.1
      - pylint==2.0.0
      - pytest==8.3.5
      - python-dateutil==2.7.3
      - requests==2.32.3
      - requests-toolbelt==1.0.0
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==1.8.3
      - sphinxcontrib-serializinghtml==2.0.0
      - sphinxcontrib-websupport==1.2.4
      - tomli==2.2.1
      - tox==3.1.2
      - tqdm==4.67.1
      - twine==1.11.0
      - typing-extensions==4.13.0
      - urllib3==2.3.0
      - virtualenv==20.29.3
prefix: /opt/conda/envs/protean

tests/core/test_entity.py::TestQuerySet::test_repr tests/core/test_entity.py::TestQuerySet::test_caching tests/core/test_entity.py::TestQuerySet::test_cache_reset tests/core/test_entity.py::TestQuerySet::test_total_with_cache tests/core/test_entity.py::TestQuerySet::test_items_with_cache tests/core/test_entity.py::TestQuerySet::test_has_next_with_cache tests/core/test_entity.py::TestQuerySet::test_has_prev_with_cache tests/core/test_entity.py::TestQuerySet::test_first_with_cache
tests/core/test_entity.py::TestEntity::test_init tests/core/test_entity.py::TestEntity::test_required_fields tests/core/test_entity.py::TestEntity::test_defaults tests/core/test_entity.py::TestEntity::test_validate_string_length tests/core/test_entity.py::TestEntity::test_validate_data_value_against_type tests/core/test_entity.py::TestEntity::test_template_init tests/core/test_entity.py::TestEntity::test_error_messages tests/core/test_entity.py::TestEntity::test_entity_inheritance tests/core/test_entity.py::TestEntity::test_default_id tests/core/test_entity.py::TestEntity::test_to_dict tests/core/test_entity.py::TestEntity::test_get tests/core/test_entity.py::TestEntity::test_get_object_not_found_error tests/core/test_entity.py::TestEntity::test_find_by tests/core/test_entity.py::TestEntity::test_find_by_object_not_found_error tests/core/test_entity.py::TestEntity::test_find_by_multiple_attributes tests/core/test_entity.py::TestEntity::test_find_by_multiple_attributes_object_not_found_error tests/core/test_entity.py::TestEntity::test_create_error tests/core/test_entity.py::TestEntity::test_create tests/core/test_entity.py::TestEntity::test_save tests/core/test_entity.py::TestEntity::test_save_validation_error tests/core/test_entity.py::TestEntity::test_update_with_invalid_id tests/core/test_entity.py::TestEntity::test_update_with_dict tests/core/test_entity.py::TestEntity::test_update_with_kwargs tests/core/test_entity.py::TestEntity::test_update_with_dict_and_kwargs tests/core/test_entity.py::TestEntity::test_that_update_runs_validations tests/core/test_entity.py::TestEntity::test_unique tests/core/test_entity.py::TestEntity::test_query_init tests/core/test_entity.py::TestEntity::test_filter_chain_initialization_from_entity tests/core/test_entity.py::TestEntity::test_filter_chaining tests/core/test_entity.py::TestEntity::test_filter_stored_value tests/core/test_entity.py::TestEntity::test_filter_chain_results_1 tests/core/test_entity.py::TestEntity::test_filter_chain_results_2 tests/core/test_entity.py::TestEntity::test_filter_chain_results_3 tests/core/test_entity.py::TestEntity::test_filter_norm tests/core/test_entity.py::TestEntity::test_exclude tests/core/test_entity.py::TestEntity::test_exclude_multiple tests/core/test_entity.py::TestEntity::test_pagination tests/core/test_entity.py::TestEntity::test_delete tests/core/test_entity.py::TestQuerySet::test_clone tests/core/test_entity.py::TestQuerySet::test_list tests/core/test_entity.py::TestQuerySet::test_bool_false tests/core/test_entity.py::TestQuerySet::test_bool_true tests/core/test_entity.py::TestQuerySet::test_len tests/core/test_entity.py::TestQuerySet::test_slice tests/core/test_entity.py::TestQuerySet::test_total tests/core/test_entity.py::TestQuerySet::test_items tests/core/test_entity.py::TestQuerySet::test_has_next tests/core/test_entity.py::TestQuerySet::test_has_prev tests/core/test_entity.py::TestQuerySet::test_first
src/protean/core/repository/pagination.py src/protean/core/entity.py
src/protean/core/repository/pagination.py src/protean/core/entity.py