-
Notifications
You must be signed in to change notification settings - Fork 933
/
Copy pathquery_linq.xml
825 lines (777 loc) · 30.6 KB
/
query_linq.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
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
<chapter id="querylinq">
<title>Linq Queries</title>
<para>
NHibernate 3.0 introduces the Linq to NHibernate provider, which allows the use of the Linq API
for querying with NHibernate.
</para>
<para>
<literal>IQueryable</literal> queries are obtained with the <literal>Query</literal> methods used on the
<literal>ISession</literal> or <literal>IStatelessSession</literal>. (Prior to NHibernate 5.0, these
methods were extensions defined in the <literal>NHibernate.Linq</literal> namespace.) A number of
NHibernate Linq extensions giving access to NHibernate specific features are defined in the
<literal>NHibernate.Linq</literal> namespace. Of course, the Linq namespace is still needed too.
</para>
<programlisting><![CDATA[using System.Linq;
using NHibernate.Linq;]]></programlisting>
<para>
Note: NHibernate has another querying API which uses lambda, <link linkend="queryqueryover">QueryOver</link>.
It should not be confused with a Linq provider.
</para>
<sect1 id="querylinq-querystructure">
<title>Structure of a Query</title>
<para>
Queries are created from an ISession using the syntax:
</para>
<programlisting><![CDATA[IList<Cat> cats =
session.Query<Cat>()
.Where(c => c.Color == "white")
.ToList();]]></programlisting>
<para>
The <literal>Query<TEntity></literal> function yields an <literal>IQueryable<TEntity></literal>,
with which Linq extension methods or Linq syntax can be used. When executed, the <literal>IQueryable<TEntity></literal>
will be translated to a SQL query on the database.
</para>
<para> </para>
<para>
It is possible to query a specific sub-class while still using a queryable of the base class.
</para>
<programlisting><![CDATA[IList<Cat> cats =
session.Query<Cat>("Eg.DomesticCat, Eg")
.Where(c => c.Name == "Max")
.ToList();]]></programlisting>
<para>
Starting with NHibernate 5.0, queries can also be created from an entity collection, with the standard
Linq extension <literal>AsQueryable</literal> available from <literal>System.Linq</literal> namespace.
</para>
<programlisting><![CDATA[IList<Cat> whiteKittens =
cat.Kittens.AsQueryable()
.Where(k => k.Color == "white")
.ToList();]]></programlisting>
<para>
This will be executed as a query on that <literal>cat</literal>'s kittens without loading the
entire collection.
</para>
<para>
If the collection is a map, call <literal>AsQueryable</literal> on its <literal>Values</literal>
property.
</para>
<programlisting><![CDATA[IList<Cat> whiteKittens =
cat.Kittens.Values.AsQueryable()
.Where(k => k.Color == "white")
.ToList();]]></programlisting>
<para> </para>
<para>
A client timeout for the query can be defined. As most others NHibernate specific features for
Linq, this is available through an extension defined in <literal>NHibernate.Linq</literal>
namespace.
</para>
<programlisting><![CDATA[IList<Cat> cats =
session.Query<Cat>()
.Where(c => c.Color == "black")
// Allows 10 seconds only.
.SetOptions(o => o.SetTimeout(10))
.ToList();]]></programlisting>
</sect1>
<sect1 id="querylinq-parametertypes">
<title>Parameter types</title>
<para>
Query parameters get extracted from the Linq expression. Their types are selected according to
<link linkend="mapping-types">NHibernate types</link> default for .Net types.
</para>
<para>
The <literal>MappedAs</literal> extension method allows to override the default type.
</para>
<programlisting><![CDATA[IList<Cat> cats =
session.Query<Cat>()
.Where(c => c.BirthDate == DateTime.Today.MappedAs(NHibernateUtil.Date))
.ToList();]]></programlisting>
<programlisting><![CDATA[IList<Cat> cats =
session.Query<Cat>()
.Where(c => c.Name == "Max".MappedAs(TypeFactory.Basic("AnsiString(200)")))
.ToList();]]></programlisting>
</sect1>
<sect1 id="querylinq-supportedmethods">
<title>Supported methods and members</title>
<para>
Many methods and members of common .Net types are supported by the Linq to NHibernate provider.
They will be translated to the appropriate SQL, provided they are called on an entity property
(or expression deriving from) or at least one of their arguments references an entity property.
(Otherwise, their return values will be evaluated with .Net runtime before query execution.)
</para>
<sect2 id="querylinq-supportedmethods-common">
<title>Common methods</title>
<para>
The .Net 4 <literal>CompareTo</literal> method of strings and numerical types is translated to
a <literal>case</literal> statement yielding <literal>-1|0|1</literal> according to the result
of the comparison.
</para>
<para> </para>
<para>
Many type conversions are available. For all of them, .Net overloads with more than one argument
are not supported.
</para>
<para>
Numerical types can be converted to other numerical types or parsed from strings, using
following methods:
</para>
<itemizedlist>
<listitem>
<para>
<literal>Convert.ToDecimal</literal>
</para>
</listitem>
<listitem>
<para>
<literal>Convert.ToDouble</literal>
</para>
</listitem>
<listitem>
<para>
<literal>Convert.ToInt32</literal>
</para>
</listitem>
<listitem>
<para>
<literal>Decimal.Parse</literal>
</para>
</listitem>
<listitem>
<para>
<literal>Double.Parse</literal>
</para>
</listitem>
<listitem>
<para>
<literal>Int32.Parse</literal>
</para>
</listitem>
</itemizedlist>
<para>
Strings can be converted to <literal>Boolean</literal> and <literal>DateTime</literal> with
<literal>Convert.ToBoolean</literal> or <literal>Boolean.Parse</literal> and
<literal>Convert.ToDateTime</literal> or <literal>DateTime.Parse</literal> respectively.
</para>
<para>
On all types supporting string conversion, <literal>ToString</literal> method can be called.
</para>
<programlisting><![CDATA[IList<string> catBirthDates =
session.Query<Cat>()
.Select(c => c.BirthDate.ToString())
.ToList();]]></programlisting>
<para> </para>
<para>
<literal>Equals</literal> methods taking a single argument with the same type can be used. Of
course, <literal>==</literal> is supported too.
</para>
</sect2>
<sect2 id="querylinq-supportedmethods-datetime">
<title><literal>DateTime</literal> and <literal>DateTimeOffset</literal></title>
<para>
Date and time parts properties can be called on <literal>DateTime</literal> and <literal>DateTimeOffset</literal>.
Those properties are:
</para>
<itemizedlist>
<listitem>
<para>
<literal>Date</literal>
</para>
</listitem>
<listitem>
<para>
<literal>Day</literal>
</para>
</listitem>
<listitem>
<para>
<literal>Hour</literal>
</para>
</listitem>
<listitem>
<para>
<literal>Minute</literal>
</para>
</listitem>
<listitem>
<para>
<literal>Month</literal>
</para>
</listitem>
<listitem>
<para>
<literal>Second</literal>
</para>
</listitem>
<listitem>
<para>
<literal>Year</literal>
</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="querylinq-supportedmethods-icollection">
<title><literal>ICollection</literal>, non generic and generic</title>
<para>
Collections <literal>Contains</literal> methods are supported.
</para>
<programlisting><![CDATA[IList<Cat> catsWithWrongKitten =
session.Query<Cat>()
.Where(c => c.Kittens.Contains(c))
.ToList();]]></programlisting>
</sect2>
<sect2 id="querylinq-supportedmethods-idictionary">
<title><literal>IDictionary</literal>, non generic and generic</title>
<para>
Dictionaries <literal>Item</literal> getter are supported. This enables referencing a dictionary
item value in a <literal>where</literal> condition, as it can be done with
<link linkend="queryhql-expressions">HQL expressions</link>.
</para>
<para>
Non generic dictionary method <literal>Contains</literal> and generic dictionary method
<literal>ContainsKey</literal> are translated to corresponding <literal>indices</literal>
<link linkend="queryhql-expressions">HQL expressions</link>. Supposing <literal>Acts</literal>
in following HQL example is generic,
</para>
<programlisting><![CDATA[from Eg.Show show where 'fizard' in indices(show.Acts)]]></programlisting>
<para>
it could be written with Linq:
</para>
<programlisting><![CDATA[IList<Show> shows =
session.Query<Show>()
.Where(s => s.Acts.ContainsKey("fizard"))
.ToList();]]></programlisting>
</sect2>
<sect2 id="querylinq-supportedmethods-math">
<title>Mathematical functions</title>
<para>
The following list of mathematical functions from <literal>System.Math</literal> is handled:
</para>
<itemizedlist>
<listitem>
<para>
Trigonometric functions: <literal>Acos</literal>, <literal>Asin</literal>, <literal>Atan</literal>,
<literal>Atan2</literal>, <literal>Cos</literal>, <literal>Cosh</literal>, <literal>Sin</literal>,
<literal>Sinh</literal>, <literal>Tan</literal>, <literal>Tanh</literal>
</para>
</listitem>
<listitem>
<para>
<literal>Abs</literal> (all overloads)
</para>
</listitem>
<listitem>
<para>
<literal>Ceiling</literal> (both overloads)
</para>
</listitem>
<listitem>
<para>
<literal>Floor</literal> (both overloads)
</para>
</listitem>
<listitem>
<para>
<literal>Pow</literal>
</para>
</listitem>
<listitem>
<para>
<literal>Round</literal> (only overloads without a mode argument)
</para>
</listitem>
<listitem>
<para>
<literal>Sign</literal> (all overloads)
</para>
</listitem>
<listitem>
<para>
<literal>Sqrt</literal>
</para>
</listitem>
<listitem>
<para>
<literal>Truncate</literal> (both overloads)
</para>
</listitem>
</itemizedlist>
</sect2>
<sect2 id="querylinq-supportedmethods-nullables">
<title>Nullables</title>
<para>
On <literal>Nullable<></literal> types, <literal>GetValueOrDefault</literal> methods, with or
without a provided default value, are supported.
</para>
</sect2>
<sect2 id="querylinq-supportedmethods-string">
<title>Strings</title>
<para>
The following properties and methods are supported on strings:
</para>
<itemizedlist>
<listitem>
<para>
<literal>Contains</literal>
</para>
</listitem>
<listitem>
<para>
<literal>EndsWith</literal> (without additional parameters)
</para>
</listitem>
<listitem>
<para>
<literal>IndexOf</literal> (only overloads taking a character or a string, and optionally a start index)
</para>
</listitem>
<listitem>
<para>
<literal>Length</literal>
</para>
</listitem>
<listitem>
<para>
<literal>Replace</literal> (both overloads)
</para>
</listitem>
<listitem>
<para>
<literal>StartsWith</literal> (without additional parameters)
</para>
</listitem>
<listitem>
<para>
<literal>Substring</literal> (both overloads)
</para>
</listitem>
<listitem>
<para>
<literal>ToLower</literal> (without additional parameters) and <literal>ToLowerInvariant</literal>,
both translated to the same database lower function.
</para>
</listitem>
<listitem>
<para>
<literal>ToUpper</literal> (without additional parameters) and <literal>ToUpperInvariant</literal>,
both translated to the same database upper function.
</para>
</listitem>
<listitem>
<para>
<literal>Trim</literal> (both overloads)
</para>
</listitem>
<listitem>
<para>
<literal>TrimEnd</literal>
</para>
</listitem>
<listitem>
<para>
<literal>TrimStart</literal>
</para>
</listitem>
</itemizedlist>
<para> </para>
<para>
Furthermore, a string <literal>Like</literal> extension methods allows expressing SQL
<literal>like</literal> conditions.
</para>
<programlisting><![CDATA[IList<DomesticCat> cats =
session.Query<DomesticCat>()
.Where(c => c.Name.Like("L%l%l"))
.ToList();]]></programlisting>
<para>
This <literal>Like</literal> extension method is a Linq to NHibernate method only. Trying to call it
in another context is not supported.
</para>
<para>
If you want to avoid depending on the <literal>NHibernate.Linq</literal> namespace,
you can define your own replica of the <literal>Like</literal> methods. Any 2 or 3 arguments method
named <literal>Like</literal> in a class named <literal>SqlMethods</literal> will be translated.
</para>
</sect2>
</sect1>
<sect1 id="querylinq-futureresults">
<title>Future results</title>
<para>
Future results are supported by the Linq provider. They are not evaluated till one gets executed.
At that point, all defined future results are evaluated in one single round-trip to the database.
</para>
<programlisting><![CDATA[// Define queries
IFutureEnumerable<Cat> cats =
session.Query<Cat>()
.Where(c => c.Color == "black")
.ToFuture();
IFutureValue<int> catCount =
session.Query<Cat>()
.ToFutureValue(q => q.Count());
// Execute them
foreach(Cat cat in cats.GetEnumerable())
{
// Do something
}
if (catCount.Value > 10)
{
// Do something
}
]]></programlisting>
<para>
See <xref linkend="performance-future" /> for more information.
</para>
</sect1>
<sect1 id="querylinq-fetching">
<title>Fetching associations</title>
<para>
A Linq query may load associated entities or collection of entities. Once the query is defined, using
<literal>Fetch</literal> allows fetching a related entity, and <literal>FetchMany</literal> allows
fetching a collection. These methods are defined as extensions in <literal>NHibernate.Linq</literal>
namespace.
</para>
<programlisting><![CDATA[IList<Cat> oldCats =
session.Query<Cat>()
.Where(c => c.BirthDate.Year < 2010)
.Fetch(c => c.Mate)
.FetchMany(c => c.Kittens)
.ToList();]]></programlisting>
<para>
Issuing many <literal>FetchMany</literal> on the same query may cause a cartesian product over
the fetched collections. This can be avoided by splitting the fetches among
<link linkend="querylinq-futureresults">future queries</link>.
</para>
<programlisting><![CDATA[IQueryable<Cat> oldCatsQuery =
session.Query<Cat>()
.Where(c => c.BirthDate.Year < 2010);
oldCatsQuery
.Fetch(c => c.Mate)
.FetchMany(c => c.Kittens)
.ToFuture();
IList<Cat> oldCats =
oldCatsQuery
.FetchMany(c => c.AnotherCollection)
.ToFuture()
.GetEnumerable()
.ToList();]]></programlisting>
<para> </para>
<para>
Use <literal>ThenFetch</literal> and <literal>ThenFetchMany</literal> for fetching associations
of the previously fetched association.
</para>
<programlisting><![CDATA[IList<Cat> oldCats =
session.Query<Cat>()
.Where(c => c.BirthDate.Year < 2010)
.Fetch(c => c.Mate)
.FetchMany(c => c.Kittens)
.ThenFetch(k => k.Mate)
.ToList();]]></programlisting>
</sect1>
<sect1 id="querylinq-modifying">
<title>Modifying entities inside the database</title>
<para>
Beginning with NHibernate 5.0, Linq queries can be used for inserting, updating or deleting entities.
The query defines the data to delete, update or insert, and then <literal>Delete</literal>,
<literal>Update</literal>, <literal>UpdateBuilder</literal>, <literal>InsertInto</literal> and
<literal>InsertBuilder</literal> queryable extension methods allow to delete it,
or instruct in which way it should be updated or inserted. Those queries happen entirely inside the
database, without extracting corresponding entities out of the database.
</para>
<para>
These operations are a Linq implementation of <xref linkend="batch-direct" />, with the same abilities
and limitations.
</para>
<sect2 id="querylinq-modifying-insert">
<title>Inserting new entities</title>
<para>
<literal>InsertInto</literal> and <literal>InsertBuilder</literal> method extensions expect a NHibernate
queryable defining the data source of the insert. This data can be entities or a projection. Then they
allow specifying the target entity type to insert, and how to convert source data to those target
entities. Three forms of target specification exist.
</para>
<para>
Using projection to target entity:
</para>
<programlisting><![CDATA[session.Query<Cat>()
.Where(c => c.BodyWeight > 20)
.InsertInto(c => new Dog { Name = c.Name + "dog", BodyWeight = c.BodyWeight });]]></programlisting>
<para>
Projections can be done with an anonymous object too, but it requires supplying explicitly the target
type, which in turn requires re-specifying the source type:
</para>
<programlisting><![CDATA[session.Query<Cat>()
.Where(c => c.BodyWeight > 20)
.InsertInto<Cat, Dog>(c => new { Name = c.Name + "dog", BodyWeight = c.BodyWeight });]]></programlisting>
<para>
Or using assignments:
</para>
<programlisting><![CDATA[session.Query<Cat>()
.Where(c => c.BodyWeight > 20)
.InsertBuilder()
.Into<Dog>()
.Value(d => d.Name, c => c.Name + "dog")
.Value(d => d.BodyWeight, c => c.BodyWeight)
.Insert();]]></programlisting>
<para>
In all cases, unspecified properties are not included in the resulting SQL insert.
<link linkend="mapping-declaration-version"><literal>version</literal></link> and
<link linkend="mapping-declaration-timestamp"><literal>timestamp</literal></link> properties are
exceptions. If not specified, they are inserted with their <literal>seed</literal> value.
</para>
<para>
For more information on <literal>Insert</literal> limitations, please refer to
<xref linkend="batch-direct" />.
</para>
</sect2>
<sect2 id="querylinq-modifying-update">
<title>Updating entities</title>
<para>
<literal>Update</literal> and <literal>UpdateBuilder</literal> method extensions expect a NHibernate
queryable defining the entities to update. Then they allow specifying which properties should be
updated with which values. As for insertion, three forms of target specification exist.
</para>
<para>
Using projection to updated entity:
</para>
<programlisting><![CDATA[session.Query<Cat>()
.Where(c => c.BodyWeight > 20)
.Update(c => new Cat { BodyWeight = c.BodyWeight / 2 });]]></programlisting>
<para>
Projections can be done with an anonymous object too:
</para>
<programlisting><![CDATA[session.Query<Cat>()
.Where(c => c.BodyWeight > 20)
.Update(c => new { BodyWeight = c.BodyWeight / 2 });]]></programlisting>
<para>
Or using assignments:
</para>
<programlisting><![CDATA[session.Query<Cat>()
.Where(c => c.BodyWeight > 20)
.UpdateBuilder()
.Set(c => c.BodyWeight, c => c.BodyWeight / 2)
.Update();]]></programlisting>
<para>
In all cases, unspecified properties are not included in the resulting SQL update. This could
be changed for <link linkend="mapping-declaration-version"><literal>version</literal></link> and
<link linkend="mapping-declaration-timestamp"><literal>timestamp</literal></link> properties:
using <literal>UpdateVersioned</literal> instead of <literal>Update</literal> allows incrementing
the version. Custom version types (<literal>NHibernate.Usertype.IUserVersionType</literal>) are
not supported.
</para>
<para>
When using projection to updated entity, please note that the constructed entity must have the
exact same type than the underlying queryable source type. Attempting to project to any other class
(anonymous projections excepted) will fail.
</para>
</sect2>
<sect2 id="querylinq-modifying-delete">
<title>Deleting entities</title>
<para>
<literal>Delete</literal> method extension expects a queryable defining the entities to delete.
It immediately deletes them.
</para>
<programlisting><![CDATA[session.Query<Cat>()
.Where(c => c.BodyWeight > 20)
.Delete();]]></programlisting>
</sect2>
</sect1>
<sect1 id="querylinq-querycache">
<title>Query cache</title>
<para>
The Linq provider can use the query cache if it is setup. Refer to
<xref linkend="performance-querycache" /> for more details on how to set it up.
</para>
<para> </para>
<para>
<literal>SetOptions</literal> extension method allows to enable the cache for the query.
</para>
<programlisting><![CDATA[IList<Cat> oldCats =
session.Query<Cat>()
.Where(c => c.BirthDate.Year < 2010)
.SetOptions(o => o.SetCacheable(true))
.ToList();]]></programlisting>
<para> </para>
<para>
The cache mode and cache region can be specified too.
</para>
<programlisting><![CDATA[IList<Cat> cats =
session.Query<Cat>()
.Where(c => c.Name == "Max")
.SetOptions(o => o
.SetCacheable(true)
.SetCacheRegion("catNames")
.SetCacheMode(CacheMode.Put))
.ToList();]]></programlisting>
</sect1>
<sect1 id="querylinq-extending">
<title>Extending the Linq to NHibernate provider</title>
<para>
The Linq to NHibernate provider can be extended for supporting additional SQL functions or
translating additional methods or properties to a SQL query.
</para>
<sect2 id="querylinq-extending-sqlfunctions">
<title>Adding SQL functions</title>
<para>
NHibernate Linq provider feature a <literal>LinqExtensionMethod</literal> attribute. It allows using an
arbitrary, built-in or user defined, SQL function. It should be applied on a method having the same
arguments than the SQL function.
</para>
<programlisting><![CDATA[public static class CustomLinqExtensions
{
[LinqExtensionMethod()]
public static string Checksum(this double input)
{
// No need to implement it in .Net, unless you wish to call it
// outside IQueryable context too.
throw new NotImplementedException("This call should be translated " +
"to SQL and run db side, but it has been run with .Net runtime");
}
}]]></programlisting>
<para>
Then it can be used in a Linq to NHibernate query.
</para>
<programlisting><![CDATA[var rnd = (new Random()).NextDouble();
IList<Cat> cats =
session.Query<Cat>()
// Pseudo random order
.OrderBy(c => (c.Id * rnd).Checksum())
.ToList();]]></programlisting>
<para>
The function name is inferred from the method name. If needed, another name can be provided.
</para>
<programlisting><![CDATA[public static class CustomLinqExtensions
{
[LinqExtensionMethod("dbo.aCustomFunction")]
public static string ACustomFunction(this string input, string otherInput)
{
throw new NotImplementedException();
}
}]]></programlisting>
<para>
Since NHibernate v5.0, the Linq provider will no more evaluate in-memory the method call
even when it does not depend on the queried data. If you wish to have the method call evaluated
before querying whenever possible, and then replaced in the query by its resulting value, specify
<literal>LinqExtensionPreEvaluation.AllowPreEvaluation</literal> on the attribute.
</para>
<programlisting><![CDATA[public static class CustomLinqExtensions
{
[LinqExtensionMethod("dbo.aCustomFunction",
LinqExtensionPreEvaluation.AllowPreEvaluation)]
public static string ACustomFunction(this string input, string otherInput)
{
// In-memory evaluation implementation.
return input.Replace(otherInput, "blah");
}
}]]></programlisting>
</sect2>
<sect2 id="querylinq-extending-generator">
<title>Adding a custom generator</title>
<para>
Generators are responsible for translating .Net method calls found in lambdas to the proper HQL
constructs. Adding support for a new method call can be achieved by registering an additional
generator in the Linq to NHibernate provider.
</para>
<para>
If the purpose of the added method is to simply call some SQL function, using
<xref linkend="querylinq-extending-sqlfunctions" /> will be easier.
</para>
<para> </para>
<para>
As an example, here is how to add support for an <literal>AsNullable</literal> method which
would allow to call aggregates which may yield <literal>null</literal> without to explicitly
cast to the nullable type of the aggregate.
</para>
<programlisting><![CDATA[public static class NullableExtensions
{
public static T? AsNullable<T>(this T value) where T : struct
{
// Allow runtime use.
// Not useful for linq-to-nhibernate, could be:
// throw NotSupportedException();
return value;
}
}]]></programlisting>
<para>
Adding support in Linq to NHibernate for a custom method requires a generator. For this
<literal>AsNullable</literal> method, we need a method generator, declaring statically its
supported method.
</para>
<programlisting><![CDATA[public class AsNullableGenerator : BaseHqlGeneratorForMethod
{
public AsNullableGenerator()
{
SupportedMethods = new[]
{
ReflectHelper.GetMethodDefinition(() => NullableExtensions.AsNullable(0))
};
}
public override HqlTreeNode BuildHql(MethodInfo method,
Expression targetObject,
ReadOnlyCollection<Expression> arguments,
HqlTreeBuilder treeBuilder,
IHqlExpressionVisitor visitor)
{
// This has just to transmit the argument "as is", HQL does not need
// a specific call for null conversion.
return visitor.Visit(arguments[0]).AsExpression();
}
}]]></programlisting>
<para>
There are property generators too, and the supported methods or properties can be
dynamically declared. Check NHibernate <literal>NHibernate.Linq.Functions</literal>
namespace classes's sources for more examples. <literal>CompareGenerator</literal>
and <literal>DateTimePropertiesHqlGenerator</literal> are examples of those other cases.
</para>
<para>
For adding <literal>AsNullableGenerator</literal> in Linq to NHibernate provider, a new
generators registry should be used. Derive from the default one and merge it. (Here we
have a static declaration of method support case.)
</para>
<programlisting><![CDATA[public class ExtendedLinqToHqlGeneratorsRegistry :
DefaultLinqToHqlGeneratorsRegistry
{
public ExtendedLinqToHqlGeneratorsRegistry()
: base()
{
this.Merge(new AsNullableGenerator());
}
}]]></programlisting>
<para>
In the case of dynamic declaration of method support, another call is required instead of
the merge: <literal>RegisterGenerator</literal>. <literal>CompareGenerator</literal>
illustrates this.
</para>
<para>
The last step is to instruct NHibernate to use this extended registry. It can be achieved
through <link linkend="configuration-xmlconfig">xml configuration</link> under
<literal>session-factory</literal> node, or by
<link linkend="configuration-programmatic">code</link> before building the session factory.
Use one of them.
</para>
<programlisting><![CDATA[<property name="linqtohql.generatorsregistry">
YourNameSpace.ExtendedLinqToHqlGeneratorsRegistry, YourAssemblyName
</property>]]></programlisting>
<programlisting><![CDATA[using NHibernate.Cfg;
// ...
var cfg = new Configuration();
cfg.LinqToHqlGeneratorsRegistry<ExtendedLinqToHqlGeneratorsRegistry>();
// And build the session factory with this configuration.]]></programlisting>
<para>
Now the following query could be executed, without failing if no <literal>Max</literal> cat
exists.
</para>
<programlisting><![CDATA[var oldestMaxBirthDate =
session.Query<Cat>()
.Where(c => c.Name == "Max")
.Select(c => c.BirthDate.AsNullable())
.Min();]]></programlisting>
<para>
(Of course, the same result could be obtained with <literal>(DateTime?)(c.BirthDate)</literal>.)
</para>
<para>
By default, the Linq provider will try to evaluate the method call with .Net runtime
whenever possible, instead of translating it to SQL. It will not do it if at least one
of the parameters of the method call has its value originating from an entity, or if
the method is marked with the <literal>NoPreEvaluation</literal> attribute (available
since NHibernate 5.0).
</para>
</sect2>
</sect1>
</chapter>