-
Notifications
You must be signed in to change notification settings - Fork 934
/
Copy pathpersistent_classes.xml
573 lines (477 loc) · 21.6 KB
/
persistent_classes.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
<chapter id="persistent-classes">
<title>Persistent Classes</title>
<para>
Persistent classes are classes in an application that implement the entities
of the business problem (e.g. Customer and Order in an E-commerce application).
Persistent classes have, as the name implies, transient and also persistent
instance stored in the database.
</para>
<para>
NHibernate works best if these classes follow some simple rules, also known
as the Plain Old CLR Object (POCO) programming model.
</para>
<sect1 id="persistent-classes-poco">
<title>A simple POCO example</title>
<para>
Most .NET applications require a persistent class representing felines.
</para>
<programlisting><![CDATA[using System;
using System.Collections.Generic;
namespace Eg
{
public class Cat
{
long _id;
// identifier
public virtual long Id
{
get { return _id; }
protected set { _id = value; }
}
public virtual string Name { get; set; }
public virtual Cat Mate { get; set; }
public virtual DateTime Birthdate { get; set; }
public virtual float Weight { get; set; }
public virtual Color Color { get; set; }
public virtual ISet<Cat> Kittens { get; set; }
public virtual char Sex { get; set; }
// AddKitten not needed by NHibernate
public virtual void AddKitten(Cat kitten)
{
kittens.Add(kitten);
}
}
}]]></programlisting>
<para>
There are four main rules to follow here:
</para>
<sect2 id="persistent-classes-poco-accessors">
<title>Declare properties for persistent fields</title>
<para>
<literal>Cat</literal> declares properties for all the persistent fields.
Many other <emphasis>ORM tools</emphasis> directly persist instance variables. We believe
it is far better to decouple this implementation detail from the persistence
mechanism. NHibernate persists properties, using their getter and setter methods.
</para>
<para>
Properties need <emphasis>not</emphasis> be declared public - NHibernate can
persist a property with an <literal>internal</literal>, <literal>protected</literal>,
<literal>protected internal</literal> or <literal>private</literal> visibility.
</para>
<para>
As shown in the example, both automatic properties and properties with a
backing field are supported.
</para>
</sect2>
<sect2 id="persistent-classes-poco-constructor">
<title>Implement a default constructor</title>
<para>
<literal>Cat</literal> has an implicit default (no-argument) constructor. All
persistent classes must have a default constructor (which may be non-public) so
NHibernate can instantiate them using <literal>Activator.CreateInstance()</literal>.
</para>
</sect2>
<sect2 id="persistent-classes-poco-identifier">
<title>Provide an identifier property (optional)</title>
<para>
<literal>Cat</literal> has a property called <literal>Id</literal>. This property
holds the primary key column of a database table. The property might have been called
anything, and its type might have been any primitive type, <literal>string</literal>
or <literal>System.DateTime</literal>. (If your legacy database table has composite
keys, you can even use a user-defined class with properties of these types - see the
section on composite identifiers below.)
</para>
<para>
The identifier property is optional. You can leave it off and let NHibernate keep track
of object identifiers internally. However, for many applications it is still
a good (and very popular) design decision.
</para>
<para>
What's more, some functionality is available only to classes which declare an
identifier property:
</para>
<itemizedlist spacing="compact">
<listitem>
<para>
Cascaded updates (see <xref linkend="manipulatingdata-graphs"/>)
</para>
</listitem>
<listitem>
<para>
<literal>ISession.SaveOrUpdate()</literal>
</para>
</listitem>
</itemizedlist>
<para>
We recommend you declare consistently-named identifier properties on persistent
classes.
</para>
</sect2>
<sect2 id="persistent-classes-poco-sealed">
<title>Prefer non-sealed classes and virtual methods (optional)</title>
<para>
A central feature of NHibernate, <emphasis>proxies</emphasis>, depends upon the
persistent class being non-sealed and all its public methods, properties and
events declared as virtual. Another possibility is for the class to implement
an interface that declares all public members.
</para>
<para>
You can persist <literal>sealed</literal> classes that do not implement an interface
and don't have virtual members with NHibernate, but you won't be able to use proxies
- which will limit your options for performance tuning.
</para>
</sect2>
</sect1>
<sect1 id="persistent-classes-inheritance">
<title>Implementing inheritance</title>
<para>
A subclass must also observe the first and second rules. It inherits its
identifier property from <literal>Cat</literal>.
</para>
<programlisting><![CDATA[using System;
namespace Eg
{
public class DomesticCat : Cat
{
public virtual string Name { get; set; }
}
}]]></programlisting>
</sect1>
<sect1 id="persistent-classes-equalshashcode">
<title>Implementing <literal>Equals()</literal> and <literal>GetHashCode()</literal></title>
<para>
You have to override the <literal>Equals()</literal> and <literal>GetHashCode()</literal>
methods if you intend to mix objects of persistent classes (e.g. in an <literal>ISet</literal>).
</para>
<para>
<emphasis>This only applies if these objects are loaded in two different
<literal>ISession</literal>s, as NHibernate only guarantees identity (<literal> a == b </literal>,
the default implementation of <literal>Equals()</literal>) inside a single
<literal>ISession</literal>!</emphasis>
</para>
<para>
Even if both objects <literal>a</literal> and <literal>b</literal> are the same database row
(they have the same primary key value as their identifier), we can't guarantee that they are
the same object instance outside of a particular <literal>ISession</literal> context.
</para>
<para>
The most obvious way is to implement <literal>Equals()</literal>/<literal>GetHashCode()</literal>
by comparing the identifier value of both objects. If the value is the same, both must
be the same database row, they are therefore equal (if both are added to an <literal>ISet</literal>,
we will only have one element in the <literal>ISet</literal>). Unfortunately, we can't use that
approach. NHibernate will only assign identifier values to objects that are persistent,
a newly created instance will not have any identifier value! We recommend implementing
<literal>Equals()</literal> and <literal>GetHashCode()</literal> using
<emphasis>Business key equality</emphasis>.
</para>
<para>
Business key equality means that the <literal>Equals()</literal>
method compares only the properties that form the business key, a key that would
identify our instance in the real world (a <emphasis>natural</emphasis> candidate key):
</para>
<programlisting><![CDATA[public class Cat
{
...
public override bool Equals(object other)
{
if (this == other) return true;
Cat cat = other as Cat;
if (cat == null) return false; // null or not a cat
if (Name != cat.Name) return false;
if (!Birthday.Equals(cat.Birthday)) return false;
return true;
}
public override int GetHashCode()
{
unchecked
{
int result;
result = Name.GetHashCode();
result = 29 * result + Birthday.GetHashCode();
return result;
}
}
}]]></programlisting>
<para>
Keep in mind that our candidate key (in this case a composite of name and birthday)
has to be only valid for a particular comparison operation (maybe even only in a
single use case). We don't need the stability criteria we usually apply to a real
primary key!
</para>
</sect1>
<sect1 id="persistent-classes-dynamicmodels">
<title>Dynamic models</title>
<para>
<emphasis>Note that the following features are currently considered
experimental and may change in the near future.</emphasis>
</para>
<para>
Persistent entities don't necessarily have to be represented as POCO classes
at runtime. NHibernate also supports dynamic models
(using <literal>Dictionaries</literal> or C# <literal>dynamic</literal>). With this approach,
you don't write persistent classes, only mapping files.
</para>
<para>
The following examples demonstrates the dynamic model feature.
First, in the mapping file, an <literal>entity-name</literal> has to be declared
instead of a class name:
</para>
<programlisting><![CDATA[<hibernate-mapping>
<class entity-name="Customer">
<id name="Id"
type="long"
column="ID">
<generator class="sequence"/>
</id>
<property name="Name"
column="NAME"
type="string"/>
<property name="Address"
column="ADDRESS"
type="string"/>
<many-to-one name="Organization"
column="ORGANIZATION_ID"
class="Organization"/>
<bag name="Orders"
inverse="true"
lazy="false"
cascade="all">
<key column="CUSTOMER_ID"/>
<one-to-many class="Order"/>
</bag>
</class>
</hibernate-mapping>]]></programlisting>
<para>
Note that even though associations are declared using target class names,
the target type of an associations may also be a dynamic entity instead
of a POCO.
</para>
<para>
At runtime we can work with <literal>Dictionaries</literal>:
</para>
<programlisting><![CDATA[using(ISession s = OpenSession())
using(ITransaction tx = s.BeginTransaction())
{
// Create a customer
var frank = new Dictionary<string, object>();
frank["name"] = "Frank";
// Create an organization
var foobar = new Dictionary<string, object>();
foobar["name"] = "Foobar Inc.";
// Link both
frank["organization"] = foobar;
// Save both
s.Save("Customer", frank);
s.Save("Organization", foobar);
tx.Commit();
}]]></programlisting>
<para>
Or we can work with <literal>dynamic</literal>:
</para>
<programlisting><![CDATA[using(var s = OpenSession())
using(var tx = s.BeginTransaction())
{
// Create a customer
dynamic frank = new ExpandoObject();
frank.Name = "Frank";
// Create an organization
dynamic foobar = new ExpandoObject();
foobar.Name = "Foobar Inc.";
// Link both
frank.Organization = foobar;
// Save both
s.Save("Customer", frank);
s.Save("Organization", foobar);
tx.Commit();
}]]></programlisting>
<para>
The advantages of a dynamic mapping are quick turnaround time for prototyping
without the need for entity class implementation. However, you lose compile-time
type checking and will very likely deal with many exceptions at runtime. Thanks
to the NHibernate mapping, the database schema can easily be normalized and sound,
allowing to add a proper domain model implementation on top later on.
</para>
<para>
A loaded dynamic entity can be manipulated as an <literal>IDictionary</literal>,
an <literal>IDictionary<string, object></literal> or a C#
<literal>dynamic</literal>.
</para>
<programlisting><![CDATA[using(ISession s = OpenSession())
using(ITransaction tx = s.BeginTransaction())
{
var customers = s
.CreateQuery("from Customer")
.List<IDictionary<string, object>>();
...
}]]></programlisting>
<programlisting><![CDATA[using System.Linq.Dynamic.Core;
...
using(ISession s = OpenSession())
using(ITransaction tx = s.BeginTransaction())
{
var customers = s
.Query<dynamic>("Customer")
.OrderBy("Name")
.ToList();
...
}]]></programlisting>
</sect1>
<sect1 id="persistent-classes-tuplizers" revision="1">
<title>Tuplizers</title>
<para>
<literal>NHibernate.Tuple.Tuplizer</literal>, and its sub-interfaces, are responsible
for managing a particular representation of a piece of data, given that representation's
<literal>NHibernate.EntityMode</literal>. If a given piece of data is thought of as
a data structure, then a tuplizer is the thing which knows how to create such a data structure
and how to extract values from and inject values into such a data structure. For example,
for the POCO entity mode, the corresponding tuplizer knows how create the POCO through its
constructor and how to access the POCO properties using the defined property accessors.
There are two high-level types of Tuplizers, represented by the
<literal>NHibernate.Tuple.Entity.IEntityTuplizer</literal> and <literal>NHibernate.Tuple.Component.IComponentTuplizer</literal>
interfaces. <literal>IEntityTuplizer</literal>s are responsible for managing the above mentioned
contracts in regards to entities, while <literal>IComponentTuplizer</literal>s do the same for
components.
</para>
<para>
Users may also plug in their own tuplizers. Perhaps you require that a <literal>IDictionary</literal> /
<literal>DynamicObject</literal> implementation other than NHibernate own implementation is used while
in the dynamic-map entity-mode; or perhaps you need to define a different proxy generation strategy
than the one used by default. Both would be achieved by defining a custom tuplizer
implementation. Tuplizers definitions are attached to the entity or component mapping they
are meant to manage. Going back to the example of our customer entity:
</para>
<programlisting><![CDATA[<hibernate-mapping>
<class entity-name="Customer">
<!--
Override the dynamic-map entity-mode
tuplizer for the customer entity
-->
<tuplizer entity-mode="dynamic-map"
class="CustomMapTuplizerImpl"/>
<id name="id" type="long" column="ID">
<generator class="sequence"/>
</id>
<!-- other properties -->
...
</class>
</hibernate-mapping>
public class CustomMapTuplizerImpl : NHibernate.Tuple.Entity.DynamicMapEntityTuplizer
{
// override the BuildInstantiator() method to plug in our custom map...
protected override IInstantiator BuildInstantiator(
NHibernate.Mapping.PersistentClass mappingInfo)
{
return new CustomMapInstantiator(mappingInfo);
}
private sealed class CustomMapInstantiator : NHibernate.Tuple.DynamicMapInstantiator
{
// override the generateMap() method to return our custom map...
protected override IDictionary GenerateMap()
{
return new CustomMap();
}
}
}]]></programlisting>
</sect1>
<sect1 id="persistent-classes-lifecycle">
<title>Lifecycle Callbacks</title>
<para>
Optionally, a persistent class might implement the interface
<literal>ILifecycle</literal> which provides some callbacks that allow
the persistent object to perform necessary initialization/cleanup after
save or load and before deletion or update.
</para>
<para>
The NHibernate <link linkend="objectstate-interceptors"><literal>IInterceptor</literal></link>
offers a less intrusive alternative, however.
</para>
<programlistingco>
<areaspec>
<area id="lifecycle1" coords="2 70"/>
<area id="lifecycle2" coords="3 70" />
<area id="lifecycle3" coords="4 70"/>
<area id="lifecycle4" coords="5 70" />
</areaspec>
<programlisting><![CDATA[public interface ILifecycle
{
LifecycleVeto OnSave(ISession s);
LifecycleVeto OnUpdate(ISession s);
LifecycleVeto OnDelete(ISession s);
void OnLoad(ISession s, object id);
}]]></programlisting>
<calloutlist>
<callout arearefs="lifecycle1">
<para>
<literal>OnSave</literal> - called just before the object is saved or
inserted
</para>
</callout>
<callout arearefs="lifecycle2">
<para>
<literal>OnUpdate</literal> - called just before an object is updated
(when the object is passed to <literal>ISession.Update()</literal>)
</para>
</callout>
<callout arearefs="lifecycle3">
<para>
<literal>OnDelete</literal> - called just before an object is deleted
</para>
</callout>
<callout arearefs="lifecycle4">
<para>
<literal>OnLoad</literal> - called just after an object is loaded
</para>
</callout>
</calloutlist>
</programlistingco>
<para>
<literal>OnSave()</literal>, <literal>OnDelete()</literal> and
<literal>OnUpdate()</literal> may be used to cascade saves and
deletions of dependent objects. This is an alternative to declaring cascaded
operations in the mapping file. <literal>OnLoad()</literal> may
be used to initialize transient properties of the object from its persistent
state. It may not be used to load dependent objects since the
<literal>ISession</literal> interface may not be invoked from
inside this method. A further intended usage of <literal>OnLoad()</literal>,
<literal>OnSave()</literal> and <literal>OnUpdate()</literal> is to store a
reference to the current <literal>ISession</literal> for later use.
</para>
<para>
Note that <literal>OnUpdate()</literal> is not called every time the object's
persistent state is updated. It is called only when a transient object is passed
to <literal>ISession.Update()</literal>.
</para>
<para>
If <literal>OnSave()</literal>, <literal>OnUpdate()</literal> or
<literal>OnDelete()</literal> return <literal>LifecycleVeto.Veto</literal>, the operation is
silently vetoed. If a <literal>CallbackException</literal> is thrown, the operation
is vetoed and the exception is passed back to the application.
</para>
<para>
Note that <literal>OnSave()</literal> is called after an identifier is assigned to
the object, except when native key generation is used.
</para>
</sect1>
<sect1 id="persistent-classes-validatable">
<title>IValidatable callback</title>
<para>
If the persistent class needs to check invariants before its state is
persisted, it may implement the following interface:
</para>
<programlisting><![CDATA[public interface IValidatable
{
void Validate();
}]]></programlisting>
<para>
The object should throw a <literal>ValidationFailure</literal> if an invariant
was violated. An instance of <literal>Validatable</literal> should not change
its state from inside <literal>Validate()</literal>.
</para>
<para>
Unlike the callback methods of the <literal>ILifecycle</literal> interface,
<literal>Validate()</literal> might be called at unpredictable times. The
application should not rely upon calls to <literal>Validate()</literal> for
business functionality.
</para>
</sect1>
</chapter>