Skip to content

Commit be382ae

Browse files
hazzikfredericDelaporte
authored andcommitted
NH-3488 - Refactoring further assignments
1 parent fe28985 commit be382ae

10 files changed

+148
-176
lines changed

src/NHibernate/Linq/Assignment.cs

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

src/NHibernate/Linq/Assignments.cs

Lines changed: 4 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Collections.ObjectModel;
43
using System.Linq;
54
using System.Linq.Expressions;
65
using NHibernate.Linq.Visitors;
@@ -14,9 +13,9 @@ namespace NHibernate.Linq
1413
/// <typeparam name="TTarget">The type of the entity to insert or to update.</typeparam>
1514
public class Assignments<TSource, TTarget>
1615
{
17-
private readonly List<Assignment> _sets = new List<Assignment>();
16+
private readonly Dictionary<string, Expression> _assignments = new Dictionary<string, Expression>();
1817

19-
internal IReadOnlyCollection<Assignment> List => _sets;
18+
internal IReadOnlyDictionary<string, Expression> List => _assignments;
2019

2120
/// <summary>
2221
/// Sets the specified property.
@@ -30,7 +29,7 @@ public Assignments<TSource, TTarget> Set<TProp>(Expression<Func<TTarget, TProp>>
3029
if (expression == null)
3130
throw new ArgumentNullException(nameof(expression));
3231
var member = GetMemberExpression(property);
33-
_sets.Add(new Assignment(member.GetMemberPath(), expression));
32+
_assignments.Add(member.GetMemberPath(), expression);
3433
return this;
3534
}
3635

@@ -44,7 +43,7 @@ public Assignments<TSource, TTarget> Set<TProp>(Expression<Func<TTarget, TProp>>
4443
public Assignments<TSource, TTarget> Set<TProp>(Expression<Func<TTarget, TProp>> property, TProp value)
4544
{
4645
var member = GetMemberExpression(property);
47-
_sets.Add(new Assignment(member.GetMemberPath(), Expression.Constant(value, typeof(TProp))));
46+
_assignments.Add(member.GetMemberPath(), Expression.Constant(value, typeof(TProp)));
4847
return this;
4948
}
5049

@@ -57,53 +56,5 @@ private static MemberExpression GetMemberExpression<TProp>(Expression<Func<TTarg
5756
throw new ArgumentException($"The property expression must refer to a property of {param.Name}({param.Type.Name})", nameof(property));
5857
return member;
5958
}
60-
61-
/// <summary>
62-
/// Converts a members initialization expression to assignments. Unset members are ignored and left untouched.
63-
/// </summary>
64-
/// <param name="expression">The expression to convert.</param>
65-
/// <returns>The corresponding assignments.</returns>
66-
public static Assignments<TSource, TTarget> FromExpression(Expression<Func<TSource, TTarget>> expression)
67-
{
68-
if (expression == null)
69-
throw new ArgumentNullException(nameof(expression));
70-
var memberInitExpression = expression.Body as MemberInitExpression ??
71-
throw new ArgumentException("The expression must be member initialization, e.g. x => new Dog { Name = x.Name, Age = x.Age + 5 }");
72-
73-
var instance = new Assignments<TSource, TTarget>();
74-
instance.AddSetsFromBindings(memberInitExpression.Bindings, "", expression.Parameters);
75-
return instance;
76-
}
77-
78-
private void AddSetsFromBindings(IEnumerable<MemberBinding> bindings, string path, ReadOnlyCollection<ParameterExpression> parameters)
79-
{
80-
foreach (var node in bindings)
81-
{
82-
switch (node.BindingType)
83-
{
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");
92-
}
93-
}
94-
}
95-
96-
private void AddSetsFromAssignment(MemberAssignment assignment, string path, ReadOnlyCollection<ParameterExpression> parameters)
97-
{
98-
// {Property=new Instance{SubProperty="Value"}}
99-
if (assignment.Expression is MemberInitExpression memberInit)
100-
{
101-
AddSetsFromBindings(memberInit.Bindings, path, parameters);
102-
}
103-
else
104-
{
105-
_sets.Add(new Assignment(path.Substring(1), Expression.Lambda(assignment.Expression, parameters)));
106-
}
107-
}
10859
}
10960
}

src/NHibernate/Linq/DefaultQueryProvider.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ public interface INhQueryProvider : IQueryProvider
1717
IFutureValue<TResult> ExecuteFutureValue<TResult>(Expression expression);
1818
void SetResultTransformerAndAdditionalCriteria(IQuery query, NhLinqExpression nhExpression, IDictionary<string, Tuple<object, IType>> parameters);
1919
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);
20+
int ExecuteUpdate<T>(Expression expression, bool versioned);
21+
int ExecuteInsert<T>(Expression expression);
2222
}
2323

2424
public class DefaultQueryProvider : INhQueryProvider
@@ -185,9 +185,9 @@ public int ExecuteDelete<T>(Expression predicate)
185185
return query.ExecuteUpdate();
186186
}
187187

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

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

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

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

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

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Linq.Expressions;
5+
using System.Reflection;
6+
using NHibernate.Linq.Visitors;
7+
using NHibernate.Util;
8+
9+
namespace NHibernate.Linq
10+
{
11+
public class DmlExpressionRewriter
12+
{
13+
static readonly ConstructorInfo DictionaryConstructorInfo = typeof(Dictionary<string, object>).GetConstructor(new[] {typeof(int)});
14+
15+
static readonly MethodInfo DictionaryAddMethodInfo = ReflectHelper.GetMethod<Dictionary<string, object>>(d => d.Add(null, null));
16+
17+
readonly IReadOnlyCollection<ParameterExpression> _parameters;
18+
readonly Dictionary<string, Expression> _assignments = new Dictionary<string, Expression>();
19+
20+
DmlExpressionRewriter(IReadOnlyCollection<ParameterExpression> parameters)
21+
{
22+
_parameters = parameters;
23+
}
24+
25+
void AddSettersFromBindings(IEnumerable<MemberBinding> bindings, string path)
26+
{
27+
foreach (var node in bindings)
28+
switch (node.BindingType)
29+
{
30+
case MemberBindingType.Assignment:
31+
AddSettersFromAssignment((MemberAssignment) node, path + "." + node.Member.Name);
32+
break;
33+
case MemberBindingType.MemberBinding:
34+
AddSettersFromBindings(((MemberMemberBinding) node).Bindings, path + "." + node.Member.Name);
35+
break;
36+
default:
37+
throw new InvalidOperationException($"{node.BindingType} is not supported");
38+
}
39+
}
40+
41+
void AddSettersFromAssignment(MemberAssignment assignment, string path)
42+
{
43+
// {Property=new Instance{SubProperty="Value"}}
44+
if (assignment.Expression is MemberInitExpression memberInit)
45+
AddSettersFromBindings(memberInit.Bindings, path);
46+
else
47+
_assignments.Add(path.Substring(1), Expression.Lambda(assignment.Expression, _parameters));
48+
}
49+
50+
/// <summary>
51+
/// Converts the assignments into a lambda expression, which creates a Dictionary&lt;string,object%gt;.
52+
/// </summary>
53+
/// <param name="assignments"></param>
54+
/// <returns>A lambda expression representing the assignments.</returns>
55+
static LambdaExpression ConvertAssignmentsToDictionaryExpression<TSource>(IReadOnlyDictionary<string, Expression> assignments)
56+
{
57+
var param = Expression.Parameter(typeof(TSource));
58+
var inits = new List<ElementInit>();
59+
foreach (var set in assignments)
60+
{
61+
var setter = set.Value;
62+
if (setter is LambdaExpression setterLambda)
63+
setter = setterLambda.Body.Replace(setterLambda.Parameters.First(), param);
64+
inits.Add(
65+
Expression.ElementInit(
66+
DictionaryAddMethodInfo,
67+
Expression.Constant(set.Key),
68+
Expression.Convert(setter, typeof(object))));
69+
}
70+
71+
//The ListInit is intentionally "infected" with the lambda parameter (param), in the form of an IIF.
72+
//The only relevance is to make sure that the ListInit is not evaluated by the PartialEvaluatingExpressionTreeVisitor,
73+
//which could turn it into a Constant
74+
var listInit = Expression.ListInit(
75+
Expression.New(
76+
DictionaryConstructorInfo,
77+
Expression.Condition(
78+
Expression.Equal(param, Expression.Constant(null, typeof(TSource))),
79+
Expression.Constant(assignments.Count),
80+
Expression.Constant(assignments.Count))),
81+
inits);
82+
83+
return Expression.Lambda(listInit, param);
84+
}
85+
86+
public static Expression PrepareExpression<TSource, TTarget>(Expression sourceExpression, Expression<Func<TSource, TTarget>> expression)
87+
{
88+
if (expression == null)
89+
throw new ArgumentNullException(nameof(expression));
90+
91+
var memberInitExpression = expression.Body as MemberInitExpression ??
92+
throw new ArgumentException("The expression must be member initialization, e.g. x => new Dog { Name = x.Name, Age = x.Age + 5 }");
93+
94+
var assignments = ExtractAssignments(expression, memberInitExpression);
95+
return PrepareExpression<TSource>(sourceExpression, assignments);
96+
}
97+
98+
public static Expression PrepareExpression<TSource>(Expression sourceExpression, IReadOnlyDictionary<string, Expression> assignments)
99+
{
100+
var lambda = ConvertAssignmentsToDictionaryExpression<TSource>(assignments);
101+
102+
return Expression.Call(
103+
ReflectionCache.QueryableMethods.SelectDefinition.MakeGenericMethod(typeof(TSource), lambda.Body.Type),
104+
sourceExpression,
105+
Expression.Quote(lambda));
106+
}
107+
108+
static Dictionary<string, Expression> ExtractAssignments<TSource, TTarget>(Expression<Func<TSource, TTarget>> expression, MemberInitExpression memberInitExpression)
109+
{
110+
var instance = new DmlExpressionRewriter(expression.Parameters);
111+
instance.AddSettersFromBindings(memberInitExpression.Bindings, "");
112+
return instance._assignments;
113+
}
114+
}
115+
}

src/NHibernate/Linq/InsertSyntax.cs

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ public int Into<TTarget>(Action<Assignments<TSource, TTarget>> assignmentActions
3030
throw new ArgumentNullException(nameof(assignmentActions));
3131
var assignments = new Assignments<TSource, TTarget>();
3232
assignmentActions.Invoke(assignments);
33-
return InsertInto(assignments);
33+
34+
return _provider.ExecuteInsert<TTarget>(DmlExpressionRewriter.PrepareExpression<TSource>(_sourceExpression, assignments.List));
3435
}
3536

3637
/// <summary>
@@ -41,13 +42,7 @@ public int Into<TTarget>(Action<Assignments<TSource, TTarget>> assignmentActions
4142
/// <returns>The number of inserted entities.</returns>
4243
public int As<TTarget>(Expression<Func<TSource, TTarget>> expression)
4344
{
44-
var assignments = Assignments<TSource, TTarget>.FromExpression(expression);
45-
return InsertInto(assignments);
46-
}
47-
48-
internal int InsertInto<TTarget>(Assignments<TSource, TTarget> assignments)
49-
{
50-
return _provider.ExecuteInsert<TSource, TTarget>(_sourceExpression, assignments.List);
45+
return _provider.ExecuteInsert<TTarget>(DmlExpressionRewriter.PrepareExpression(_sourceExpression, expression));
5146
}
5247
}
5348
}

src/NHibernate/Linq/NhLinqExpression.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ public class NhLinqExpression : IQueryExpression
3232

3333
protected virtual QueryMode QueryMode => QueryMode.Select;
3434

35-
private Expression _expression;
35+
private readonly Expression _expression;
3636
private readonly IDictionary<ConstantExpression, NamedParameter> _constantToParameterMap;
3737

3838
public NhLinqExpression(Expression expression, ISessionFactoryImplementor sessionFactory)
Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
using System.Collections.Generic;
21
using System.Linq.Expressions;
32
using NHibernate.Engine;
4-
using NHibernate.Util;
53

64
namespace NHibernate.Linq
75
{
8-
public class NhLinqInsertExpression<TSource, TTarget> : NhLinqExpression
6+
public class NhLinqInsertExpression<TTarget> : NhLinqExpression
97
{
108
protected override QueryMode QueryMode => QueryMode.Insert;
119

@@ -14,19 +12,10 @@ public class NhLinqInsertExpression<TSource, TTarget> : NhLinqExpression
1412
/// </summary>
1513
protected override System.Type TargetType => typeof(TTarget);
1614

17-
public NhLinqInsertExpression(Expression expression, ISessionFactoryImplementor sessionFactory, IReadOnlyCollection<Assignment> assignments)
18-
: base(RewriteForInsert(expression, assignments), sessionFactory)
15+
public NhLinqInsertExpression(ISessionFactoryImplementor sessionFactory, Expression expression)
16+
: base(expression, sessionFactory)
1917
{
2018
Key = "INSERT " + Key;
2119
}
22-
23-
private static Expression RewriteForInsert(Expression expression, IReadOnlyCollection<Assignment> assignments)
24-
{
25-
var lambda = Assignment.ConvertAssignmentsToDictionaryExpression<TSource>(assignments);
26-
27-
return Expression.Call(
28-
ReflectionCache.QueryableMethods.SelectDefinition.MakeGenericMethod(typeof(TSource), lambda.Body.Type),
29-
expression, Expression.Quote(lambda));
30-
}
3120
}
3221
}

0 commit comments

Comments
 (0)