Skip to content

Commit fe28985

Browse files
hazzikfredericDelaporte
authored andcommitted
NH-3488 - Refactor assignment
1 parent 59bee52 commit fe28985

File tree

10 files changed

+117
-96
lines changed

10 files changed

+117
-96
lines changed

doc/reference/modules/query_linq.xml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -544,9 +544,9 @@ IList<Cat> oldCats =
544544
In both cases, unspecified properties are not included in the resulting SQL update. This could
545545
be changed for <link linkend="mapping-declaration-version"><literal>version</literal></link> and
546546
<link linkend="mapping-declaration-timestamp"><literal>timestamp</literal></link> properties:
547-
<literal>As</literal> and <literal>Assign</literal> methods take an optional boolean parameter,
548-
<literal>versioned</literal>, which allows incrementing the version. Custom version types
549-
(<literal>NHibernate.Usertype.IUserVersionType</literal>) are not supported.
547+
using <literal>UpdateVersioned</literal> instead of <literal>Update</literal> allows incrementing
548+
the version. Custom version types (<literal>NHibernate.Usertype.IUserVersionType</literal>) are
549+
not supported.
550550
</para>
551551
</sect2>
552552

src/NHibernate.Test/LinqBulkManipulation/Fixture.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -542,7 +542,7 @@ public void IncrementCounterVersion()
542542
// Note: Update more than one column to showcase NH-3624, which involved losing some columns. /2014-07-26
543543
var count =
544544
s.Query<IntegerVersioned>()
545-
.Update().Assign(x => x.Set(y => y.Name, y => y.Name + "upd").Set(y => y.Data, y => y.Data + "upd"), true);
545+
.UpdateVersioned().Assign(x => x.Set(y => y.Name, y => y.Name + "upd").Set(y => y.Data, y => y.Data + "upd"));
546546
Assert.That(count, Is.EqualTo(1), "incorrect exec count");
547547
t.Commit();
548548
}
@@ -571,7 +571,7 @@ public void IncrementTimestampVersion()
571571
{
572572
// Note: Update more than one column to showcase NH-3624, which involved losing some columns. /2014-07-26
573573
var count = s.Query<TimestampVersioned>()
574-
.Update().Assign(x => x.Set(y => y.Name, y => y.Name + "upd").Set(y => y.Data, y => y.Data + "upd"), true);
574+
.UpdateVersioned().Assign(x => x.Set(y => y.Name, y => y.Name + "upd").Set(y => y.Data, y => y.Data + "upd"));
575575
Assert.That(count, Is.EqualTo(1), "incorrect exec count");
576576
t.Commit();
577577
}

src/NHibernate/Linq/Assignment.cs

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
1+
using System.Collections.Generic;
2+
using System.Linq;
13
using System.Linq.Expressions;
4+
using System.Reflection;
5+
using NHibernate.Linq.Visitors;
6+
using NHibernate.Util;
27

38
namespace NHibernate.Linq
49
{
@@ -10,16 +15,55 @@ public class Assignment
1015
/// <summary>
1116
/// The assigned property.
1217
/// </summary>
13-
public string PropertyPath { get; set; }
18+
public string PropertyPath { get; }
1419
/// <summary>
1520
/// The value to assign.
1621
/// </summary>
17-
public Expression Expression { get; set; }
22+
public Expression Expression { get; }
1823

1924
public Assignment(string propertyPath, Expression expression)
2025
{
2126
PropertyPath = propertyPath;
2227
Expression = expression;
2328
}
29+
30+
public static readonly ConstructorInfo DictionaryConstructorInfo = typeof(Dictionary<string, object>).GetConstructor(new[] { typeof(int) });
31+
public static readonly MethodInfo DictionaryAddMethodInfo = ReflectHelper.GetMethod<Dictionary<string, object>>(d => d.Add(null, null));
32+
33+
/// <summary>
34+
/// Converts the assignments into a lambda expression, which creates a Dictionary&lt;string,object%gt;.
35+
/// </summary>
36+
/// <param name="assignments"></param>
37+
/// <returns>A lambda expression representing the assignments.</returns>
38+
public static LambdaExpression ConvertAssignmentsToDictionaryExpression<TSource>(IReadOnlyCollection<Assignment> assignments)
39+
{
40+
var param = Expression.Parameter(typeof(TSource));
41+
var inits = new List<ElementInit>();
42+
foreach (var set in assignments)
43+
{
44+
var setter = set.Expression;
45+
if (setter is LambdaExpression setterLambda)
46+
{
47+
setter = setterLambda.Body.Replace(setterLambda.Parameters.First(), param);
48+
}
49+
inits.Add(Expression.ElementInit(
50+
DictionaryAddMethodInfo, Expression.Constant(set.PropertyPath),
51+
Expression.Convert(setter, typeof(object))));
52+
}
53+
54+
//The ListInit is intentionally "infected" with the lambda parameter (param), in the form of an IIF.
55+
//The only relevance is to make sure that the ListInit is not evaluated by the PartialEvaluatingExpressionTreeVisitor,
56+
//which could turn it into a Constant
57+
var listInit = Expression.ListInit(
58+
Expression.New(
59+
DictionaryConstructorInfo,
60+
Expression.Condition(
61+
Expression.Equal(param, Expression.Constant(null, typeof(TSource))),
62+
Expression.Constant(assignments.Count),
63+
Expression.Constant(assignments.Count))),
64+
inits);
65+
66+
return Expression.Lambda(listInit, param);
67+
}
2468
}
25-
}
69+
}

src/NHibernate/Linq/Assignments.cs

Lines changed: 20 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -3,27 +3,21 @@
33
using System.Collections.ObjectModel;
44
using System.Linq;
55
using System.Linq.Expressions;
6-
using System.Reflection;
76
using NHibernate.Linq.Visitors;
8-
using NHibernate.Util;
97

108
namespace NHibernate.Linq
119
{
12-
public abstract class Assignments
13-
{
14-
protected static readonly ConstructorInfo DictionaryConstructorInfo = typeof(Dictionary<string, object>).GetConstructor(new[] { typeof(int) });
15-
protected static readonly MethodInfo DictionaryAddMethodInfo = ReflectHelper.GetMethod<Dictionary<string, object>>(d => d.Add(null, null));
16-
}
17-
1810
/// <summary>
1911
/// Class to hold assignments used in updates and inserts.
2012
/// </summary>
2113
/// <typeparam name="TSource">The type of the entity source of the insert or to update.</typeparam>
2214
/// <typeparam name="TTarget">The type of the entity to insert or to update.</typeparam>
23-
public class Assignments<TSource, TTarget> : Assignments
15+
public class Assignments<TSource, TTarget>
2416
{
2517
private readonly List<Assignment> _sets = new List<Assignment>();
2618

19+
internal IReadOnlyCollection<Assignment> List => _sets;
20+
2721
/// <summary>
2822
/// Sets the specified property.
2923
/// </summary>
@@ -64,40 +58,6 @@ private static MemberExpression GetMemberExpression<TProp>(Expression<Func<TTarg
6458
return member;
6559
}
6660

67-
/// <summary>
68-
/// Converts the assignments into a lambda expression, which creates a Dictionary&lt;string,object%gt;.
69-
/// </summary>
70-
/// <returns>A lambda expression representing the assignments.</returns>
71-
public LambdaExpression ConvertToDictionaryExpression()
72-
{
73-
var param = Expression.Parameter(typeof(TSource));
74-
var inits = new List<ElementInit>();
75-
foreach (var set in _sets)
76-
{
77-
var setter = set.Expression;
78-
if (setter is LambdaExpression setterLambda)
79-
{
80-
setter = setterLambda.Body.Replace(setterLambda.Parameters.First(), param);
81-
}
82-
inits.Add(Expression.ElementInit(DictionaryAddMethodInfo, Expression.Constant(set.PropertyPath),
83-
Expression.Convert(setter, typeof(object))));
84-
}
85-
86-
//The ListInit is intentionally "infected" with the lambda parameter (param), in the form of an IIF.
87-
//The only relevance is to make sure that the ListInit is not evaluated by the PartialEvaluatingExpressionTreeVisitor,
88-
//which could turn it into a Constant
89-
var listInit = Expression.ListInit(
90-
Expression.New(
91-
DictionaryConstructorInfo,
92-
Expression.Condition(
93-
Expression.Equal(param, Expression.Constant(null, typeof(TSource))),
94-
Expression.Constant(_sets.Count),
95-
Expression.Constant(_sets.Count))),
96-
inits);
97-
98-
return Expression.Lambda(listInit, param);
99-
}
100-
10161
/// <summary>
10262
/// Converts a members initialization expression to assignments. Unset members are ignored and left untouched.
10363
/// </summary>
@@ -107,40 +67,42 @@ public static Assignments<TSource, TTarget> FromExpression(Expression<Func<TSour
10767
{
10868
if (expression == null)
10969
throw new ArgumentNullException(nameof(expression));
110-
var instance = new Assignments<TSource, TTarget>();
11170
var memberInitExpression = expression.Body as MemberInitExpression ??
112-
throw new ArgumentException("The expression must be member initialization, e.g. x => new Dog { Name = x.Name, Age = x.Age + 5 }");
113-
114-
AddSetsFromBindings(memberInitExpression.Bindings, instance, "", expression.Parameters);
71+
throw new ArgumentException("The expression must be member initialization, e.g. x => new Dog { Name = x.Name, Age = x.Age + 5 }");
11572

73+
var instance = new Assignments<TSource, TTarget>();
74+
instance.AddSetsFromBindings(memberInitExpression.Bindings, "", expression.Parameters);
11675
return instance;
11776
}
11877

119-
private static void AddSetsFromBindings(IEnumerable<MemberBinding> bindings, Assignments<TSource, TTarget> instance, string path, ReadOnlyCollection<ParameterExpression> parameters)
78+
private void AddSetsFromBindings(IEnumerable<MemberBinding> bindings, string path, ReadOnlyCollection<ParameterExpression> parameters)
12079
{
121-
foreach (var binding in bindings)
80+
foreach (var node in bindings)
12281
{
123-
if (binding.BindingType == MemberBindingType.Assignment) // {Property="Value"}
124-
{
125-
AddSetsFromAssignment((MemberAssignment)binding, instance, path + "." + binding.Member.Name, parameters);
126-
}
127-
else if (binding.BindingType == MemberBindingType.MemberBinding) // {Property={SubProperty="Value}}
82+
switch (node.BindingType)
12883
{
129-
AddSetsFromBindings(((MemberMemberBinding)binding).Bindings, instance, path + "." + binding.Member.Name, parameters);
84+
case MemberBindingType.Assignment:
85+
AddSetsFromAssignment((MemberAssignment)node, path + "." + node.Member.Name, parameters);
86+
break;
87+
case MemberBindingType.MemberBinding:
88+
AddSetsFromBindings(((MemberMemberBinding)node).Bindings, path + "." + node.Member.Name, parameters);
89+
break;
90+
default:
91+
throw new InvalidOperationException($"{node.BindingType} is not supported");
13092
}
13193
}
13294
}
13395

134-
private static void AddSetsFromAssignment(MemberAssignment assignment, Assignments<TSource, TTarget> instance, string path, ReadOnlyCollection<ParameterExpression> parameters)
96+
private void AddSetsFromAssignment(MemberAssignment assignment, string path, ReadOnlyCollection<ParameterExpression> parameters)
13597
{
13698
// {Property=new Instance{SubProperty="Value"}}
13799
if (assignment.Expression is MemberInitExpression memberInit)
138100
{
139-
AddSetsFromBindings(memberInit.Bindings, instance, path, parameters);
101+
AddSetsFromBindings(memberInit.Bindings, path, parameters);
140102
}
141103
else
142104
{
143-
instance._sets.Add(new Assignment(path.Substring(1), Expression.Lambda(assignment.Expression, parameters)));
105+
_sets.Add(new Assignment(path.Substring(1), Expression.Lambda(assignment.Expression, parameters)));
144106
}
145107
}
146108
}

src/NHibernate/Linq/DefaultQueryProvider.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ public interface INhQueryProvider : IQueryProvider
1616
IEnumerable<TResult> ExecuteFuture<TResult>(Expression expression);
1717
IFutureValue<TResult> ExecuteFutureValue<TResult>(Expression expression);
1818
void SetResultTransformerAndAdditionalCriteria(IQuery query, NhLinqExpression nhExpression, IDictionary<string, Tuple<object, IType>> parameters);
19-
int ExecuteDelete(Expression predicate);
20-
int ExecuteUpdate<T>(Expression expression, Assignments<T, T> assignments, bool versioned);
21-
int ExecuteInsert<TInput, TOutput>(Expression expression, Assignments<TInput, TOutput> assignments);
19+
int ExecuteDelete<T>(Expression predicate);
20+
int ExecuteUpdate<TInput>(Expression expression, bool versioned, IReadOnlyCollection<Assignment> assignments);
21+
int ExecuteInsert<TInput, TOutput>(Expression expression, IReadOnlyCollection<Assignment> assignments);
2222
}
2323

2424
public class DefaultQueryProvider : INhQueryProvider
@@ -174,7 +174,7 @@ public virtual void SetResultTransformerAndAdditionalCriteria(IQuery query, NhLi
174174
}
175175
}
176176

177-
public int ExecuteDelete(Expression predicate)
177+
public int ExecuteDelete<T>(Expression predicate)
178178
{
179179
var nhLinqExpression = new NhLinqDeleteExpression(predicate, Session.Factory);
180180

@@ -185,9 +185,9 @@ public int ExecuteDelete(Expression predicate)
185185
return query.ExecuteUpdate();
186186
}
187187

188-
public int ExecuteUpdate<T>(Expression expression, Assignments<T, T> assignments, bool versioned)
188+
public int ExecuteUpdate<T>(Expression expression, bool versioned, IReadOnlyCollection<Assignment> assignments)
189189
{
190-
var nhLinqExpression = new NhLinqUpdateExpression<T>(expression, assignments, Session.Factory, versioned);
190+
var nhLinqExpression = new NhLinqUpdateExpression<T>(expression, Session.Factory, versioned, assignments);
191191

192192
var query = Session.CreateQuery(nhLinqExpression);
193193

@@ -196,9 +196,9 @@ public int ExecuteUpdate<T>(Expression expression, Assignments<T, T> assignments
196196
return query.ExecuteUpdate();
197197
}
198198

199-
public int ExecuteInsert<TSource, TTarget>(Expression expression, Assignments<TSource, TTarget> assignments)
199+
public int ExecuteInsert<TSource, TTarget>(Expression expression, IReadOnlyCollection<Assignment> assignments)
200200
{
201-
var nhLinqExpression = new NhLinqInsertExpression<TSource, TTarget>(expression, assignments, Session.Factory);
201+
var nhLinqExpression = new NhLinqInsertExpression<TSource, TTarget>(expression, Session.Factory, assignments);
202202

203203
var query = Session.CreateQuery(nhLinqExpression);
204204

@@ -207,4 +207,4 @@ public int ExecuteInsert<TSource, TTarget>(Expression expression, Assignments<TS
207207
return query.ExecuteUpdate();
208208
}
209209
}
210-
}
210+
}

src/NHibernate/Linq/InsertSyntax.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public int As<TTarget>(Expression<Func<TSource, TTarget>> expression)
4747

4848
internal int InsertInto<TTarget>(Assignments<TSource, TTarget> assignments)
4949
{
50-
return _provider.ExecuteInsert(_sourceExpression, assignments);
50+
return _provider.ExecuteInsert<TSource, TTarget>(_sourceExpression, assignments.List);
5151
}
5252
}
5353
}

src/NHibernate/Linq/LinqExtensionMethods.cs

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ public static IFutureValue<TResult> ToFutureValue<TSource, TResult>(this IQuerya
141141
public static int Delete<TSource>(this IQueryable<TSource> source)
142142
{
143143
var provider = GetNhProvider(source);
144-
return provider.ExecuteDelete(source.Expression);
144+
return provider.ExecuteDelete<TSource>(source.Expression);
145145
}
146146

147147
/// <summary>
@@ -153,7 +153,20 @@ public static int Delete<TSource>(this IQueryable<TSource> source)
153153
public static UpdateSyntax<TSource> Update<TSource>(this IQueryable<TSource> source)
154154
{
155155
var provider = GetNhProvider(source);
156-
return new UpdateSyntax<TSource>(source.Expression, provider);
156+
return new UpdateSyntax<TSource>(source.Expression, provider, false);
157+
}
158+
159+
/// <summary>
160+
/// Initiate a <c>update versioned</c> for the entities selected by the query. The update operation
161+
/// will be performed in the database without reading the entities out of it.
162+
/// </summary>
163+
/// <typeparam name="TSource">The type of the elements of <paramref name="source" />.</typeparam>
164+
/// <param name="source">The query matching the entities to update.</param>
165+
/// <returns>An update builder, allowing to specify assignments to the entities properties.</returns>
166+
public static UpdateSyntax<TSource> UpdateVersioned<TSource>(this IQueryable<TSource> source)
167+
{
168+
var provider = GetNhProvider(source);
169+
return new UpdateSyntax<TSource>(source.Expression, provider, true);
157170
}
158171

159172
/// <summary>

src/NHibernate/Linq/NhLinqInsertExpression.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Generic;
12
using System.Linq.Expressions;
23
using NHibernate.Engine;
34
using NHibernate.Util;
@@ -13,19 +14,19 @@ public class NhLinqInsertExpression<TSource, TTarget> : NhLinqExpression
1314
/// </summary>
1415
protected override System.Type TargetType => typeof(TTarget);
1516

16-
public NhLinqInsertExpression(Expression expression, Assignments<TSource, TTarget> assignments, ISessionFactoryImplementor sessionFactory)
17+
public NhLinqInsertExpression(Expression expression, ISessionFactoryImplementor sessionFactory, IReadOnlyCollection<Assignment> assignments)
1718
: base(RewriteForInsert(expression, assignments), sessionFactory)
1819
{
1920
Key = "INSERT " + Key;
2021
}
2122

22-
private static Expression RewriteForInsert(Expression expression, Assignments<TSource, TTarget> assignments)
23+
private static Expression RewriteForInsert(Expression expression, IReadOnlyCollection<Assignment> assignments)
2324
{
24-
var lambda = assignments.ConvertToDictionaryExpression();
25+
var lambda = Assignment.ConvertAssignmentsToDictionaryExpression<TSource>(assignments);
2526

2627
return Expression.Call(
2728
ReflectionCache.QueryableMethods.SelectDefinition.MakeGenericMethod(typeof(TSource), lambda.Body.Type),
2829
expression, Expression.Quote(lambda));
2930
}
3031
}
31-
}
32+
}

src/NHibernate/Linq/NhLinqUpdateExpression.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Generic;
12
using System.Linq.Expressions;
23
using NHibernate.Engine;
34
using NHibernate.Util;
@@ -15,20 +16,20 @@ public class NhLinqUpdateExpression<T> : NhLinqExpression
1516

1617
private readonly bool _versioned;
1718

18-
public NhLinqUpdateExpression(Expression expression, Assignments<T, T> assignments, ISessionFactoryImplementor sessionFactory, bool versioned)
19+
public NhLinqUpdateExpression(Expression expression, ISessionFactoryImplementor sessionFactory, bool versioned, IReadOnlyCollection<Assignment> assignments)
1920
: base(RewriteForUpdate(expression, assignments), sessionFactory)
2021
{
2122
_versioned = versioned;
22-
Key = $"UPDATE {(versioned ? "VERSIONED " : "")}{Key}";
23+
Key = (versioned ? "UPDATE VERSIONED " : "UPDATE ") + Key;
2324
}
2425

25-
private static Expression RewriteForUpdate(Expression expression, Assignments<T, T> assignments)
26+
private static Expression RewriteForUpdate(Expression expression, IReadOnlyCollection<Assignment> assignments)
2627
{
27-
var lambda = assignments.ConvertToDictionaryExpression();
28+
var lambda = Assignment.ConvertAssignmentsToDictionaryExpression<T>(assignments);
2829

2930
return Expression.Call(
3031
ReflectionCache.QueryableMethods.SelectDefinition.MakeGenericMethod(typeof(T), lambda.Body.Type),
3132
expression, Expression.Quote(lambda));
3233
}
3334
}
34-
}
35+
}

0 commit comments

Comments
 (0)