|
| 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<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 | +} |
0 commit comments