Skip to content

NH-2401 - Method for specifying IType of LINQ parameter #346

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all 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
78 changes: 78 additions & 0 deletions src/NHibernate.Test/Linq/LinqQuerySamples.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
using System.Collections.Generic;
using System.Linq;
using NHibernate.DomainModel.Northwind.Entities;
using NHibernate.Exceptions;
using NHibernate.Linq;
using NUnit.Framework;

namespace NHibernate.Test.Linq
Expand Down Expand Up @@ -1620,6 +1622,82 @@ public void DLinq2C()

Assert.That(!q.Any(orderid => withNullShippingDate.Contains(orderid)));
}

[Test]
public void CanSpecifyParameterTypeOnRestriction()
{
//NH-2401
using (this.session.BeginTransaction())
{
//this should work perfectly, even if doesn't return anything (sanity check)
(from o in this.db.Orders where o.ShippingDate == DateTime.Now select o).FirstOrDefault();

//this one should throw an exception because of invalid case
//note: cannot use Assert.Throws because we need to look at the actual exception
try
{
(from o in this.db.Orders where o.ShippingDate.MappedAs(NHibernateUtil.String) == DateTime.Now select o).FirstOrDefault();
}
catch (GenericADOException ex)
{
Assert.IsInstanceOf<InvalidCastException>(ex.InnerException);
Assert.AreEqual("Unable to cast object of type 'System.DateTime' to type 'System.String'.", ex.InnerException.Message);
}
}
}

[Test]
public void CanSpecifyParameterTypeAndToStringOnRestriction()
{
//NH-2401
using (this.session.BeginTransaction())
{
var firstDate = (from o in this.db.Orders where o.ShippingDate != null orderby o.ShippingDate select o.ShippingDate.ToString()).First();
var firstDateConverted = (from o in this.db.Orders where o.ShippingDate.ToString() == firstDate.MappedAs(NHibernateUtil.String) select o.ShippingDate).FirstOrDefault();

Assert.AreEqual(DateTime.Parse(firstDate), firstDateConverted);
}
}


[Test]
public void CanSpecifyParameterTypeAndConvertToIntOnRestriction()
{
//NH-2401
using (this.session.BeginTransaction())
{
var firstIdAsInt = (from o in this.db.Orders where o.OrderDate != null orderby o.OrderDate select Convert.ToInt32(o.OrderDate)).First();
var firstIdConverted = (from o in this.db.Orders where Convert.ToInt32(o.OrderDate.MappedAs(NHibernateUtil.Int32)) == firstIdAsInt orderby o.OrderDate select Convert.ToInt32(o.OrderDate)).Single();

Assert.AreEqual(firstIdAsInt, firstIdConverted);
}
}

[Test]
public void CanSpecifyParameterTypeAndConvertToDecimalOnRestriction()
{
//NH-2401
using (this.session.BeginTransaction())
{
var firstIdAsDecimal = Convert.ToDecimal((from o in this.db.Orders orderby o.OrderId select o.OrderId).First());
var firstIdConverted = (from o in this.db.Orders where o.OrderId.ToString() == firstIdAsDecimal.MappedAs(NHibernateUtil.Decimal).ToString() orderby o.OrderId select Convert.ToDecimal(o.OrderId)).Single();

Assert.AreEqual(firstIdAsDecimal, firstIdConverted);
}
}

[Test]
public void CanSpecifyParameterTypeAndConvertToDoubleOnRestriction()
{
//NH-2401
using (this.session.BeginTransaction())
{
var firstIdAsDouble = Convert.ToDouble((from o in this.db.Orders orderby o.OrderId select o.OrderId).First());
var firstIdConverted = (from o in this.db.Orders where Convert.ToDouble(o.OrderId.MappedAs(NHibernateUtil.Double)) == firstIdAsDouble orderby o.OrderId select Convert.ToDouble(o.OrderId)).Single();

Assert.AreEqual(firstIdAsDouble, firstIdConverted);
}
}
}

public class ParentChildBatch<T, TKey, TSub>
Expand Down
11 changes: 7 additions & 4 deletions src/NHibernate/Linq/Functions/ConvertGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.ObjectModel;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using NHibernate.Hql.Ast;
using NHibernate.Linq.Visitors;
using System.Collections.ObjectModel;

namespace NHibernate.Linq.Functions
{
public abstract class ConvertToGenerator<T> : BaseHqlGeneratorForMethod
{
public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
var mce = targetObject as MethodCallExpression;
if (mce != null)
{
return treeBuilder.Cast(visitor.Visit(mce.Arguments[0]).AsExpression(), typeof(T));
}

return treeBuilder.Cast(visitor.Visit(arguments[0]).AsExpression(), typeof(T));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@ public DefaultLinqToHqlGeneratorsRegistry()
this.Merge(new CollectionContainsGenerator());

this.Merge(new DateTimePropertiesHqlGenerator());

//NH-2401
this.Merge(new MappedAsGenerator());
}

protected bool GetRuntimeMethodGenerator(MethodInfo method, out IHqlGeneratorForMethod methodGenerator)
Expand Down
26 changes: 26 additions & 0 deletions src/NHibernate/Linq/Functions/MappedAsGenerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq.Expressions;
using System.Reflection;
using NHibernate.Hql.Ast;
using NHibernate.Linq;
using NHibernate.Linq.Visitors;
using NHibernate.Type;

namespace NHibernate.Linq.Functions
{
public class MappedAsGenerator : BaseHqlGeneratorForMethod
{
public MappedAsGenerator()
{
SupportedMethods = new[] { ReflectionHelper.GetMethodDefinition<object>(x => x.MappedAs(null)) };
}

public override HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
var result = visitor.Visit(arguments[0]).AsExpression();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see traces of debugging here...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add the logic here?

return result;
}
}
}
6 changes: 6 additions & 0 deletions src/NHibernate/Linq/Functions/StringGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,12 @@ public IEnumerable<MethodInfo> SupportedMethods

public HqlTreeNode BuildHql(MethodInfo method, Expression targetObject, ReadOnlyCollection<Expression> arguments, HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor)
{
var mce = targetObject as MethodCallExpression;
if (mce != null)
{
return treeBuilder.MethodCall("str", visitor.Visit(mce.Arguments[0]).AsExpression());
}

return treeBuilder.MethodCall("str", visitor.Visit(targetObject).AsExpression());
}
}
Expand Down
6 changes: 6 additions & 0 deletions src/NHibernate/Linq/LinqExtensionMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ namespace NHibernate.Linq
{
public static class LinqExtensionMethods
{
public static T MappedAs<T>(this T parameter, NHibernate.Type.IType type)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This random thought: can we have different MappedAs for all IType which can make sense, instead of generic one?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like this:

public static decimal MappedAs(this decimal parameter, NHibernate.Type.IType type)

?
Sure... it may make sense... want me to update the pull request?
BTW, not related, but why don't we also support, at least, Convert.ToDateTime() in a LINQ query?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hooray, you've learnt how to use line comments... sorry, just trying to joke.

Like this:
public static decimal MappedAs(this decimal parameter, NHibernate.Type.IType type)

Probably

public static decimal MappedAs(this decimal parameter, NHibernate.Type.DecimalType type)

But I see now, that this is a bad idea.

but why don't we also support, at least, Convert.ToDateTime() in a LINQ query?

Just because (c) my 4yo. Just because we forgot to support it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a specific type, we need to allow passing arbitrary types! BTW, why is that a bad idea?

{
//NH-2401
return parameter;
}

public static IQueryable<T> Query<T>(this ISession session)
{
return new NhQueryable<T>(session.GetSessionImplementation());
Expand Down
33 changes: 29 additions & 4 deletions src/NHibernate/Linq/Visitors/ExpressionParameterVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ namespace NHibernate.Linq.Visitors
/// </summary>
public class ExpressionParameterVisitor : ExpressionTreeVisitor
{
//NH-2401
private readonly Dictionary<int, IType> parameterTypeOverrides = new Dictionary<int, IType>();
private readonly Dictionary<ConstantExpression, NamedParameter> _parameters = new Dictionary<ConstantExpression, NamedParameter>();
private readonly ISessionFactoryImplementor _sessionFactory;

Expand Down Expand Up @@ -60,6 +62,25 @@ protected override Expression VisitMethodCallExpression(MethodCallExpression exp
return Expression.Call(null, expression.Method, query, arg);
}

/*if (expression.Method == ReflectionHelper.GetMethod<object>(x => x.ToString()))
{
//NH-2401: detect ToString after MappedAs
//we just leave thos to StringGenerator
return base.VisitMethodCallExpression(expression);
}*/

if ((expression.Method.DeclaringType == typeof(LinqExtensionMethods)) && (expression.Method.Name == "MappedAs"))
{
//NH-2401: detect MappedAs
//we cannot do this in a *Generator class because there we don't have access to the parameters collection (_parameters)
var typeExpression = Expression.Lambda<Func<IType>>(Expression.Convert(expression.Arguments[1], typeof(IType)));
var type = typeExpression.Compile()();

this.parameterTypeOverrides[_parameters.Count] = type;

return expression;
}

if (VisitorUtil.IsDynamicComponentDictionaryGetter(expression, _sessionFactory))
{
return expression;
Expand All @@ -75,10 +96,14 @@ protected override Expression VisitConstantExpression(ConstantExpression express
// We use null for the type to indicate that the caller should let HQL figure it out.
IType type = null;

// We have a bit more information about the null parameter value.
// Figure out a type so that HQL doesn't break on the null. (Related to NH-2430)
if (expression.Value == null)
type = NHibernateUtil.GuessType(expression.Type);
//NH-2401
if (this.parameterTypeOverrides.TryGetValue(this._parameters.Count, out type) == false)
{
// We have a bit more information about the null parameter value.
// Figure out a type so that HQL doesn't break on the null. (Related to NH-2430)
if (expression.Value == null)
type = NHibernateUtil.GuessType(expression.Type);
}

// There is more information available in the Linq expression than to HQL directly.
// In some cases it might be advantageous to use the extra info. Assuming this
Expand Down
1 change: 1 addition & 0 deletions src/NHibernate/NHibernate.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,7 @@
<Compile Include="Linq\ExpressionTransformers\RemoveRedundantCast.cs" />
<Compile Include="Linq\Functions\ConvertGenerator.cs" />
<Compile Include="Linq\Functions\GetValueOrDefaultGenerator.cs" />
<Compile Include="Linq\Functions\MappedAsGenerator.cs" />
<Compile Include="Linq\Functions\MathGenerator.cs" />
<Compile Include="Linq\Functions\DictionaryGenerator.cs" />
<Compile Include="Linq\Functions\EqualsGenerator.cs" />
Expand Down