Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
using System;
using System.Linq.Expressions;
using System.Reflection;
using NHibernate.Impl;
using NUnit.Framework;
using Expression = System.Linq.Expressions.Expression;

namespace NHibernate.Test.Criteria.Lambda
{
public static class DateTimeExtensions
{
public static long ToLongDateTime(this DateTime date)
{
return Convert.ToInt64(date.ToString("yyyyMMddHHmmss"));
}
}

[TestFixture]
public class ExpressionProcessorFindValueFixture
{
private static object GetValue<T>(Expression<Func<T>> expression)
{
try
{
return ExpressionProcessor.FindValue(expression.Body);
}
catch (TargetInvocationException e)
{
throw e.InnerException;
}
}

private static int GetIntegerDate()
{
return Convert.ToInt32(DateTime.Now.ToString("yyyyMMdd"));
}

[Test]
public void GivenStaticPropertyInstanceMethodCall()
{
var actual = GetValue(() => DateTime.Now.ToString("yyyyMMdd"));
var expected = DateTime.Now.ToString("yyyyMMdd");

Assert.AreEqual(expected, actual);
}

[Test]
public void GivenStaticPropertyInstanceMultipleMethodCall()
{
var actual = GetValue(() => DateTime.Now.AddDays(365).ToString("yyyyMMdd"));
var expected = DateTime.Now.AddDays(365).ToString("yyyyMMdd");

Assert.AreEqual(expected, actual);
}

[Test]
public void GivenStaticPropertyInstanceMethodCallThenCast()
{
var actual = GetValue(() => Convert.ToInt32(DateTime.Now.AddDays(365).ToString("yyyyMMdd")));
var expected = Convert.ToInt32(DateTime.Now.AddDays(365).ToString("yyyyMMdd"));

Assert.AreEqual(expected, actual);
}

[Test]
public void GivenStaticMethodCall()
{
var actual = GetValue(() => GetIntegerDate());
var expected = GetIntegerDate();

Assert.AreEqual(expected, actual);
}

[Test]
public void GivenExtensionMethodCall()
{
var date = DateTime.Now;
var actual = GetValue(() => date.ToLongDateTime());
var expected = date.ToLongDateTime();

Assert.AreEqual(expected, actual);
}

[Test]
public void GivenNestedPropertyAccess()
{
var animal = new { Snake = new { Animal = new { Name = "Scorpion" } } };
var actual = GetValue(() => animal.Snake.Animal.Name);
var expected = animal.Snake.Animal.Name;

Assert.AreEqual(expected, actual);
}

[Test]
public void GivenGuidToStringCast()
{
var guid = Guid.NewGuid();
Expression<Func<string>> expression = () => $"{guid}";
var actual = GetValue(expression);

//Check with expression compile and invoke
var lambdaExpression = Expression.Lambda(expression).Compile();
var expected = ((dynamic) lambdaExpression.DynamicInvoke()).Invoke();

Assert.AreEqual(expected, actual);
}

[Test]
public void GivenNestedPropertyToIntegerCast()
{
var animal = new { Snake = new { Animal = new { Weight = 9.89 } } };
Expression<Func<int>> expression = () => (int) animal.Snake.Animal.Weight;
var actual = GetValue(expression);

//Check with expression compile and invoke
var lambdaExpression = Expression.Lambda(expression).Compile();
var expected = ((dynamic) lambdaExpression.DynamicInvoke()).Invoke();

Assert.AreEqual(expected, actual);
}

[Test]
public void GivenNestedPropertyToIntegerConvert()
{
var animal = new { Snake = new { Animal = new { Weight = 9.89 } } };
Expression<Func<int>> expression = () => Convert.ToInt32(animal.Snake.Animal.Weight);
var actual = GetValue(expression);

//Check with expression compile and invoke
var lambdaExpression = Expression.Lambda(expression).Compile();
var expected = ((dynamic) lambdaExpression.DynamicInvoke()).Invoke();

Assert.AreEqual(expected, actual);
}

[Test]
public void GivenNullToIntegerCastFails()
{
object value = null;
Expression<Func<int>> expression = () => (int) value;

Assert.Throws<NullReferenceException>(() => GetValue(expression));

//Check with expression compile and invoke
var lambdaExpression = Expression.Lambda(expression).Compile();

Assert.Throws<NullReferenceException>(() => ((dynamic) lambdaExpression.DynamicInvoke()).Invoke());
}

[Test]
public void GivenNullableIntegerToIntegerCastFails()
{
int? value = null;
Expression<Func<int>> expression = () => (int) value;

Assert.Throws<InvalidOperationException>(() => GetValue(expression));

//Check with expression compile and invoke
var lambdaExpression = Expression.Lambda(expression).Compile();

Assert.Throws<InvalidOperationException>(() => ((dynamic) lambdaExpression.DynamicInvoke()).Invoke());

}

[Test]
public void GivenNullableIntegerToInteger()
{
int? value = 1;
Expression<Func<int>> expression = () => (int) value;

var actual = GetValue(expression);

//Check with expression compile and invoke
var lambdaExpression = Expression.Lambda(expression).Compile();

var expected = ((dynamic) lambdaExpression.DynamicInvoke()).Invoke();

Assert.AreEqual(expected, actual);
}

[Test]
public void GivenIntegerToInteger()
{
int value = 1;
Expression<Func<int>> expression = () => (int) value;

var actual = GetValue(expression);

//Check with expression compile and invoke
var lambdaExpression = Expression.Lambda(expression).Compile();

var expected = ((dynamic) lambdaExpression.DynamicInvoke()).Invoke();

Assert.AreEqual(expected, actual);
}

[Test]
public void GivenIntegerToNullableIntegerCast()
{
int value = 12345;
Expression<Func<int?>> expression = () => value;

var actual = GetValue(expression);

//Check with expression compile and invoke
var lambdaExpression = Expression.Lambda(expression).Compile();

var expected = ((dynamic) lambdaExpression.DynamicInvoke()).Invoke();

Assert.AreEqual(expected, actual);

}

[Test]
public void GivenInt16ToIntegerCast()
{
short value = 12345;
Expression<Func<int>> expression = () => value;

var actual = GetValue(expression);

//Check with expression compile and invoke
var lambdaExpression = Expression.Lambda(expression).Compile();

var expected = ((dynamic) lambdaExpression.DynamicInvoke()).Invoke();

Assert.AreEqual(expected, actual);

}

[Test]
public void GivenNullableDecimalToDecimalCast()
{
decimal? value = 9.89m;
Expression<Func<decimal>> expression = () => (decimal) value;

var actual = GetValue(expression);

//Check with expression compile and invoke
var lambdaExpression = Expression.Lambda(expression).Compile();
var expected = ((dynamic) lambdaExpression.DynamicInvoke()).Invoke();

Assert.AreEqual(expected, actual);

}

[Test]
public void GivenStringToIntegerCastFails()
{
object value = "Abatay";
Expression<Func<int>> expression = () => (int) value;

Assert.Throws<InvalidCastException>(() => GetValue(expression));

//Check with expression compile and invoke
var lambdaExpression = Expression.Lambda(expression).Compile();

Assert.Throws<InvalidCastException>(() => ((dynamic) lambdaExpression.DynamicInvoke()).Invoke());
}

[Test]
public void GivenBooleanToCharCastFails()
{
object isTrue = true;
Expression<Func<char>> expression = () => (char) isTrue;

Assert.Throws<InvalidCastException>(() => GetValue(expression));

//Check with expression compile and invoke
var lambdaExpression = Expression.Lambda(expression).Compile();

Assert.Throws<InvalidCastException>(() => ((dynamic) lambdaExpression.DynamicInvoke()).Invoke());
}
}
}
56 changes: 40 additions & 16 deletions src/NHibernate/Impl/ExpressionProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -248,32 +248,56 @@ private static ICriterion Le(ProjectionInfo property, object value)
}

/// <summary>
/// Invoke the expression to extract its runtime value
/// Walk or Invoke expression to extract its runtime value
/// </summary>
public static object FindValue(Expression expression)
{
if (expression.NodeType == ExpressionType.Constant)
return ((ConstantExpression) expression).Value;

if (expression.NodeType == ExpressionType.MemberAccess)
switch (expression.NodeType)
{
var memberAccess = (MemberExpression) expression;
if (memberAccess.Expression == null || memberAccess.Expression.NodeType == ExpressionType.Constant)
{
var constantValue = ((ConstantExpression) memberAccess.Expression)?.Value;
var member = memberAccess.Member;
switch (member.MemberType)
case ExpressionType.Constant:
var constantExpression = (ConstantExpression) expression;
return constantExpression.Value;
case ExpressionType.MemberAccess:
var memberExpression = (MemberExpression) expression;
object getObj() => memberExpression.Expression != null ? FindValue(memberExpression.Expression) : null;

switch (memberExpression.Member.MemberType)
{
case MemberTypes.Field:
return ((FieldInfo) member).GetValue(constantValue);
return ((FieldInfo) memberExpression.Member).GetValue(getObj());
case MemberTypes.Property:
return ((PropertyInfo) member).GetValue(constantValue);
return ((PropertyInfo) memberExpression.Member).GetValue(getObj());
}
}
break;
case ExpressionType.Call:
var methodCallExpression = (MethodCallExpression) expression;
var args = methodCallExpression.Arguments.ToArray(arg => FindValue(arg));

var callingObject = methodCallExpression.Object == null ? null : FindValue(methodCallExpression.Object);
return methodCallExpression.Method.Invoke(callingObject, args);
case ExpressionType.Convert:
var unaryExpression = (UnaryExpression) expression;

if (unaryExpression.Method != null)
return unaryExpression.Method.Invoke(null, new[] { FindValue(unaryExpression.Operand) });

var toType = unaryExpression.Type;
var fromType = unaryExpression.Operand.Type;
if (toType == typeof(object) || toType == fromType || Nullable.GetUnderlyingType(toType) == fromType)
return FindValue(unaryExpression.Operand);

if (toType == Nullable.GetUnderlyingType(fromType))
{
var val = FindValue(unaryExpression.Operand);
if (val != null)
return val;
}

break;
}

var valueExpression = Expression.Lambda(expression).Compile();
object value = valueExpression.DynamicInvoke();
var lambdaExpression = Expression.Lambda(expression).Compile(true);
var value = lambdaExpression.DynamicInvoke();
return value;
}

Expand Down
13 changes: 13 additions & 0 deletions src/NHibernate/Impl/LambdaExpressionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Linq.Expressions;

namespace NHibernate.Impl
{
#if NET461
internal static class LambdaExpressionExtensions
{
public static Delegate Compile(this LambdaExpression expression, bool preferInterpretation) =>
expression.Compile(); //Concurrent Compile() call causes "Garbage Collector" suspend all threads too often
}
#endif
}
9 changes: 4 additions & 5 deletions src/NHibernate/Linq/Visitors/ExpressionParameterVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
using System.Linq.Expressions;
using System.Reflection;
using NHibernate.Engine;
using NHibernate.Impl;
using NHibernate.Linq.Functions;
using NHibernate.Param;
using NHibernate.Type;
Expand Down Expand Up @@ -150,12 +151,10 @@ protected override Expression VisitUnary(UnaryExpression node)
node.Method != null && // The implicit/explicit operator method
node.Operand is ConstantExpression constantExpression)
{
// Instead of getting constantExpression.Value, we override the value by compiling and executing this subtree,
// performing the cast.
var lambda = Expression.Lambda<Func<object>>(Expression.Convert(node, typeof(object)));
var compiledLambda = lambda.Compile();
// Instead of getting constantExpression.Value, invoke method
var value = node.Method.Invoke(null, new[] { constantExpression.Value });

AddConstantExpressionParameter(constantExpression, compiledLambda());
AddConstantExpressionParameter(constantExpression, value);
}

return base.VisitUnary(node);
Expand Down