forked from nhibernate/nhibernate-core
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbest_practices.xml
203 lines (200 loc) · 11.3 KB
/
best_practices.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
<chapter id="best-practices" revision="3">
<title>Best Practices</title>
<variablelist spacing="compact">
<varlistentry>
<term>Write fine-grained classes and map them using <literal><component></literal>.</term>
<listitem>
<para>
Use an <literal>Address</literal> class to encapsulate <literal>street</literal>,
<literal>suburb</literal>, <literal>state</literal>, <literal>postcode</literal>.
This encourages code reuse and simplifies refactoring.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Declare identifier properties on persistent classes.</term>
<listitem>
<para>
NHibernate makes identifier properties optional. There are all sorts of reasons why
you should use them. We recommend that identifiers be 'synthetic' (generated, with
no business meaning) and of a primitive type. For maximum flexibility, use
<literal>Int64</literal> or <literal>String</literal>.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Place each class mapping in its own file.</term>
<listitem>
<para>
Don't use a single monolithic mapping document. Map <literal>Eg.Foo</literal> in
the file <literal>Eg/Foo.hbm.xml</literal>. This makes particularly good sense in
a team environment.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Embed mappings in assemblies.</term>
<listitem>
<para>
Place mapping files along with the classes they map and declare them as
<literal>Embedded Resource</literal>s in Visual Studio.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Consider externalising query strings.</term>
<listitem>
<para>
This is a good practice if your queries call non-ANSI-standard SQL functions.
Externalising the query strings to mapping files will make the application more portable.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Use parameters.</term>
<listitem>
<para>
As in ADO.NET, always replace non-constant values by "?". Never use string manipulation to
bind a non-constant value in a query! Even better, consider using named parameters in
queries.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Don't manage your own ADO.NET connections.</term>
<listitem>
<para>
NHibernate lets the application manage ADO.NET connections. This approach should be considered
a last-resort. If you can't use the built-in connections providers, consider providing your
own implementation of <literal>NHibernate.Connection.IConnectionProvider</literal>.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Consider using a custom type.</term>
<listitem>
<para>
Suppose you have a type, say from some library, that needs to be persisted but doesn't
provide the accessors needed to map it as a component. You should consider implementing
<literal>NHibernate.UserTypes.IUserType</literal>. This approach frees the application
code from implementing transformations to / from an NHibernate type.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Use hand-coded ADO.NET in bottlenecks.</term>
<listitem>
<para>
In performance-critical areas of the system, some kinds of operations (eg. mass update /
delete) might benefit from direct ADO.NET. But please, wait until you <emphasis>know</emphasis>
something is a bottleneck. And don't assume that direct ADO.NET is necessarily faster. If need to
use direct ADO.NET, it might be worth opening a NHibernate <literal>ISession</literal> and using
that SQL connection. That way you can still use the same transaction strategy and underlying
connection provider.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Understand <literal>ISession</literal> flushing.</term>
<listitem>
<para>
From time to time the ISession synchronizes its persistent state with the database. Performance will
be affected if this process occurs too often. You may sometimes minimize unnecessary flushing by
disabling automatic flushing or even by changing the order of queries and other operations within a
particular transaction.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>In a three tiered architecture, consider using <literal>SaveOrUpdate()</literal>.</term>
<listitem>
<para>
When using a distributed architecture, you could pass persistent objects loaded in
the middle tier to and from the user interface tier. Use a new session to service each request.
Use <literal>ISession.Update()</literal> or <literal>ISession.SaveOrUpdate()</literal> to update the
persistent state of an object.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>In a two tiered architecture, consider using session disconnection.</term>
<listitem>
<para>
Database Transactions have to be as short as possible for best scalability. However, it is often
necessary to implement long running Application Transactions, a single unit-of-work from the
point of view of a user. This Application Transaction might span several client requests and
response cycles. Either use Detached Objects or, in two tiered architectures, simply disconnect
the NHibernate Session from the ADO.NET connection and reconnect it for each subsequent request.
Never use a single Session for more than one Application Transaction use-case, otherwise, you
will run into stale data.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Don't treat exceptions as recoverable.</term>
<listitem>
<para>
This is more of a necessary practice than a "best" practice. When an exception occurs, roll back
the <literal>ITransaction</literal> and close the <literal>ISession</literal>. If you don't,
NHibernate can't guarantee that in-memory state accurately represents persistent state.
As a special case of this, do not use <literal>ISession.Load()</literal> to determine if an
instance with the given identifier exists on the database; use <literal>Get()</literal>
or a query instead.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Prefer lazy fetching for associations.</term>
<listitem>
<para id="best-practices-p14" revision="1">
Use eager (outer-join) fetching sparingly. Use proxies and/or lazy collections for most associations
to classes that are not cached in the second-level cache. For associations to cached classes, where
there is a high probability of a cache hit, explicitly disable eager fetching using
<literal>fetch="select"</literal>. When an outer-join fetch is appropriate to a particular use
case, use a query with a <literal>left join fetch</literal>.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Consider abstracting your business logic from NHibernate.</term>
<listitem>
<para>
Hide (NHibernate) data-access code behind an interface. Combine the <emphasis>DAO</emphasis> and
<emphasis>Thread Local Session</emphasis> patterns. You can even have some classes persisted by
hand-coded ADO.NET, associated to NHibernate via an <literal>IUserType</literal>. (This advice is
intended for "sufficiently large" applications; it is not appropriate for an application with
five tables!)
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Implement <literal>Equals()</literal> and <literal>GetHashCode()</literal> using a unique business key.</term>
<listitem>
<para>
If you compare objects outside of the ISession scope, you have to implement <literal>Equals()</literal>
and <literal>GetHashCode()</literal>. Inside the ISession scope, object identity is guaranteed. If
you implement these methods, never ever use the database identifier! A transient object doesn't have
an identifier value and NHibernate would assign a value when the object is saved. If the object
is in an ISet while being saved, the hash code changes, breaking the contract. To implement
<literal>Equals()</literal> and <literal>GetHashCode()</literal>, use a unique business key, that is,
compare a unique combination of class properties. Remember that this key has to be stable and unique
only while the object is in an ISet, not for the whole lifetime (not as stable as a database primary
key). Never use collections in the <literal>Equals()</literal> comparison (lazy loading) and be careful
with other associated classes that might be proxied.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term>Don't use exotic association mappings.</term>
<listitem>
<para>
Good use-cases for a real many-to-many associations are rare. Most of the time you need
additional information stored in the "link table". In this case, it is much better to
use two one-to-many associations to an intermediate link class. In fact, we think that
most associations are one-to-many and many-to-one, you should be careful when using any
other association style and ask yourself if it is really necessary.
</para>
</listitem>
</varlistentry>
</variablelist>
</chapter>