Skip to content

NH-3488 - Strongly typed Inserts, Updates and Deletes #391

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 24, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion doc/reference/modules/batch.xml
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ session.Close();]]></programlisting>
(DML) statements: <literal>INSERT</literal>, <literal>UPDATE</literal>, <literal>DELETE</literal>)
data directly in the database will not affect in-memory state. However, NHibernate provides methods
for bulk SQL-style DML statement execution which are performed through the
Hibernate Query Language (<link linkend="queryhql">HQL</link>).
Hibernate Query Language (<link linkend="queryhql">HQL</link>). A
<link linkend="querylinq-modifying">Linq implementation</link> is available too.
</para>

<para>
Expand Down
113 changes: 113 additions & 0 deletions doc/reference/modules/query_linq.xml
Original file line number Diff line number Diff line change
Expand Up @@ -467,6 +467,119 @@ IList<Cat> oldCats =
.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>

Expand Down
48 changes: 48 additions & 0 deletions src/NHibernate.Test/Hql/Ast/BulkManipulation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,29 @@ public void SimpleInsert()
data.Cleanup();
}

[Test]
public void SimpleInsertFromAggregate()
{
var data = new TestData(this);
data.Prepare();

ISession s = OpenSession();
ITransaction t = s.BeginTransaction();

s.CreateQuery("insert into Pickup (id, Vin, Owner) select id, max(Vin), max(Owner) from Car group by id").ExecuteUpdate();

t.Commit();
t = s.BeginTransaction();

s.CreateQuery("delete Vehicle").ExecuteUpdate();

t.Commit();
s.Close();

data.Cleanup();
}


[Test]
public void InsertWithManyToOne()
{
Expand All @@ -92,6 +115,31 @@ public void InsertWithManyToOne()
data.Cleanup();
}

[Test]
public void InsertWithManyToOneAsParameter()
{
var data = new TestData(this);
data.Prepare();

ISession s = OpenSession();
ITransaction t = s.BeginTransaction();

var mother = data.Butterfly;

s.CreateQuery(
"insert into Animal (description, bodyWeight, mother) select description, bodyWeight, :mother from Human")
.SetEntity("mother",mother)
.ExecuteUpdate();

t.Commit();
t = s.BeginTransaction();

t.Commit();
s.Close();

data.Cleanup();
}

[Test]
public void InsertWithMismatchedTypes()
{
Expand Down
41 changes: 41 additions & 0 deletions src/NHibernate.Test/LinqBulkManipulation/Domain/Address.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
namespace NHibernate.Test.LinqBulkManipulation.Domain
{
public class Address
{
private string street;
private string city;
private string postalCode;
private string country;
private StateProvince stateProvince;

public string Street
{
get { return street; }
set { street = value; }
}

public string City
{
get { return city; }
set { city = value; }
}

public string PostalCode
{
get { return postalCode; }
set { postalCode = value; }
}

public string Country
{
get { return country; }
set { country = value; }
}

public StateProvince StateProvince
{
get { return stateProvince; }
set { stateProvince = value; }
}
}
}
74 changes: 74 additions & 0 deletions src/NHibernate.Test/LinqBulkManipulation/Domain/Animal.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
using System.Collections.Generic;

namespace NHibernate.Test.LinqBulkManipulation.Domain
{
public class Animal
{
private long id;
private float bodyWeight;
private ISet<Animal> offspring;
private Animal mother;
private Animal father;
private string description;
private Zoo zoo;
private string serialNumber;

public virtual long Id
{
get { return id; }
set { id = value; }
}

public virtual float BodyWeight
{
get { return bodyWeight; }
set { bodyWeight = value; }
}

public virtual ISet<Animal> Offspring
{
get { return offspring; }
set { offspring = value; }
}

public virtual Animal Mother
{
get { return mother; }
set { mother = value; }
}

public virtual Animal Father
{
get { return father; }
set { father = value; }
}

public virtual string Description
{
get { return description; }
set { description = value; }
}

public virtual Zoo Zoo
{
get { return zoo; }
set { zoo = value; }
}

public virtual string SerialNumber
{
get { return serialNumber; }
set { serialNumber = value; }
}

public virtual void AddOffspring(Animal offSpring)
{
if (offspring == null)
{
offspring = new HashSet<Animal>();
}

offspring.Add(offSpring);
}
}
}
Loading