-
Notifications
You must be signed in to change notification settings - Fork 934
/
Copy pathquickstart.xml
450 lines (381 loc) · 19.2 KB
/
quickstart.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
<chapter id="quickstart">
<title>Quick-start with IIS and Microsoft SQL Server</title>
<sect1 id="quickstart-intro">
<title>Getting started with NHibernate</title>
<para>
This tutorial explains a setup of NHibernate 5.0.0 within a Microsoft
environment. The tools used in this tutorial are:
</para>
<orderedlist>
<listitem>
Microsoft Internet Information Services (IIS) - web server supporting
ASP.NET.
</listitem>
<listitem>
Microsoft SQL Server 2012 - the database server. This tutorial uses
the desktop edition (SQL Express), a free download from Microsoft. Support
for other databases is only a matter of changing the NHibernate SQL
dialect and driver configuration.
</listitem>
<listitem>
Microsoft Visual Studio .NET (at least 2013) - the development environment.
</listitem>
</orderedlist>
<para>
First, we have to create a new Web project. We use the name <literal>QuickStart</literal>.
In the project, add a NuGet reference to <literal>NHibernate</literal>. Visual Studio
will automatically copy the library and its dependencies to the project output directory.
If you are using a database other than SQL Server, add a reference to its driver assembly
to your project.
</para>
<para>
We now set up the database connection information for NHibernate. To do this, open
the file <literal>Web.config</literal> automatically generated for your project and add
configuration elements according to the listing below:
</para>
<programlisting><![CDATA[<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<!-- Add this element -->
<configSections>
<section
name="hibernate-configuration"
type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
</configSections>
<!-- Add this element -->
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="dialect">NHibernate.Dialect.MsSql2012Dialect</property>
<property name="connection.connection_string">
Server=localhost\SQLEXPRESS;initial catalog=quickstart;Integrated Security=True
</property>
<mapping assembly="QuickStart" />
</session-factory>
</hibernate-configuration>
<!-- Leave the other sections unchanged -->
<system.web>
...
</system.web>
</configuration>]]></programlisting>
<para>
The <literal><configSections></literal> element contains definitions of
sections that follow and handlers to use to process their content. We declare
the handler for the configuration section here. The <literal>
<hibernate-configuration></literal> section contains the configuration
itself, telling NHibernate that we will use a Microsoft SQL Server 2012
database and connect to it through the specified connection string.
The dialect is a required setting, databases differ in their interpretation
of the SQL "standard". NHibernate will take care of the differences and comes
bundled with dialects for several major commercial and open source databases.
</para>
<para>
An <literal>ISessionFactory</literal> is NHibernate's concept of a single
datastore, multiple databases can be used by creating multiple XML
configuration files and creating multiple <literal>Configuration</literal>
and <literal>ISessionFactory</literal> objects in your application.
</para>
<para>
The last element of the <literal><hibernate-configuration></literal>
section declares <literal>QuickStart</literal> as the name of an assembly
containing class declarations and mapping files. The mapping files
contain the metadata for the mapping of the POCO class to a database table
(or multiple tables). We'll come back to mapping files soon. Let's write the
POCO class first and then declare the mapping metadata for it.
</para>
</sect1>
<sect1 id="quickstart-persistentclass">
<title>First persistent class</title>
<para>
NHibernate works best with the Plain Old CLR Objects (POCOs, sometimes
called Plain Ordinary CLR Objects) programming model for persistent classes.
A POCO has its data accessible through the standard .NET property mechanisms,
shielding the internal representation from the publicly visible interface:
</para>
<programlisting><![CDATA[namespace QuickStart
{
public class Cat
{
public virtual string Id { get; set; }
public virtual string Name { get; set; }
public virtual char Sex { get; set; }
public virtual float Weight { get; set; }
}
}]]></programlisting>
<para>
NHibernate is not restricted in its usage of property types, all .NET
types and primitives (like <literal>string</literal>, <literal>char</literal>
and <literal>DateTime</literal>) can be mapped, including classes from the
<literal>System.Collections.Generic</literal> namespace. You can map them as values,
collections of values, or associations to other entities. The <literal>Id</literal>
is a special property that represents the database identifier (primary key) of
that class, it is highly recommended for entities like a <literal>Cat</literal>.
NHibernate can use identifiers only internally, without having to declare them
on the class, but we would lose some of the flexibility in our application
architecture.
</para>
<para>
No special interface has to be implemented for persistent classes nor do we have
to subclass from a special root persistent class. NHibernate also doesn't use any
build time processing, such as IL manipulation, it relies solely on
.NET reflection and runtime class enhancement.
So, without any dependency in the POCO class on NHibernate, we can map it to
a database table.
</para>
<para>
For the above mentioned runtime class enhancement to work, NHibernate requires that all
public properties of an entity class are declared as <literal>virtual</literal>. It also
requires a parameter-less constructor: if you add a constructor having parameters,
make sure to add a parameter-less constructor too.
</para>
</sect1>
<sect1 id="quickstart-mapping">
<title>Mapping the cat</title>
<para>
This tutorial directly uses xml mapping files. This is just one among many mapping solutions
NHibernate provides, see <xref linkend="mapping-declaration" />.
</para>
<para>
The <literal>Cat.hbm.xml</literal> mapping file contains the metadata
required for the object/relational mapping. The metadata includes declaration
of persistent classes and the mapping of properties (to columns and
foreign key relationships to other entities) to database tables.
</para>
<para>
Please note that the <literal>Cat.hbm.xml</literal> file should be set to an embedded resource.
</para>
<programlisting><![CDATA[<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
namespace="QuickStart" assembly="QuickStart">
<class name="Cat" table="Cat">
<!-- A 32 hex character is our surrogate key. It's automatically
generated by NHibernate with the UUID pattern. -->
<id name="Id">
<column name="CatId" sql-type="char(32)" not-null="true"/>
<generator class="uuid.hex" />
</id>
<!-- A cat has to have a name, but it shouldn't be too long. -->
<property name="Name">
<column name="Name" length="16" not-null="true" />
</property>
<property name="Sex" />
<property name="Weight" />
</class>
</hibernate-mapping>]]></programlisting>
<para>
Every persistent class should have an identifier attribute (actually, only
classes representing entities, not dependent value objects, which
are mapped as components of an entity). This property is used to distinguish
persistent objects: Two cats are equal if
<literal>catA.Id.Equals(catB.Id)</literal> is true, this concept is
called <emphasis>database identity</emphasis>. NHibernate comes bundled with
various identifier generators for different scenarios (including native generators
for database sequences, hi/lo identifier tables, and application assigned
identifiers). We use the UUID generator (only recommended for testing, as integer
surrogate keys generated by the database should be preferred) and also specify the
column <literal>CatId</literal> of the table <literal>Cat</literal> for the
NHibernate generated identifier value (as a primary key of the table).
</para>
<para>
All other properties of <literal>Cat</literal> are mapped to the same table. In
the case of the <literal>Name</literal> property, we mapped it with an explicit
database column declaration. This is especially useful when the database
schema is automatically generated (as SQL DDL statements) from the mapping
declaration with NHibernate's <emphasis>SchemaExport</emphasis> tool. All other
properties are mapped using NHibernate's default settings, which is what you
need most of the time. Here the specification of the table name with the attribute
<literal>table</literal> is redundant, it default to the class name when not specified.
The table <literal>Cat</literal> in the database looks like this:
</para>
<programlisting><![CDATA[ Column | Type | Modifiers
--------+--------------+----------------------
CatId | char(32) | not null, primary key
Name | nvarchar(16) | not null
Sex | nchar(1) |
Weight | real |]]></programlisting>
<para>
You should now create the database and this table manually, and later read
<xref linkend="toolsetguide"/> if you want to automate this step with the
SchemaExport tool. This tool can create a full SQL DDL, including table
definition, custom column type constraints, unique constraints and indexes.
If you are using SQL Server, you should also make sure the <literal>ASPNET</literal>
user has permissions to use the database.
</para>
</sect1>
<sect1 id="quickstart-playingwithcats" revision="1">
<title>Playing with cats</title>
<para>
We're now ready to start NHibernate's <literal>ISession</literal>. It is the
<emphasis>persistence manager</emphasis> interface, we use it
to store and retrieve <literal>Cat</literal>s to and from the database.
But first, we've to get an <literal>ISession</literal> (NHibernate's unit-of-work)
from the <literal>ISessionFactory</literal>:
</para>
<programlisting><![CDATA[ISessionFactory sessionFactory =
new Configuration().Configure().BuildSessionFactory();]]></programlisting>
<para>
An <literal>ISessionFactory</literal> is responsible for one database and
may only use one XML configuration file (<literal>Web.config</literal> or
<literal>hibernate.cfg.xml</literal>).
You can set other properties (and even change the mapping metadata) by
accessing the <literal>Configuration</literal> <emphasis>before</emphasis>
you build the <literal>ISessionFactory</literal> (it is immutable). Where
do we create the <literal>ISessionFactory</literal> and how can we access
it in our application?
</para>
<para>
An <literal>ISessionFactory</literal> is usually only built once,
e.g. at start-up inside <literal>Application_Start</literal> event handler.
This also means you should not keep it in an instance variable in your
ASP.NET pages or MVC controllers, but in some other location. Furthermore,
we need some kind of <emphasis>Singleton</emphasis>, so we can access the
<literal>ISessionFactory</literal> easily in application code. The approach
shown next solves both problems: configuration and easy access to a
<literal>ISessionFactory</literal>.
</para>
<para>
We implement a <literal>NHibernateHelper</literal> helper class:
</para>
<programlisting><![CDATA[using System;
using System.Web;
using NHibernate;
using NHibernate.Cfg;
namespace QuickStart
{
public sealed class NHibernateHelper
{
private const string CurrentSessionKey = "nhibernate.current_session";
private static readonly ISessionFactory _sessionFactory;
static NHibernateHelper()
{
_sessionFactory = new Configuration().Configure().BuildSessionFactory();
}
public static ISession GetCurrentSession()
{
var context = HttpContext.Current;
var currentSession = context.Items[CurrentSessionKey] as ISession;
if (currentSession == null)
{
currentSession = _sessionFactory.OpenSession();
context.Items[CurrentSessionKey] = currentSession;
}
return currentSession;
}
public static void CloseSession()
{
var context = HttpContext.Current;
var currentSession = context.Items[CurrentSessionKey] as ISession;
if (currentSession == null)
{
// No current session
return;
}
currentSession.Close();
context.Items.Remove(CurrentSessionKey);
}
public static void CloseSessionFactory()
{
if (_sessionFactory != null)
{
_sessionFactory.Close();
}
}
}
}]]></programlisting>
<para>
This class does not only take care of the <literal>ISessionFactory</literal>
with its static attribute, but also has code to remember the <literal>ISession</literal>
for the current HTTP request.
</para>
<para>
An <literal>ISessionFactory</literal> is threadsafe, many threads can access
it concurrently and request <literal>ISession</literal>s. An <literal>ISession</literal>
is a non-threadsafe object that represents a single unit-of-work with the database.
<literal>ISession</literal>s are opened by an <literal>ISessionFactory</literal> and
are closed when all work is completed:
</para>
<programlisting><![CDATA[ISession session = NHibernateHelper.GetCurrentSession();
try
{
using (ITransaction tx = session.BeginTransaction())
{
var princess = new Cat
{
Name = "Princess",
Sex = 'F',
Weight = 7.4f
};
session.Save(princess);
tx.Commit();
}
}
finally
{
NHibernateHelper.CloseSession();
}]]></programlisting>
<para>
In an <literal>ISession</literal>, every database operation occurs inside a
transaction that isolates the database operations (even read-only operations).
We use NHibernate's <literal>ITransaction</literal> API to abstract from the underlying
transaction strategy (in our case, ADO.NET transactions). Please note that the example
above does not handle any exceptions.
</para>
<para>
Also note that you may call <literal>NHibernateHelper.GetCurrentSession();</literal>
as many times as you like, you will always get the current <literal>ISession</literal>
of this HTTP request. You have to make sure the <literal>ISession</literal> is closed
after your unit-of-work completes, either in <literal>Application_EndRequest</literal>
event handler in your application class, or with a MVC action filter, or in a
<literal>HttpModule</literal> before the HTTP response is sent. The nice side effect
of the latter is easy lazy initialization: the <literal>ISession</literal> is still
open when the view is rendered, so NHibernate can load uninitialized objects while you
navigate the graph.
</para>
<para>
NHibernate has various methods that can be used to retrieve objects from the
database. Nowadays the most standard way is using Linq:
</para>
<programlisting><![CDATA[using(var tx = session.BeginTransaction())
{
var females = session
.Query<Cat>()
.Where(c => c.Sex == 'F')
.ToList();
foreach (var cat in females)
{
Console.Out.WriteLine("Female Cat: " + cat.Name);
}
tx.Commit();
}]]></programlisting>
<para>
If you use an older NHibernate, you may have to import the
<literal>NHibernate.Linq</literal> namespace.
</para>
<para>
NHibernate also offers an object-oriented <emphasis>query by criteria</emphasis> API
that can be used to formulate type-safe queries, the Hibernate Query Language (HQL),
which is an easy to learn and powerful object-oriented extension to SQL, as well as a
strongly-typed LINQ API which translates internally to HQL.
NHibernate of course uses <literal>DbCommand</literal>s and parameter binding for all
SQL communication with the database. You may also use NHibernate's direct SQL query
feature or get a plain ADO.NET connection from an <literal>ISession</literal> in rare
cases.
</para>
<para>
Since NHibernate 5.0, the session and its queries IO bound methods have async counterparts.
Each call to an async method must be awaited before further interacting with the session or
its queries.
</para>
</sect1>
<sect1 id="quickstart-summary">
<title>Finally</title>
<para>
We only scratched the surface of NHibernate in this small tutorial. Please note that
we don't include any ASP.NET specific code in our examples. You have to create an
ASP.NET page yourself and insert the NHibernate code as you see fit.
</para>
<para>
Keep in mind that NHibernate, as a data access layer, is tightly integrated into
your application. Usually, all other layers depend on the persistence mechanism.
Make sure you understand the implications of this design.
</para>
</sect1>
</chapter>