diff --git a/django/db/models/fields/related.py b/django/db/models/fields/related.py
index f5fdaa55ee..8ddbb5ba68 100644
--- a/django/db/models/fields/related.py
+++ b/django/db/models/fields/related.py
@@ -528,6 +528,10 @@ class ForeignObject(RelatedField):
             frozenset(ut)
             for ut in self.remote_field.model._meta.unique_together
         })
+        unique_foreign_fields.update({
+            frozenset(uc.fields)
+            for uc in self.remote_field.model._meta.total_unique_constraints
+        })
         foreign_fields = {f.name for f in self.foreign_related_fields}
         has_unique_constraint = any(u <= foreign_fields for u in unique_foreign_fields)
 
@@ -541,8 +545,10 @@ class ForeignObject(RelatedField):
                     "No subset of the fields %s on model '%s' is unique."
                     % (field_combination, model_name),
                     hint=(
-                        "Add unique=True on any of those fields or add at "
-                        "least a subset of them to a unique_together constraint."
+                        'Mark a single field as unique=True or add a set of '
+                        'fields to a unique constraint (via unique_together '
+                        'or a UniqueConstraint (without condition) in the '
+                        'model Meta.constraints).'
                     ),
                     obj=self,
                     id='fields.E310',
@@ -553,8 +559,13 @@ class ForeignObject(RelatedField):
             model_name = self.remote_field.model.__name__
             return [
                 checks.Error(
-                    "'%s.%s' must set unique=True because it is referenced by "
+                    "'%s.%s' must be unique because it is referenced by "
                     "a foreign key." % (model_name, field_name),
+                    hint=(
+                        'Add unique=True to this field or add a '
+                        'UniqueConstraint (without condition) in the model '
+                        'Meta.constraints.'
+                    ),
                     obj=self,
                     id='fields.E311',
                 )
diff --git a/docs/ref/checks.txt b/docs/ref/checks.txt
index ce5cf5b45a..57c7b56fa9 100644
--- a/docs/ref/checks.txt
+++ b/docs/ref/checks.txt
@@ -220,7 +220,7 @@ Related fields
   ``'__'``.
 * **fields.E310**: No subset of the fields ``<field1>``, ``<field2>``, ... on
   model ``<model>`` is unique.
-* **fields.E311**: ``<model>`` must set ``unique=True`` because it is
+* **fields.E311**: ``<model>.<field name>`` must be unique because it is
   referenced by a ``ForeignKey``.
 * **fields.E312**: The ``to_field`` ``<field name>`` doesn't exist on the
   related model ``<app label>.<model>``.
diff --git a/tests/invalid_models_tests/test_relative_fields.py b/tests/invalid_models_tests/test_relative_fields.py
index 4211973cf3..24f27168c2 100644
--- a/tests/invalid_models_tests/test_relative_fields.py
+++ b/tests/invalid_models_tests/test_relative_fields.py
@@ -352,7 +352,11 @@ class RelativeFieldTests(SimpleTestCase):
         field = Model._meta.get_field('foreign_key')
         self.assertEqual(field.check(), [
             Error(
-                "'Target.bad' must set unique=True because it is referenced by a foreign key.",
+                "'Target.bad' must be unique because it is referenced by a foreign key.",
+                hint=(
+                    'Add unique=True to this field or add a UniqueConstraint '
+                    '(without condition) in the model Meta.constraints.'
+                ),
                 obj=field,
                 id='fields.E311',
             ),
@@ -368,12 +372,64 @@ class RelativeFieldTests(SimpleTestCase):
         field = Model._meta.get_field('field')
         self.assertEqual(field.check(), [
             Error(
-                "'Target.bad' must set unique=True because it is referenced by a foreign key.",
+                "'Target.bad' must be unique because it is referenced by a foreign key.",
+                hint=(
+                    'Add unique=True to this field or add a UniqueConstraint '
+                    '(without condition) in the model Meta.constraints.'
+                ),
                 obj=field,
                 id='fields.E311',
             ),
         ])
 
+    def test_foreign_key_to_partially_unique_field(self):
+        class Target(models.Model):
+            source = models.IntegerField()
+
+            class Meta:
+                constraints = [
+                    models.UniqueConstraint(
+                        fields=['source'],
+                        name='tfktpuf_partial_unique',
+                        condition=models.Q(pk__gt=2),
+                    ),
+                ]
+
+        class Model(models.Model):
+            field = models.ForeignKey(Target, models.CASCADE, to_field='source')
+
+        field = Model._meta.get_field('field')
+        self.assertEqual(field.check(), [
+            Error(
+                "'Target.source' must be unique because it is referenced by a "
+                "foreign key.",
+                hint=(
+                    'Add unique=True to this field or add a UniqueConstraint '
+                    '(without condition) in the model Meta.constraints.'
+                ),
+                obj=field,
+                id='fields.E311',
+            ),
+        ])
+
+    def test_foreign_key_to_unique_field_with_meta_constraint(self):
+        class Target(models.Model):
+            source = models.IntegerField()
+
+            class Meta:
+                constraints = [
+                    models.UniqueConstraint(
+                        fields=['source'],
+                        name='tfktufwmc_unique',
+                    ),
+                ]
+
+        class Model(models.Model):
+            field = models.ForeignKey(Target, models.CASCADE, to_field='source')
+
+        field = Model._meta.get_field('field')
+        self.assertEqual(field.check(), [])
+
     def test_foreign_object_to_non_unique_fields(self):
         class Person(models.Model):
             # Note that both fields are not unique.
@@ -396,14 +452,82 @@ class RelativeFieldTests(SimpleTestCase):
             Error(
                 "No subset of the fields 'country_id', 'city_id' on model 'Person' is unique.",
                 hint=(
-                    "Add unique=True on any of those fields or add at least "
-                    "a subset of them to a unique_together constraint."
+                    'Mark a single field as unique=True or add a set of '
+                    'fields to a unique constraint (via unique_together or a '
+                    'UniqueConstraint (without condition) in the model '
+                    'Meta.constraints).'
                 ),
                 obj=field,
                 id='fields.E310',
             )
         ])
 
+    def test_foreign_object_to_partially_unique_field(self):
+        class Person(models.Model):
+            country_id = models.IntegerField()
+            city_id = models.IntegerField()
+
+            class Meta:
+                constraints = [
+                    models.UniqueConstraint(
+                        fields=['country_id', 'city_id'],
+                        name='tfotpuf_partial_unique',
+                        condition=models.Q(pk__gt=2),
+                    ),
+                ]
+
+        class MMembership(models.Model):
+            person_country_id = models.IntegerField()
+            person_city_id = models.IntegerField()
+            person = models.ForeignObject(
+                Person,
+                on_delete=models.CASCADE,
+                from_fields=['person_country_id', 'person_city_id'],
+                to_fields=['country_id', 'city_id'],
+            )
+
+        field = MMembership._meta.get_field('person')
+        self.assertEqual(field.check(), [
+            Error(
+                "No subset of the fields 'country_id', 'city_id' on model "
+                "'Person' is unique.",
+                hint=(
+                    'Mark a single field as unique=True or add a set of '
+                    'fields to a unique constraint (via unique_together or a '
+                    'UniqueConstraint (without condition) in the model '
+                    'Meta.constraints).'
+                ),
+                obj=field,
+                id='fields.E310',
+            ),
+        ])
+
+    def test_foreign_object_to_unique_field_with_meta_constraint(self):
+        class Person(models.Model):
+            country_id = models.IntegerField()
+            city_id = models.IntegerField()
+
+            class Meta:
+                constraints = [
+                    models.UniqueConstraint(
+                        fields=['country_id', 'city_id'],
+                        name='tfotpuf_unique',
+                    ),
+                ]
+
+        class MMembership(models.Model):
+            person_country_id = models.IntegerField()
+            person_city_id = models.IntegerField()
+            person = models.ForeignObject(
+                Person,
+                on_delete=models.CASCADE,
+                from_fields=['person_country_id', 'person_city_id'],
+                to_fields=['country_id', 'city_id'],
+            )
+
+        field = MMembership._meta.get_field('person')
+        self.assertEqual(field.check(), [])
+
     def test_on_delete_set_null_on_non_nullable_field(self):
         class Person(models.Model):
             pass
@@ -1453,8 +1577,10 @@ class M2mThroughFieldsTests(SimpleTestCase):
             Error(
                 "No subset of the fields 'a', 'b' on model 'Parent' is unique.",
                 hint=(
-                    "Add unique=True on any of those fields or add at least "
-                    "a subset of them to a unique_together constraint."
+                    'Mark a single field as unique=True or add a set of '
+                    'fields to a unique constraint (via unique_together or a '
+                    'UniqueConstraint (without condition) in the model '
+                    'Meta.constraints).'
                 ),
                 obj=field,
                 id='fields.E310',
@@ -1489,8 +1615,10 @@ class M2mThroughFieldsTests(SimpleTestCase):
             Error(
                 "No subset of the fields 'a', 'b', 'd' on model 'Parent' is unique.",
                 hint=(
-                    "Add unique=True on any of those fields or add at least "
-                    "a subset of them to a unique_together constraint."
+                    'Mark a single field as unique=True or add a set of '
+                    'fields to a unique constraint (via unique_together or a '
+                    'UniqueConstraint (without condition) in the model '
+                    'Meta.constraints).'
                 ),
                 obj=field,
                 id='fields.E310',