Skip to content

Commit 62cf307

Browse files
NH-3488 - Anonymous selector for Linq insert/update
1 parent 0530838 commit 62cf307

File tree

5 files changed

+206
-58
lines changed

5 files changed

+206
-58
lines changed

doc/reference/modules/query_linq.xml

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -474,7 +474,7 @@ IList<Cat> oldCats =
474474
Beginning with NHibernate 5.0, Linq queries can be used for inserting, updating or deleting entities.
475475
The query defines the data to delete, update or insert, and then <literal>Delete</literal>,
476476
<literal>Update</literal> and <literal>Insert</literal> queryable extension methods allow to delete it,
477-
or instruct in which way it should updated or inserted. Those queries happen entirely inside the
477+
or instruct in which way it should be updated or inserted. Those queries happen entirely inside the
478478
database, without extracting corresponding entities out of the database.
479479
</para>
480480
<para>
@@ -487,7 +487,7 @@ IList<Cat> oldCats =
487487
<para>
488488
<literal>Insert</literal> method extension expects a NHibernate queryable defining the data source of
489489
the insert. This data can be entities or a projection. Then it allows specifying the target entity type
490-
to insert, and how to convert source data to those target entities. Two forms of target specification
490+
to insert, and how to convert source data to those target entities. Three forms of target specification
491491
exist.
492492
</para>
493493
<para>
@@ -497,6 +497,13 @@ IList<Cat> oldCats =
497497
.Where(c => c.BodyWeight > 20)
498498
.Insert()
499499
.As(c => new Dog { Name = c.Name + "dog", BodyWeight = c.BodyWeight });]]></programlisting>
500+
<para>
501+
Projections can be done with an anonymous object too, but it requires supplying explicitly the target type:
502+
</para>
503+
<programlisting><![CDATA[session.Query<Cat>()
504+
.Where(c => c.BodyWeight > 20)
505+
.Insert()
506+
.As<Dog>(c => new { Name = c.Name + "dog", BodyWeight = c.BodyWeight });]]></programlisting>
500507
<para>
501508
Or using assignments:
502509
</para>
@@ -507,7 +514,7 @@ IList<Cat> oldCats =
507514
.Set(d => d.Name, c => c.Name + "dog")
508515
.Set(d => d.BodyWeight, c => c.BodyWeight));]]></programlisting>
509516
<para>
510-
In both cases, unspecified properties are not included in the resulting SQL insert.
517+
In all cases, unspecified properties are not included in the resulting SQL insert.
511518
<link linkend="mapping-declaration-version"><literal>version</literal></link> and
512519
<link linkend="mapping-declaration-timestamp"><literal>timestamp</literal></link> properties are
513520
exceptions. If not specified, they are inserted with their <literal>seed</literal> value.
@@ -523,7 +530,7 @@ IList<Cat> oldCats =
523530
<para>
524531
<literal>Update</literal> method extension expects a queryable defining the entities to update.
525532
Then it allows specifying which properties should be updated with which values. As for
526-
<literal>Insert</literal>, two forms of target specification exist.
533+
<literal>Insert</literal>, three forms of target specification exist.
527534
</para>
528535
<para>
529536
Using projection to updated entity:
@@ -532,6 +539,13 @@ IList<Cat> oldCats =
532539
.Where(c => c.BodyWeight > 20)
533540
.Update()
534541
.As(c => new Cat { BodyWeight = c.BodyWeight / 2 });]]></programlisting>
542+
<para>
543+
Projections can be done with an anonymous object too:
544+
</para>
545+
<programlisting><![CDATA[session.Query<Cat>()
546+
.Where(c => c.BodyWeight > 20)
547+
.Update()
548+
.As(c => new { BodyWeight = c.BodyWeight / 2 });]]></programlisting>
535549
<para>
536550
Or using assignments:
537551
</para>
@@ -541,7 +555,7 @@ IList<Cat> oldCats =
541555
.Assign(a => a
542556
.Set(c => c.BodyWeight, c => c.BodyWeight / 2));]]></programlisting>
543557
<para>
544-
In both cases, unspecified properties are not included in the resulting SQL update. This could
558+
In all cases, unspecified properties are not included in the resulting SQL update. This could
545559
be changed for <link linkend="mapping-declaration-version"><literal>version</literal></link> and
546560
<link linkend="mapping-declaration-timestamp"><literal>timestamp</literal></link> properties:
547561
using <literal>UpdateVersioned</literal> instead of <literal>Update</literal> allows incrementing

src/NHibernate.Test/LinqBulkManipulation/Fixture.cs

Lines changed: 105 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,20 @@ public void SimpleInsert()
187187
using (var s = OpenSession())
188188
using (var t = s.BeginTransaction())
189189
{
190-
var count = s.Query<Car>().Insert().As(x => new Pickup { Id = x.Id, Vin = x.Vin, Owner = x.Owner });
190+
var count = s.Query<Car>().Insert().As(x => new Pickup { Id = -x.Id, Vin = x.Vin, Owner = x.Owner });
191+
Assert.AreEqual(1, count);
192+
193+
t.Commit();
194+
}
195+
}
196+
197+
[Test]
198+
public void SimpleAnonymousInsert()
199+
{
200+
using (var s = OpenSession())
201+
using (var t = s.BeginTransaction())
202+
{
203+
var count = s.Query<Car>().Insert().As<Pickup>(x => new { Id = -x.Id, x.Vin, x.Owner });
191204
Assert.AreEqual(1, count);
192205

193206
t.Commit();
@@ -200,10 +213,11 @@ public void SimpleInsertFromAggregate()
200213
using (var s = OpenSession())
201214
using (var t = s.BeginTransaction())
202215
{
203-
var count = s.Query<Car>()
216+
var count = s
217+
.Query<Car>()
204218
.GroupBy(x => x.Id)
205219
.Select(x => new { Id = x.Key, Vin = x.Max(y => y.Vin), Owner = x.Max(y => y.Owner) })
206-
.Insert().As(x => new Pickup { Id = x.Id, Vin = x.Vin, Owner = x.Owner });
220+
.Insert().As(x => new Pickup { Id = -x.Id, Vin = x.Vin, Owner = x.Owner });
207221
Assert.AreEqual(1, count);
208222

209223
t.Commit();
@@ -216,7 +230,8 @@ public void SimpleInsertFromLimited()
216230
using (var s = OpenSession())
217231
using (var t = s.BeginTransaction())
218232
{
219-
var count = s.Query<Vehicle>()
233+
var count = s
234+
.Query<Vehicle>()
220235
.Skip(1)
221236
.Take(1)
222237
.Insert().As(x => new Pickup { Id = -x.Id, Vin = x.Vin, Owner = x.Owner });
@@ -232,8 +247,9 @@ public void SimpleInsertWithConstants()
232247
using (var s = OpenSession())
233248
using (var t = s.BeginTransaction())
234249
{
235-
var count = s.Query<Car>()
236-
.Insert().Into<Pickup>(x => x.Set(y => y.Id, y => y.Id).Set(y => y.Vin, y => y.Vin).Set(y => y.Owner, "The owner"));
250+
var count = s
251+
.Query<Car>()
252+
.Insert().Into<Pickup>(x => x.Set(y => y.Id, y => -y.Id).Set(y => y.Vin, y => y.Vin).Set(y => y.Owner, "The owner"));
237253
Assert.AreEqual(1, count);
238254

239255
t.Commit();
@@ -246,9 +262,10 @@ public void SimpleInsertFromProjection()
246262
using (var s = OpenSession())
247263
using (var t = s.BeginTransaction())
248264
{
249-
var count = s.Query<Car>()
265+
var count = s
266+
.Query<Car>()
250267
.Select(x => new { x.Id, x.Owner, UpperOwner = x.Owner.ToUpper() })
251-
.Insert().Into<Pickup>(x => x.Set(y => y.Id, y => y.Id).Set(y => y.Vin, y => y.UpperOwner));
268+
.Insert().Into<Pickup>(x => x.Set(y => y.Id, y => -y.Id).Set(y => y.Vin, y => y.UpperOwner));
252269
Assert.AreEqual(1, count);
253270

254271
t.Commit();
@@ -261,9 +278,10 @@ public void InsertWithClientSideRequirementsThrowsException()
261278
using (var s = OpenSession())
262279
using (var t = s.BeginTransaction())
263280
{
264-
Assert.Throws<NotSupportedException>(() =>
265-
s.Query<Car>()
266-
.Insert().As(x => new Pickup { Id = x.Id, Vin = x.Vin, Owner = x.Owner.PadRight(200) }));
281+
Assert.Throws<NotSupportedException>(
282+
() => s
283+
.Query<Car>()
284+
.Insert().As(x => new Pickup { Id = -x.Id, Vin = x.Vin, Owner = x.Owner.PadRight(200) }));
267285

268286
t.Commit();
269287
}
@@ -277,7 +295,8 @@ public void InsertWithManyToOne()
277295
using (var s = OpenSession())
278296
using (var t = s.BeginTransaction())
279297
{
280-
var count = s.Query<Human>()
298+
var count = s
299+
.Query<Human>()
281300
.Insert().As(x => new Animal { Description = x.Description, BodyWeight = x.BodyWeight, Mother = x.Mother });
282301
Assert.AreEqual(3, count);
283302

@@ -293,7 +312,8 @@ public void InsertWithManyToOneAsParameter()
293312
using (var s = OpenSession())
294313
using (var t = s.BeginTransaction())
295314
{
296-
var count = s.Query<Human>()
315+
var count = s
316+
.Query<Human>()
297317
.Insert().As(x => new Animal { Description = x.Description, BodyWeight = x.BodyWeight, Mother = _butterfly });
298318
Assert.AreEqual(3, count);
299319

@@ -309,7 +329,8 @@ public void InsertWithManyToOneWithCompositeKey()
309329
using (var s = OpenSession())
310330
using (var t = s.BeginTransaction())
311331
{
312-
var count = s.Query<EntityWithCrazyCompositeKey>()
332+
var count = s
333+
.Query<EntityWithCrazyCompositeKey>()
313334
.Insert().As(x => new EntityReferencingEntityWithCrazyCompositeKey { Name = "Child", Parent = x });
314335
Assert.AreEqual(1, count);
315336

@@ -324,7 +345,7 @@ public void InsertIntoSuperclassPropertiesFails()
324345
using (var t = s.BeginTransaction())
325346
{
326347
Assert.Throws<QueryException>(
327-
() => s.Query<Lizard>().Insert().As(x => new Human { Id = x.Id, BodyWeight = x.BodyWeight }),
348+
() => s.Query<Lizard>().Insert().As(x => new Human { Id = -x.Id, BodyWeight = x.BodyWeight }),
328349
"superclass prop insertion did not error");
329350

330351
t.Commit();
@@ -381,10 +402,10 @@ public void InsertWithGeneratedVersionAndId()
381402
using (var s = OpenSession())
382403
using (var t = s.BeginTransaction())
383404
{
384-
var count =
385-
s.Query<IntegerVersioned>()
386-
.Where(x => x.Id == initialId)
387-
.Insert().As(x => new IntegerVersioned { Name = x.Name, Data = x.Data });
405+
var count = s
406+
.Query<IntegerVersioned>()
407+
.Where(x => x.Id == initialId)
408+
.Insert().As(x => new IntegerVersioned { Name = x.Name, Data = x.Data });
388409
Assert.That(count, Is.EqualTo(1), "unexpected insertion count");
389410
t.Commit();
390411
}
@@ -408,10 +429,10 @@ public void InsertWithGeneratedTimestampVersion()
408429
using (var s = OpenSession())
409430
using (var t = s.BeginTransaction())
410431
{
411-
var count =
412-
s.Query<TimestampVersioned>()
413-
.Where(x => x.Id == initialId)
414-
.Insert().As(x => new TimestampVersioned { Name = x.Name, Data = x.Data });
432+
var count = s
433+
.Query<TimestampVersioned>()
434+
.Where(x => x.Id == initialId)
435+
.Insert().As(x => new TimestampVersioned { Name = x.Name, Data = x.Data });
415436
Assert.That(count, Is.EqualTo(1), "unexpected insertion count");
416437

417438
t.Commit();
@@ -438,7 +459,8 @@ public void InsertWithSelectListUsingJoins()
438459

439460
Assert.DoesNotThrow(() =>
440461
{
441-
s.Query<Human>().Where(x => x.Mother.Mother != null)
462+
s
463+
.Query<Human>().Where(x => x.Mother.Mother != null)
442464
.Insert().As(x => new Animal { Description = x.Description, BodyWeight = x.BodyWeight });
443465
});
444466

@@ -462,13 +484,25 @@ public void InsertToComponent()
462484
// https://firebirdsql.org/file/documentation/reference_manuals/fblangref25-en/html/fblangref25-dml-insert.html#fblangref25-dml-insert-select-unstable
463485
.Where(sc => sc.Name.First != correctName)
464486
.Insert().Into<SimpleClassWithComponent>(x => x.Set(y => y.Name.First, y => correctName));
465-
Assert.That(count, Is.EqualTo(1), "incorrect insert count");
487+
Assert.That(count, Is.EqualTo(1), "incorrect insert count from individual setters");
466488

467-
count =
468-
s.Query<SimpleClassWithComponent>()
469-
.Where(x => x.Name.First == correctName && x.Name.Initial != 'Z')
470-
.Insert().As(x => new SimpleClassWithComponent { Name = new Name { First = x.Name.First, Last = x.Name.Last, Initial = 'Z' } });
471-
Assert.That(count, Is.EqualTo(1), "incorrect insert from corrected count");
489+
count = s
490+
.Query<SimpleClassWithComponent>()
491+
.Where(x => x.Name.First == correctName && x.Name.Initial != 'Z')
492+
.Insert().As(x => new SimpleClassWithComponent { Name = new Name { First = x.Name.First, Last = x.Name.Last, Initial = 'Z' } });
493+
Assert.That(count, Is.EqualTo(1), "incorrect insert from non anonymous selector");
494+
495+
count = s
496+
.Query<SimpleClassWithComponent>()
497+
.Where(x => x.Name.First == correctName && x.Name.Initial == 'Z')
498+
.Insert().As<SimpleClassWithComponent>(x => new { Name = new { x.Name.First, x.Name.Last, Initial = 'W' } });
499+
Assert.That(count, Is.EqualTo(1), "incorrect insert from anonymous selector");
500+
501+
count = s
502+
.Query<SimpleClassWithComponent>()
503+
.Where(x => x.Name.First == correctName && x.Name.Initial == 'Z')
504+
.Insert().As<SimpleClassWithComponent>(x => new { Name = new Name { First = x.Name.First, Last = x.Name.Last, Initial = 'V' } });
505+
Assert.That(count, Is.EqualTo(1), "incorrect insert from hybrid selector");
472506
t.Commit();
473507
}
474508
}
@@ -488,6 +522,34 @@ private void CheckSupportOfBulkInsertionWithGeneratedId<T>()
488522

489523
#region UPDATES
490524

525+
[Test]
526+
public void SimpleUpdate()
527+
{
528+
using (var s = OpenSession())
529+
using (s.BeginTransaction())
530+
{
531+
var count = s
532+
.Query<Car>()
533+
.Update()
534+
.As(a => new Car { Owner = a.Owner + " a" });
535+
Assert.AreEqual(1, count);
536+
}
537+
}
538+
539+
[Test]
540+
public void SimpleAnonymousUpdate()
541+
{
542+
using (var s = OpenSession())
543+
using (s.BeginTransaction())
544+
{
545+
var count = s
546+
.Query<Car>()
547+
.Update()
548+
.As(a => new { Owner = a.Owner + " a" });
549+
Assert.AreEqual(1, count);
550+
}
551+
}
552+
491553
[Test]
492554
public void UpdateWithWhereExistsSubquery()
493555
{
@@ -501,7 +563,8 @@ public void UpdateWithWhereExistsSubquery()
501563
using (var s = OpenSession())
502564
using (var t = s.BeginTransaction())
503565
{
504-
var count = s.Query<Human>()
566+
var count = s
567+
.Query<Human>()
505568
.Where(x => x.Friends.OfType<Human>().Any(f => f.Name.Last == "Public"))
506569
.Update().Assign(x => x.Set(y => y.Description, "updated"));
507570
Assert.That(count, Is.EqualTo(1));
@@ -513,14 +576,16 @@ public void UpdateWithWhereExistsSubquery()
513576
using (var t = s.BeginTransaction())
514577
{
515578
// one-to-many test
516-
var count = s.Query<SimpleEntityWithAssociation>()
579+
var count = s
580+
.Query<SimpleEntityWithAssociation>()
517581
.Where(x => x.AssociatedEntities.Any(a => a.Name == "one-to-many-association"))
518582
.Update().Assign(x => x.Set(y => y.Name, "updated"));
519583
Assert.That(count, Is.EqualTo(1));
520584
// many-to-many test
521585
if (Dialect.SupportsSubqueryOnMutatingTable)
522586
{
523-
count = s.Query<SimpleEntityWithAssociation>()
587+
count = s
588+
.Query<SimpleEntityWithAssociation>()
524589
.Where(x => x.ManyToManyAssociatedEntities.Any(a => a.Name == "many-to-many-association"))
525590
.Update().Assign(x => x.Set(y => y.Name, "updated"));
526591

@@ -540,9 +605,9 @@ public void IncrementCounterVersion()
540605
using (var t = s.BeginTransaction())
541606
{
542607
// Note: Update more than one column to showcase NH-3624, which involved losing some columns. /2014-07-26
543-
var count =
544-
s.Query<IntegerVersioned>()
545-
.UpdateVersioned().Assign(x => x.Set(y => y.Name, y => y.Name + "upd").Set(y => y.Data, y => y.Data + "upd"));
608+
var count = s
609+
.Query<IntegerVersioned>()
610+
.UpdateVersioned().Assign(x => x.Set(y => y.Name, y => y.Name + "upd").Set(y => y.Data, y => y.Data + "upd"));
546611
Assert.That(count, Is.EqualTo(1), "incorrect exec count");
547612
t.Commit();
548613
}
@@ -570,7 +635,8 @@ public void IncrementTimestampVersion()
570635
using (var t = s.BeginTransaction())
571636
{
572637
// Note: Update more than one column to showcase NH-3624, which involved losing some columns. /2014-07-26
573-
var count = s.Query<TimestampVersioned>()
638+
var count = s
639+
.Query<TimestampVersioned>()
574640
.UpdateVersioned().Assign(x => x.Set(y => y.Name, y => y.Name + "upd").Set(y => y.Data, y => y.Data + "upd"));
575641
Assert.That(count, Is.EqualTo(1), "incorrect exec count");
576642
t.Commit();
@@ -625,8 +691,8 @@ public void UpdateWithClientSideRequirementsThrowsException()
625691
using (var s = OpenSession())
626692
using (var t = s.BeginTransaction())
627693
{
628-
Assert.Throws<NotSupportedException>(() =>
629-
s.Query<Human>().Where(x => x.Id == _stevee.Id).Update().As(x => new Human { Name = { First = x.Name.First.PadLeft(200) } })
694+
Assert.Throws<NotSupportedException>(
695+
() => s.Query<Human>().Where(x => x.Id == _stevee.Id).Update().As(x => new Human { Name = { First = x.Name.First.PadLeft(200) } })
630696
);
631697

632698
t.Commit();

0 commit comments

Comments
 (0)