Skip to content

Commit 139fe47

Browse files
authored
DOCSP-26552 - polymorphism (#143)
1 parent 22e0f73 commit 139fe47

File tree

3 files changed

+273
-44
lines changed

3 files changed

+273
-44
lines changed

source/fundamentals/class-mapping.txt

Lines changed: 0 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -127,49 +127,6 @@ You can also ignore any extra elements when registering a class map:
127127
classMap.SetIgnoreExtraElements(true);
128128
});
129129

130-
Using Class Discriminators
131-
~~~~~~~~~~~~~~~~~~~~~~~~~~
132-
133-
You can specify **discriminators** to help identify **polymorphic** classes that
134-
are serialized to the same collection. Polymorphic classes are classes that
135-
inherit properties and methods from a parent class. A discriminator is an
136-
element that's added to a document to identify which class the document was
137-
serialized from.
138-
139-
You can specify a discriminator using the ``BsonDiscriminator`` attribute as
140-
follows:
141-
142-
.. code-block:: csharp
143-
144-
[BsonDiscriminator("personClass")]
145-
public class Person
146-
{
147-
public string Name { get; set; }
148-
public int Age { get; set; }
149-
public List<string> Hobbies {get; set;}
150-
}
151-
152-
You can also specify a discriminator when registering a class map as follows:
153-
154-
.. code-block:: csharp
155-
156-
BsonClassMap.RegisterClassMap<Person>(classMap =>
157-
{
158-
classMap.AutoMap();
159-
classMap.SetDiscriminator("personClass");
160-
});
161-
162-
In BSON, discriminators have the field name ``_t``.
163-
164-
The following example shows how a document from the ``Person`` class with the
165-
"personClass" discriminator appears in the collection after serialization:
166-
167-
.. code-block:: json
168-
169-
{ "_id": "...", "_t": "personClass", "Name": "...", "Age": "...", "Hobbies": [...]}
170-
171-
.. TODO: Link to page on polymorphism/discriminators
172-
173130
Mapping with Constructors
174131
-------------------------
175132

source/fundamentals/data-formats.txt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ Data Formats
1313
/fundamentals/data-formats/poco
1414
/fundamentals/data-formats/guid-serialization
1515
/fundamentals/data-formats/serialization
16+
/fundamentals/data-formats/polymorphism
1617

1718
- :ref:`csharp-bson`
1819
- :ref:`csharp-poco`
1920
- :ref:`csharp-guids`
20-
- :ref:`csharp-serialization`
21+
- :ref:`csharp-serialization`
22+
- :ref:`csharp-polymorphism`
Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
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

Comments
 (0)