|
| 1 | +.. _csharp-polymorphism: |
| 2 | + |
| 3 | +=================== |
| 4 | +Polymorphic Objects |
| 5 | +=================== |
| 6 | + |
| 7 | +.. contents:: On this page |
| 8 | + :local: |
| 9 | + :backlinks: none |
| 10 | + :depth: 2 |
| 11 | + :class: singlecol |
| 12 | + |
| 13 | +.. facet:: |
| 14 | + :name: genre |
| 15 | + :values: reference |
| 16 | + |
| 17 | +.. meta:: |
| 18 | + :keywords: inheritance, child, parent, hierarchy, derived, base, serialize, deserialize, root |
| 19 | + |
| 20 | +Overview |
| 21 | +-------- |
| 22 | + |
| 23 | +**Polymorphic** objects inherit properties and methods from one or more parent classes. |
| 24 | +These objects require special mapping to ensure that the {+driver-short+} correctly |
| 25 | +serializes them to and from BSON documents. |
| 26 | + |
| 27 | +This guide explains the following: |
| 28 | + |
| 29 | +- How to deserialize polymorphic types |
| 30 | +- The discriminator conventions included with the {+driver-short+} |
| 31 | +- How to create custom discriminator conventions |
| 32 | + |
| 33 | +The examples on this page use the following inheritance hierarchy: |
| 34 | + |
| 35 | +.. code-block:: csharp |
| 36 | + |
| 37 | + public class Animal |
| 38 | + { |
| 39 | + } |
| 40 | + |
| 41 | + public class Cat : Animal |
| 42 | + { |
| 43 | + } |
| 44 | + |
| 45 | + public class Dog : Animal |
| 46 | + { |
| 47 | + } |
| 48 | + |
| 49 | + public class Lion : Cat |
| 50 | + { |
| 51 | + } |
| 52 | + |
| 53 | + public class Tiger : Cat |
| 54 | + { |
| 55 | + } |
| 56 | + |
| 57 | +Deserialize Polymorphic Objects |
| 58 | +------------------------------- |
| 59 | + |
| 60 | +Before the serializer can deserialize any polymorphic objects, you must document the |
| 61 | +relationship of all classes in the inheritance hierarchy. |
| 62 | + |
| 63 | +If you're using the automapper to map your classes, apply the ``[BsonKnownTypes]`` |
| 64 | +attribute to each base class in the hierarchy. Pass each class that directly inherits |
| 65 | +from the base class as an argument. |
| 66 | + |
| 67 | +The following example shows how to apply the ``[BsonKnownTypes]`` attribute to |
| 68 | +classes in the example ``Animal`` hierarchy: |
| 69 | + |
| 70 | +.. code-block:: csharp |
| 71 | + :emphasize-lines: 1,6 |
| 72 | + |
| 73 | + [BsonKnownTypes(typeof(Cat), typeof(Dog))] |
| 74 | + public class Animal |
| 75 | + { |
| 76 | + } |
| 77 | + |
| 78 | + [BsonKnownTypes(typeof(Lion), typeof(Tiger))] |
| 79 | + public class Cat : Animal |
| 80 | + { |
| 81 | + } |
| 82 | + |
| 83 | + public class Dog : Animal |
| 84 | + { |
| 85 | + } |
| 86 | + |
| 87 | + public class Lion : Cat |
| 88 | + { |
| 89 | + } |
| 90 | + |
| 91 | + public class Tiger : Cat |
| 92 | + { |
| 93 | + } |
| 94 | + |
| 95 | +.. note:: Using BsonKnownTypes |
| 96 | + |
| 97 | + Apply the ``[BsonKnownTypes]`` attribute only to parent classes. Pass as arguments |
| 98 | + only the types that *directly* inherit from the class, not all child classes in |
| 99 | + the hierarchy. |
| 100 | + |
| 101 | +If you're creating a class map manually, call the |
| 102 | +``BsonClassMap.RegisterClassMap<T>()`` method for every class in the hierarchy, as shown |
| 103 | +in the following example: |
| 104 | + |
| 105 | +.. code-block:: csharp |
| 106 | + |
| 107 | + BsonClassMap.RegisterClassMap<Animal>(); |
| 108 | + BsonClassMap.RegisterClassMap<Cat>(); |
| 109 | + BsonClassMap.RegisterClassMap<Dog>(); |
| 110 | + BsonClassMap.RegisterClassMap<Lion>(); |
| 111 | + BsonClassMap.RegisterClassMap<Tiger>(); |
| 112 | + |
| 113 | +.. tip:: Class Maps |
| 114 | + |
| 115 | + To learn more about mapping classes, see the :ref:`csharp-class-mapping` documentation. |
| 116 | + |
| 117 | +Use Discriminators |
| 118 | +------------------ |
| 119 | + |
| 120 | +In MongoDB, a **discriminator** is a field added to a document to identify the class |
| 121 | +to which the document deserializes. When a collection contains more than one type from a |
| 122 | +single inheritance hierarchy, discriminators ensure that each |
| 123 | +document deserializes to the right class. The {+driver-short+} stores the discriminator |
| 124 | +value in a field named ``_t`` in the BSON document. Generally, ``_t`` is the second |
| 125 | +field in the BSON document after ``_id``. |
| 126 | + |
| 127 | +**Discriminator conventions** define the value stored in the discriminator field. |
| 128 | +In this section, you can learn about the discriminator conventions included |
| 129 | +with the {+driver-short+} and how to create custom discriminator conventions. |
| 130 | + |
| 131 | +ScalarDiscriminatorConvention |
| 132 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 133 | + |
| 134 | +By default, the {+driver-short+} uses the ``ScalarDiscriminatorConvention``. According |
| 135 | +to this convention, the {+driver-short+} sets the value of the ``_t`` field to the name of |
| 136 | +the class from which the document was serialized. |
| 137 | + |
| 138 | +Suppose you create an instance of the example ``Animal`` class and each of its |
| 139 | +subclasses. If you serialize these objects to a single collection, the |
| 140 | +{+driver-short+} applies the ``ScalarDiscriminatorConvention`` and the corresponding |
| 141 | +BSON documents appear as follows: |
| 142 | + |
| 143 | +.. code-block:: json |
| 144 | + :copyable: false |
| 145 | + |
| 146 | + { _id: ..., _t: "Animal", ... } |
| 147 | + { _id: ..., _t: "Cat", ... } |
| 148 | + { _id: ..., _t: "Dog", ... } |
| 149 | + { _id: ..., _t: "Lion", ... } |
| 150 | + { _id: ..., _t: "Tiger", ... } |
| 151 | + |
| 152 | +The ``ScalarDiscriminatorConvention`` uses concise discriminator values, but can be |
| 153 | +difficult to run a query on. For example, to find all documents of type or subtype ``Cat``, |
| 154 | +you must explicitly list each class you're looking for: |
| 155 | + |
| 156 | +.. code-block:: csharp |
| 157 | + :copyable: true |
| 158 | + |
| 159 | + var query = coll.Aggregate().Match(a => a is Cat || a is Lion || a is Tiger); |
| 160 | + |
| 161 | +HierarchicalDiscriminatorConvention |
| 162 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 163 | + |
| 164 | +To simplify queries against your collection of polymorphic types, you can use the |
| 165 | +``HierarchicalDiscriminatorConvention``. According to this convention, the value of ``_t`` |
| 166 | +is an array of all classes in the inheritance hierarchy of the document's type. |
| 167 | + |
| 168 | +To use the ``HierarchicalDiscriminatorConvention``, label the base class of your |
| 169 | +inheritance hierarchy as the root class. If you're using the automapper, |
| 170 | +label the root class by applying the |
| 171 | +``[BsonDiscriminatorAttribute]`` attribute to the class and passing ``RootClass = true`` |
| 172 | +as an argument. The following code example labels the ``Animal`` class as the |
| 173 | +root of the example inheritance hierarchy: |
| 174 | + |
| 175 | +.. code-block:: csharp |
| 176 | + :emphasize-lines: 1 |
| 177 | + |
| 178 | + [BsonDiscriminator(RootClass = true)] |
| 179 | + [BsonKnownTypes(typeof(Cat), typeof(Dog)] |
| 180 | + public class Animal |
| 181 | + { |
| 182 | + } |
| 183 | + |
| 184 | +If you're creating a class map manually, call the ``SetIsRootClass()`` method and pass |
| 185 | +``true`` as an argument when you register the class map for the root class. The following |
| 186 | +code example registers class maps for all five example classes but labels only the |
| 187 | +``Animal`` class as the root of the inheritance hierarchy: |
| 188 | + |
| 189 | +.. code-block:: csharp |
| 190 | + :copyable: true |
| 191 | + :emphasize-lines: 3 |
| 192 | + |
| 193 | + BsonClassMap.RegisterClassMap<Animal>(classMap => { |
| 194 | + classMap.AutoMap(); |
| 195 | + classMap.SetIsRootClass(true); |
| 196 | + }); |
| 197 | + BsonClassMap.RegisterClassMap<Cat>(); |
| 198 | + BsonClassMap.RegisterClassMap<Dog>(); |
| 199 | + BsonClassMap.RegisterClassMap<Lion>(); |
| 200 | + BsonClassMap.RegisterClassMap<Tiger>(); |
| 201 | + |
| 202 | +Suppose you label the example ``Animal`` class as the root of the inheritance hierarchy, |
| 203 | +and then create an instance of the ``Animal`` class and each of its |
| 204 | +subclasses. If you serialize these objects to a single collection, the {+driver-short+} |
| 205 | +applies the ``HierarchicalDiscriminatorConvention`` and the corresponding |
| 206 | +BSON documents appear as follows: |
| 207 | + |
| 208 | +.. code-block:: javascript |
| 209 | + |
| 210 | + { _id: ..., _t: "Animal", ... } |
| 211 | + { _id: ..., _t: ["Animal", "Cat"], ... } |
| 212 | + { _id: ..., _t: ["Animal", "Dog"], ... } |
| 213 | + { _id: ..., _t: ["Animal", "Cat", "Lion"], ... } |
| 214 | + { _id: ..., _t: ["Animal", "Cat", "Tiger"], ... } |
| 215 | + |
| 216 | +.. important:: Root Class Discriminator |
| 217 | + |
| 218 | + Any document mapped to the root class still uses a string value for the discriminator |
| 219 | + field. |
| 220 | + |
| 221 | +When using the ``HierarchicalDiscriminatorConvention``, you can search for all |
| 222 | +documents of type or subtype ``Cat`` by using a single boolean condition, as shown in |
| 223 | +the following example: |
| 224 | + |
| 225 | +.. code-block:: csharp |
| 226 | + :copyable: true |
| 227 | + |
| 228 | + var query = coll.Aggregate().Match(a => a is Cat); |
| 229 | + |
| 230 | +Custom Discriminator Conventions |
| 231 | +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
| 232 | + |
| 233 | +If you're working with data that doesn't follow the conventions used by the |
| 234 | +{+driver-short+}--for example, data inserted into MongoDB by another driver or object |
| 235 | +mapper--you might need to use a different value for your discriminator field to |
| 236 | +ensure your classes align with those conventions. |
| 237 | + |
| 238 | +If you're using the automapper, you can specify a custom value for a class's discriminator |
| 239 | +field by applying the ``[BsonDiscriminator]`` attribute to the class and passing |
| 240 | +the custom discriminator value as a string argument. The following code example |
| 241 | +sets the value of the discriminator field for the ``Animal`` class to "myAnimalClass": |
| 242 | + |
| 243 | +.. code-block:: csharp |
| 244 | + :emphasize-lines: 1 |
| 245 | + |
| 246 | + [BsonDiscriminator("myAnimalClass")] |
| 247 | + public class Animal |
| 248 | + { |
| 249 | + } |
| 250 | + |
| 251 | +If you're creating a class map manually, call the ``SetDiscriminator()`` method and pass |
| 252 | +the custom discriminator value as an argument when |
| 253 | +you register the class map. The following code example sets the value of the |
| 254 | +discriminator field for the ``Animal`` class to "myAnimalClass": |
| 255 | + |
| 256 | +.. code-block:: csharp |
| 257 | + :emphasize-lines: 4 |
| 258 | + |
| 259 | + BsonClassMap.RegisterClassMap<Animal>(classMap => |
| 260 | + { |
| 261 | + classMap.AutoMap(); |
| 262 | + classMap.SetDiscriminator("myAnimalClass"); |
| 263 | + }); |
| 264 | + |
| 265 | +An instance of the previous ``Animal`` class appears as follows after serialization: |
| 266 | + |
| 267 | +.. code-block:: json |
| 268 | + :copyable: false |
| 269 | + |
| 270 | + { "_id": "...", "_t": "myAnimalClass"} |
0 commit comments