Skip to content

Commit 241d2f5

Browse files
Add ability to use dynamic entities as C# dynamic
1 parent 9de4aaa commit 241d2f5

File tree

5 files changed

+250
-32
lines changed

5 files changed

+250
-32
lines changed

doc/reference/modules/persistent_classes.xml

+60-23
Original file line numberDiff line numberDiff line change
@@ -254,60 +254,56 @@ namespace Eg
254254
<para>
255255
Persistent entities don't necessarily have to be represented as POCO classes
256256
at runtime. NHibernate also supports dynamic models
257-
(using <literal>Dictionaries</literal>). With this approach, you don't
258-
write persistent classes, only mapping files.
257+
(using <literal>Dictionaries</literal> or C# <literal>dynamic</literal>). With this approach,
258+
you don't write persistent classes, only mapping files.
259259
</para>
260260

261261
<para>
262-
The following examples demonstrates the representation using <literal>Dictionaries</literal>.
262+
The following examples demonstrates the dynamic model feature.
263263
First, in the mapping file, an <literal>entity-name</literal> has to be declared
264264
instead of a class name:
265265
</para>
266-
267-
<programlisting><![CDATA[<hibernate-mapping>
268266

267+
<programlisting><![CDATA[<hibernate-mapping>
269268
<class entity-name="Customer">
270-
271-
<id name="id"
269+
<id name="Id"
272270
type="long"
273271
column="ID">
274272
<generator class="sequence"/>
275273
</id>
276274
277-
<property name="name"
275+
<property name="Name"
278276
column="NAME"
279277
type="string"/>
280278
281-
<property name="address"
279+
<property name="Address"
282280
column="ADDRESS"
283281
type="string"/>
284282
285-
<many-to-one name="organization"
283+
<many-to-one name="Organization"
286284
column="ORGANIZATION_ID"
287285
class="Organization"/>
288286
289-
<bag name="orders"
287+
<bag name="Orders"
290288
inverse="true"
291289
lazy="false"
292290
cascade="all">
293291
<key column="CUSTOMER_ID"/>
294292
<one-to-many class="Order"/>
295293
</bag>
296-
297294
</class>
298-
299295
</hibernate-mapping>]]></programlisting>
300-
296+
301297
<para>
302298
Note that even though associations are declared using target class names,
303299
the target type of an associations may also be a dynamic entity instead
304300
of a POCO.
305301
</para>
306-
302+
307303
<para>
308304
At runtime we can work with <literal>Dictionaries</literal>:
309305
</para>
310-
306+
311307
<programlisting><![CDATA[using(ISession s = OpenSession())
312308
using(ITransaction tx = s.BeginTransaction())
313309
{
@@ -328,7 +324,32 @@ using(ITransaction tx = s.BeginTransaction())
328324
329325
tx.Commit();
330326
}]]></programlisting>
331-
327+
328+
<para>
329+
Or we can work with <literal>dynamic</literal>:
330+
</para>
331+
332+
<programlisting><![CDATA[using(var s = OpenSession())
333+
using(var tx = s.BeginTransaction())
334+
{
335+
// Create a customer
336+
dynamic frank = new ExpandoObject();
337+
frank.Name = "Frank";
338+
339+
// Create an organization
340+
dynamic foobar = new ExpandoObject();
341+
foobar.Name = "Foobar Inc.";
342+
343+
// Link both
344+
frank.Organization = foobar;
345+
346+
// Save both
347+
s.Save("Customer", frank);
348+
s.Save("Organization", foobar);
349+
350+
tx.Commit();
351+
}]]></programlisting>
352+
332353
<para>
333354
The advantages of a dynamic mapping are quick turnaround time for prototyping
334355
without the need for entity class implementation. However, you lose compile-time
@@ -338,8 +359,9 @@ using(ITransaction tx = s.BeginTransaction())
338359
</para>
339360

340361
<para>
341-
A loaded dynamic entity can be manipulated as an <literal>IDictionary</literal> or
342-
<literal>IDictionary&lt;string, object&gt;</literal>.
362+
A loaded dynamic entity can be manipulated as an <literal>IDictionary</literal>,
363+
an <literal>IDictionary&lt;string, object&gt;</literal> or a C#
364+
<literal>dynamic</literal>.
343365
</para>
344366

345367
<programlisting><![CDATA[using(ISession s = OpenSession())
@@ -350,8 +372,23 @@ using(ITransaction tx = s.BeginTransaction())
350372
.List<IDictionary<string, object>>();
351373
...
352374
}]]></programlisting>
375+
376+
<programlisting><![CDATA[using System.Linq.Dynamic.Core;
377+
378+
...
379+
380+
using(ISession s = OpenSession())
381+
using(ITransaction tx = s.BeginTransaction())
382+
{
383+
var customers = s
384+
.Query<dynamic>("Customer")
385+
.OrderBy("Name")
386+
.ToList();
387+
...
388+
}]]></programlisting>
389+
353390
</sect1>
354-
391+
355392
<sect1 id="persistent-classes-tuplizers" revision="1">
356393
<title>Tuplizers</title>
357394

@@ -371,9 +408,9 @@ using(ITransaction tx = s.BeginTransaction())
371408
</para>
372409

373410
<para>
374-
Users may also plug in their own tuplizers. Perhaps you require that a <literal>IDictionary</literal>
375-
implementation other than <literal>System.Collections.Generic.Dictionary&lt;string, object&gt;</literal>
376-
is used while in the dynamic-map entity-mode; or perhaps you need to define a different proxy generation strategy
411+
Users may also plug in their own tuplizers. Perhaps you require that a <literal>IDictionary</literal> /
412+
<literal>DynamicObject</literal> implementation other than NHibernate own implementation is used while
413+
in the dynamic-map entity-mode; or perhaps you need to define a different proxy generation strategy
377414
than the one used by default. Both would be achieved by defining a custom tuplizer
378415
implementation. Tuplizers definitions are attached to the entity or component mapping they
379416
are meant to manage. Going back to the example of our customer entity:

src/NHibernate.Test/Async/EntityModeTest/Map/Basic/DynamicClassFixture.cs

+62
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,13 @@
1010

1111
using System.Collections;
1212
using System.Collections.Generic;
13+
using System.Dynamic;
14+
using System.Linq.Dynamic.Core;
15+
using System.Linq;
1316
using Antlr.Runtime.Misc;
1417
using NUnit.Framework;
1518
using NHibernate.Criterion;
19+
using NHibernate.Linq;
1620

1721
namespace NHibernate.Test.EntityModeTest.Map.Basic
1822
{
@@ -126,6 +130,14 @@ public async Task ShouldWorkWithCriteriaAndGenericsAsync()
126130
s => s.CreateCriteria("Model").List<IDictionary<string, object>>()));
127131
}
128132

133+
[Test]
134+
public async Task ShouldWorkWithLinqAndGenericsAsync()
135+
{
136+
await (TestLazyDynamicClassAsync(
137+
s => (IDictionary<string, object>) s.Query<dynamic>("ProductLine").OrderBy("Description").Single(),
138+
s => s.Query<dynamic>("Model").ToList().Cast<IDictionary<string, object>>().ToList()));
139+
}
140+
129141
public async Task TestLazyDynamicClassAsync(
130142
Func<ISession, IDictionary<string, object>> singleCarQueryHandler,
131143
Func<ISession, IList<IDictionary<string, object>>> allModelQueryHandler, CancellationToken cancellationToken = default(CancellationToken))
@@ -179,6 +191,56 @@ public async Task TestLazyDynamicClassAsync(
179191
}
180192
}
181193

194+
[Test]
195+
public async Task ShouldWorkWithLinqAndDynamicsAsync()
196+
{
197+
using (var s = OpenSession())
198+
using (var t = s.BeginTransaction())
199+
{
200+
dynamic cars = new ExpandoObject();
201+
cars.Description = "Cars";
202+
203+
dynamic monaro = new ExpandoObject();
204+
monaro.ProductLine = cars;
205+
monaro.Name = "Monaro";
206+
monaro.Description = "Holden Monaro";
207+
208+
dynamic hsv = new ExpandoObject();
209+
hsv.ProductLine = cars;
210+
hsv.Name = "hsv";
211+
hsv.Description = "Holden hsv";
212+
213+
var models = new List<dynamic> {monaro, hsv};
214+
215+
cars.Models = models;
216+
217+
await (s.SaveAsync("ProductLine", cars));
218+
await (t.CommitAsync());
219+
}
220+
221+
using (var s = OpenSession())
222+
using (var t = s.BeginTransaction())
223+
{
224+
var cars = await (s.Query<dynamic>("ProductLine").OrderBy("Description").SingleAsync());
225+
var models = cars.Models;
226+
Assert.That(NHibernateUtil.IsInitialized(models), Is.False);
227+
Assert.That(models.Count, Is.EqualTo(2));
228+
Assert.That(NHibernateUtil.IsInitialized(models), Is.True);
229+
s.Clear();
230+
231+
var list = await (s.Query<dynamic>("Model").Where("ProductLine.Description = @0", "Cars").ToListAsync());
232+
foreach (var model in list)
233+
{
234+
Assert.That(NHibernateUtil.IsInitialized(model.ProductLine), Is.False);
235+
}
236+
var model1 = list[0];
237+
Assert.That(model1.ProductLine.Models.Contains(model1), Is.True);
238+
s.Clear();
239+
240+
await (t.CommitAsync());
241+
}
242+
}
243+
182244
protected override void OnTearDown()
183245
{
184246
using (var s = OpenSession())

src/NHibernate.Test/EntityModeTest/Map/Basic/DynamicClassFixture.cs

+61
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
using System.Collections;
22
using System.Collections.Generic;
3+
using System.Dynamic;
4+
using System.Linq.Dynamic.Core;
5+
using System.Linq;
36
using Antlr.Runtime.Misc;
47
using NUnit.Framework;
58
using NHibernate.Criterion;
@@ -114,6 +117,14 @@ public void ShouldWorkWithCriteriaAndGenerics()
114117
s => s.CreateCriteria("Model").List<IDictionary<string, object>>());
115118
}
116119

120+
[Test]
121+
public void ShouldWorkWithLinqAndGenerics()
122+
{
123+
TestLazyDynamicClass(
124+
s => (IDictionary<string, object>) s.Query<dynamic>("ProductLine").OrderBy("Description").Single(),
125+
s => s.Query<dynamic>("Model").ToList().Cast<IDictionary<string, object>>().ToList());
126+
}
127+
117128
public void TestLazyDynamicClass(
118129
Func<ISession, IDictionary<string, object>> singleCarQueryHandler,
119130
Func<ISession, IList<IDictionary<string, object>>> allModelQueryHandler)
@@ -167,6 +178,56 @@ public void TestLazyDynamicClass(
167178
}
168179
}
169180

181+
[Test]
182+
public void ShouldWorkWithLinqAndDynamics()
183+
{
184+
using (var s = OpenSession())
185+
using (var t = s.BeginTransaction())
186+
{
187+
dynamic cars = new ExpandoObject();
188+
cars.Description = "Cars";
189+
190+
dynamic monaro = new ExpandoObject();
191+
monaro.ProductLine = cars;
192+
monaro.Name = "Monaro";
193+
monaro.Description = "Holden Monaro";
194+
195+
dynamic hsv = new ExpandoObject();
196+
hsv.ProductLine = cars;
197+
hsv.Name = "hsv";
198+
hsv.Description = "Holden hsv";
199+
200+
var models = new List<dynamic> {monaro, hsv};
201+
202+
cars.Models = models;
203+
204+
s.Save("ProductLine", cars);
205+
t.Commit();
206+
}
207+
208+
using (var s = OpenSession())
209+
using (var t = s.BeginTransaction())
210+
{
211+
var cars = s.Query<dynamic>("ProductLine").OrderBy("Description").Single();
212+
var models = cars.Models;
213+
Assert.That(NHibernateUtil.IsInitialized(models), Is.False);
214+
Assert.That(models.Count, Is.EqualTo(2));
215+
Assert.That(NHibernateUtil.IsInitialized(models), Is.True);
216+
s.Clear();
217+
218+
var list = s.Query<dynamic>("Model").Where("ProductLine.Description = @0", "Cars").ToList();
219+
foreach (var model in list)
220+
{
221+
Assert.That(NHibernateUtil.IsInitialized(model.ProductLine), Is.False);
222+
}
223+
var model1 = list[0];
224+
Assert.That(model1.ProductLine.Models.Contains(model1), Is.True);
225+
s.Clear();
226+
227+
t.Commit();
228+
}
229+
}
230+
170231
protected override void OnTearDown()
171232
{
172233
using (var s = OpenSession())

0 commit comments

Comments
 (0)