Skip to content

Commit 02761b7

Browse files
NH-3488 - alternate Linq DML syntax, inspired by linq2db.
1 parent 62cf307 commit 02761b7

File tree

8 files changed

+359
-243
lines changed

8 files changed

+359
-243
lines changed

src/NHibernate.Test/LinqBulkManipulation/Fixture.cs

Lines changed: 60 additions & 55 deletions
Large diffs are not rendered by default.

src/NHibernate/Linq/Assignments.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,40 +11,38 @@ namespace NHibernate.Linq
1111
/// </summary>
1212
/// <typeparam name="TSource">The type of the entity source of the insert or to update.</typeparam>
1313
/// <typeparam name="TTarget">The type of the entity to insert or to update.</typeparam>
14-
public class Assignments<TSource, TTarget>
14+
internal class Assignments<TSource, TTarget>
1515
{
1616
private readonly Dictionary<string, Expression> _assignments = new Dictionary<string, Expression>();
1717

1818
internal IReadOnlyDictionary<string, Expression> List => _assignments;
1919

2020
/// <summary>
21-
/// Sets the specified property.
21+
/// Set the specified property.
2222
/// </summary>
2323
/// <typeparam name="TProp">The type of the property.</typeparam>
2424
/// <param name="property">The property.</param>
2525
/// <param name="expression">The expression that should be assigned to the property.</param>
2626
/// <returns>The current assignments list.</returns>
27-
public Assignments<TSource, TTarget> Set<TProp>(Expression<Func<TTarget, TProp>> property, Expression<Func<TSource, TProp>> expression)
27+
public void Set<TProp>(Expression<Func<TTarget, TProp>> property, Expression<Func<TSource, TProp>> expression)
2828
{
2929
if (expression == null)
3030
throw new ArgumentNullException(nameof(expression));
3131
var member = GetMemberExpression(property);
3232
_assignments.Add(member.GetMemberPath(), expression);
33-
return this;
3433
}
3534

3635
/// <summary>
37-
/// Sets the specified property.
36+
/// Set the specified property.
3837
/// </summary>
3938
/// <typeparam name="TProp">The type of the property.</typeparam>
4039
/// <param name="property">The property.</param>
4140
/// <param name="value">The value.</param>
4241
/// <returns>The current assignments list.</returns>
43-
public Assignments<TSource, TTarget> Set<TProp>(Expression<Func<TTarget, TProp>> property, TProp value)
42+
public void Set<TProp>(Expression<Func<TTarget, TProp>> property, TProp value)
4443
{
4544
var member = GetMemberExpression(property);
4645
_assignments.Add(member.GetMemberPath(), Expression.Constant(value, typeof(TProp)));
47-
return this;
4846
}
4947

5048
private static MemberExpression GetMemberExpression<TProp>(Expression<Func<TTarget, TProp>> property)
Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
using System;
2+
using System.Linq;
3+
using System.Linq.Expressions;
4+
5+
namespace NHibernate.Linq
6+
{
7+
/// <summary>
8+
/// NHibernate LINQ DML extension methods. They are meant to work with <see cref="NhQueryable{T}"/>. Supplied <see cref="IQueryable{T}"/> parameters
9+
/// should at least have an <see cref="INhQueryProvider"/> <see cref="IQueryable.Provider"/>. <see cref="LinqExtensionMethods.Query{T}(ISession)"/> and
10+
/// its overloads supply such queryables.
11+
/// </summary>
12+
public static class DmlExtensionMethods
13+
{
14+
/// <summary>
15+
/// Delete all entities selected by the specified query. The delete operation is performed in the database without reading the entities out of it.
16+
/// </summary>
17+
/// <typeparam name="TSource">The type of the elements of <paramref name="source" />.</typeparam>
18+
/// <param name="source">The query matching the entities to delete.</param>
19+
/// <returns>The number of deleted entities.</returns>
20+
public static int Delete<TSource>(this IQueryable<TSource> source)
21+
{
22+
var provider = source.GetNhProvider();
23+
return provider.ExecuteDml<TSource>(QueryMode.Delete, source.Expression);
24+
}
25+
26+
/// <summary>
27+
/// Update all entities selected by the specified query. The update operation is performed in the database without reading the entities out of it.
28+
/// </summary>
29+
/// <typeparam name="TSource">The type of the elements of <paramref name="source" />.</typeparam>
30+
/// <param name="source">The query matching the entities to update.</param>
31+
/// <param name="expression">The update setters expressed as a member initialization of updated entities, e.g.
32+
/// <c>x => new Dog { Name = x.Name, Age = x.Age + 5 }</c>. Unset members are ignored and left untouched.</param>
33+
/// <returns>The number of updated entities.</returns>
34+
public static int Update<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, TSource>> expression)
35+
{
36+
return ExecuteUpdate(source, DmlExpressionRewriter.PrepareExpression(source.Expression, expression), false);
37+
}
38+
39+
/// <summary>
40+
/// Update all entities selected by the specified query, using an anonymous initializer for specifying setters. The update operation is performed
41+
/// in the database without reading the entities out of it.
42+
/// </summary>
43+
/// <typeparam name="TSource">The type of the elements of <paramref name="source" />.</typeparam>
44+
/// <param name="source">The query matching the entities to update.</param>
45+
/// <param name="expression">The assignments expressed as an anonymous object, e.g.
46+
/// <c>x => new { Name = x.Name, Age = x.Age + 5 }</c>. Unset members are ignored and left untouched.</param>
47+
/// <returns>The number of updated entities.</returns>
48+
public static int Update<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, object>> expression)
49+
{
50+
return ExecuteUpdate(source, DmlExpressionRewriter.PrepareExpressionFromAnonymous(source.Expression, expression), false);
51+
}
52+
53+
/// <summary>
54+
/// Perform an <c>update versioned</c> on all entities selected by the specified query. The update operation is performed in the database without
55+
/// reading the entities out of it.
56+
/// </summary>
57+
/// <typeparam name="TSource">The type of the elements of <paramref name="source" />.</typeparam>
58+
/// <param name="source">The query matching the entities to update.</param>
59+
/// <param name="expression">The update setters expressed as a member initialization of updated entities, e.g.
60+
/// <c>x => new Dog { Name = x.Name, Age = x.Age + 5 }</c>. Unset members are ignored and left untouched.</param>
61+
/// <returns>The number of updated entities.</returns>
62+
public static int UpdateVersioned<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, TSource>> expression)
63+
{
64+
return ExecuteUpdate(source, DmlExpressionRewriter.PrepareExpression(source.Expression, expression), true);
65+
}
66+
67+
/// <summary>
68+
/// Perform an <c>update versioned</c> on all entities selected by the specified query, using an anonymous initializer for specifying setters.
69+
/// The update operation is performed in the database without reading the entities out of it.
70+
/// </summary>
71+
/// <typeparam name="TSource">The type of the elements of <paramref name="source" />.</typeparam>
72+
/// <param name="source">The query matching the entities to update.</param>
73+
/// <param name="expression">The assignments expressed as an anonymous object, e.g.
74+
/// <c>x => new { Name = x.Name, Age = x.Age + 5 }</c>. Unset members are ignored and left untouched.</param>
75+
/// <returns>The number of updated entities.</returns>
76+
public static int UpdateVersioned<TSource>(this IQueryable<TSource> source, Expression<Func<TSource, object>> expression)
77+
{
78+
return ExecuteUpdate(source, DmlExpressionRewriter.PrepareExpressionFromAnonymous(source.Expression, expression), true);
79+
}
80+
81+
/// <summary>
82+
/// Initiate an update for the entities selected by the query. Return
83+
/// a builder allowing to set properties and allowing to execute the update.
84+
/// </summary>
85+
/// <typeparam name="TSource">The type of the elements of <paramref name="source" />.</typeparam>
86+
/// <param name="source">The query matching the entities to update.</param>
87+
/// <returns>An update builder.</returns>
88+
public static UpdateBuilder<TSource> UpdateBuilder<TSource>(this IQueryable<TSource> source)
89+
{
90+
return new UpdateBuilder<TSource>(source);
91+
}
92+
93+
internal static int ExecuteUpdate<TSource>(this IQueryable<TSource> source, Expression updateExpression, bool versioned)
94+
{
95+
var provider = source.GetNhProvider();
96+
return provider.ExecuteDml<TSource>(versioned ? QueryMode.UpdateVersioned : QueryMode.Update, updateExpression);
97+
}
98+
99+
/// <summary>
100+
/// Insert all entities selected by the specified query. The insert operation is performed in the database without reading the entities out of it.
101+
/// </summary>
102+
/// <typeparam name="TSource">The type of the elements of <paramref name="source" />.</typeparam>
103+
/// <typeparam name="TTarget">The type of the entities to insert.</typeparam>
104+
/// <param name="source">The query matching entities source of the data to insert.</param>
105+
/// <param name="expression">The expression projecting a source entity to the entity to insert.</param>
106+
/// <returns>The number of inserted entities.</returns>
107+
public static int InsertInto<TSource, TTarget>(this IQueryable<TSource> source, Expression<Func<TSource, TTarget>> expression)
108+
{
109+
return ExecuteInsert<TSource, TTarget>(source, DmlExpressionRewriter.PrepareExpression(source.Expression, expression));
110+
}
111+
112+
/// <summary>
113+
/// Insert all entities selected by the specified query, using an anonymous initializer for specifying setters. <typeparamref name="TTarget"/>
114+
/// must be explicitly provided, e.g. <c>source.InsertInto&lt;Cat, Dog&gt;(c => new {...})</c>. The insert operation is performed in the
115+
/// database without reading the entities out of it.
116+
/// </summary>
117+
/// <typeparam name="TSource">The type of the elements of <paramref name="source" />.</typeparam>
118+
/// <typeparam name="TTarget">The type of the entities to insert. Must be explicitly provided.</typeparam>
119+
/// <param name="source">The query matching entities source of the data to insert.</param>
120+
/// <param name="expression">The expression projecting a source entity to an anonymous object representing
121+
/// the entity to insert.</param>
122+
/// <returns>The number of inserted entities.</returns>
123+
public static int InsertInto<TSource, TTarget>(this IQueryable<TSource> source, Expression<Func<TSource, object>> expression)
124+
{
125+
return ExecuteInsert<TSource, TTarget>(source, DmlExpressionRewriter.PrepareExpressionFromAnonymous(source.Expression, expression));
126+
}
127+
128+
/// <summary>
129+
/// Initiate an insert using selected entities as a source. Return
130+
/// a builder allowing to set properties to insert and allowing to execute the update.
131+
/// </summary>
132+
/// <typeparam name="TSource">The type of the elements of <paramref name="source" />.</typeparam>
133+
/// <param name="source">The query matching the entities to update.</param>
134+
/// <returns>An update builder.</returns>
135+
public static InsertBuilder<TSource> InsertBuilder<TSource>(this IQueryable<TSource> source)
136+
{
137+
return new InsertBuilder<TSource>(source);
138+
}
139+
140+
internal static int ExecuteInsert<TSource, TTarget>(this IQueryable<TSource> source, Expression insertExpression)
141+
{
142+
var provider = source.GetNhProvider();
143+
return provider.ExecuteDml<TTarget>(QueryMode.Insert, insertExpression);
144+
}
145+
}
146+
}

src/NHibernate/Linq/InsertBuilder.cs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
using System;
2+
using System.Linq;
3+
using System.Linq.Expressions;
4+
5+
namespace NHibernate.Linq
6+
{
7+
/// <summary>
8+
/// An insert builder on which entities to insert can be specified.
9+
/// </summary>
10+
/// <typeparam name="TSource">The type of the entities selected as source of the insert.</typeparam>
11+
public class InsertBuilder<TSource>
12+
{
13+
private readonly IQueryable<TSource> _source;
14+
15+
internal InsertBuilder(IQueryable<TSource> source)
16+
{
17+
_source = source;
18+
}
19+
20+
/// <summary>
21+
/// Specifies the type of the entities to insert, and return an insert builder allowing to specify the values to insert.
22+
/// </summary>
23+
/// <typeparam name="TTarget">The type of the entities to insert.</typeparam>
24+
/// <returns>An insert builder.</returns>
25+
public InsertBuilder<TSource, TTarget> Into<TTarget>()
26+
{
27+
return new InsertBuilder<TSource, TTarget>(_source);
28+
}
29+
}
30+
31+
/// <summary>
32+
/// An insert builder on which entities to insert can be specified.
33+
/// </summary>
34+
/// <typeparam name="TSource">The type of the entities selected as source of the insert.</typeparam>
35+
/// <typeparam name="TTarget">The type of the entities to insert.</typeparam>
36+
public class InsertBuilder<TSource, TTarget>
37+
{
38+
private readonly IQueryable<TSource> _source;
39+
private readonly Assignments<TSource, TTarget> _assignments = new Assignments<TSource, TTarget>();
40+
41+
internal InsertBuilder(IQueryable<TSource> source)
42+
{
43+
_source = source;
44+
}
45+
46+
/// <summary>
47+
/// Set the specified property value and return this builder.
48+
/// </summary>
49+
/// <typeparam name="TProp">The type of the property.</typeparam>
50+
/// <param name="property">The property.</param>
51+
/// <param name="expression">The expression that should be assigned to the property.</param>
52+
/// <returns>This insert builder.</returns>
53+
public InsertBuilder<TSource, TTarget> Value<TProp>(Expression<Func<TTarget, TProp>> property, Expression<Func<TSource, TProp>> expression)
54+
{
55+
_assignments.Set(property, expression);
56+
return this;
57+
}
58+
59+
/// <summary>
60+
/// Set the specified property value and return this builder.
61+
/// </summary>
62+
/// <typeparam name="TProp">The type of the property.</typeparam>
63+
/// <param name="property">The property.</param>
64+
/// <param name="value">The value.</param>
65+
/// <returns>This insert builder.</returns>
66+
public InsertBuilder<TSource, TTarget> Value<TProp>(Expression<Func<TTarget, TProp>> property, TProp value)
67+
{
68+
_assignments.Set(property, value);
69+
return this;
70+
}
71+
72+
/// <summary>
73+
/// Insert the entities. The insert operation is performed in the database without reading the entities out of it. Will use
74+
/// <c>INSERT INTO [...] SELECT FROM [...]</c> in the database.
75+
/// </summary>
76+
/// <returns>The number of inserted entities.</returns>
77+
public int Insert()
78+
{
79+
return _source.ExecuteInsert<TSource, TTarget>(DmlExpressionRewriter.PrepareExpression<TSource>(_source.Expression, _assignments.List));
80+
}
81+
}
82+
}

src/NHibernate/Linq/InsertSyntax.cs

Lines changed: 0 additions & 65 deletions
This file was deleted.

0 commit comments

Comments
 (0)