Skip to content

Commit d6a4859

Browse files
nkreipkemaca88
andauthored
Add special case for constants contained within Convert nodes (#2643)
Fixes #2642 Co-authored-by: maca88 <bostjan.markezic@siol.net>
1 parent a2d3122 commit d6a4859

File tree

3 files changed

+177
-38
lines changed

3 files changed

+177
-38
lines changed

src/NHibernate.Test/Async/Linq/ParameterTests.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -616,6 +616,64 @@ public async Task UsingParameterInEvaluatableExpressionAsync()
616616
await (db.Users.Where(x => names.Length == 0 || names.Contains(x.Name)).ToListAsync());
617617
}
618618

619+
[Test]
620+
public async Task UsingParameterWithImplicitOperatorAsync()
621+
{
622+
var id = new GuidImplicitWrapper(new Guid("{356E4A7E-B027-4321-BA40-E2677E6502CF}"));
623+
Assert.That(await (db.Shippers.Where(o => o.Reference == id).ToListAsync()), Has.Count.EqualTo(1));
624+
625+
id = new GuidImplicitWrapper(new Guid("{356E4A7E-B027-4321-BA40-E2677E6502FF}"));
626+
Assert.That(await (db.Shippers.Where(o => o.Reference == id).ToListAsync()), Is.Empty);
627+
628+
await (AssertTotalParametersAsync(
629+
db.Shippers.Where(o => o.Reference == id && id == o.Reference),
630+
1));
631+
}
632+
633+
private struct GuidImplicitWrapper
634+
{
635+
public readonly Guid Id;
636+
637+
public GuidImplicitWrapper(Guid id)
638+
{
639+
Id = id;
640+
}
641+
642+
public static implicit operator Guid(GuidImplicitWrapper idWrapper)
643+
{
644+
return idWrapper.Id;
645+
}
646+
}
647+
648+
[Test]
649+
public async Task UsingParameterWithExplicitOperatorAsync()
650+
{
651+
var id = new GuidExplicitWrapper(new Guid("{356E4A7E-B027-4321-BA40-E2677E6502CF}"));
652+
Assert.That(await (db.Shippers.Where(o => o.Reference == (Guid) id).ToListAsync()), Has.Count.EqualTo(1));
653+
654+
id = new GuidExplicitWrapper(new Guid("{356E4A7E-B027-4321-BA40-E2677E6502FF}"));
655+
Assert.That(await (db.Shippers.Where(o => o.Reference == (Guid) id).ToListAsync()), Is.Empty);
656+
657+
await (AssertTotalParametersAsync(
658+
db.Shippers.Where(o => o.Reference == (Guid) id && (Guid) id == o.Reference),
659+
1));
660+
}
661+
662+
private struct GuidExplicitWrapper
663+
{
664+
public readonly Guid Id;
665+
666+
public GuidExplicitWrapper(Guid id)
667+
{
668+
Id = id;
669+
}
670+
671+
public static explicit operator Guid(GuidExplicitWrapper idWrapper)
672+
{
673+
return idWrapper.Id;
674+
}
675+
}
676+
619677
[Test]
620678
public async Task UsingParameterOnSelectorsAsync()
621679
{

src/NHibernate.Test/Linq/ParameterTests.cs

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,64 @@ public void UsingParameterInEvaluatableExpression()
604604
db.Users.Where(x => names.Length == 0 || names.Contains(x.Name)).ToList();
605605
}
606606

607+
[Test]
608+
public void UsingParameterWithImplicitOperator()
609+
{
610+
var id = new GuidImplicitWrapper(new Guid("{356E4A7E-B027-4321-BA40-E2677E6502CF}"));
611+
Assert.That(db.Shippers.Where(o => o.Reference == id).ToList(), Has.Count.EqualTo(1));
612+
613+
id = new GuidImplicitWrapper(new Guid("{356E4A7E-B027-4321-BA40-E2677E6502FF}"));
614+
Assert.That(db.Shippers.Where(o => o.Reference == id).ToList(), Is.Empty);
615+
616+
AssertTotalParameters(
617+
db.Shippers.Where(o => o.Reference == id && id == o.Reference),
618+
1);
619+
}
620+
621+
private struct GuidImplicitWrapper
622+
{
623+
public readonly Guid Id;
624+
625+
public GuidImplicitWrapper(Guid id)
626+
{
627+
Id = id;
628+
}
629+
630+
public static implicit operator Guid(GuidImplicitWrapper idWrapper)
631+
{
632+
return idWrapper.Id;
633+
}
634+
}
635+
636+
[Test]
637+
public void UsingParameterWithExplicitOperator()
638+
{
639+
var id = new GuidExplicitWrapper(new Guid("{356E4A7E-B027-4321-BA40-E2677E6502CF}"));
640+
Assert.That(db.Shippers.Where(o => o.Reference == (Guid) id).ToList(), Has.Count.EqualTo(1));
641+
642+
id = new GuidExplicitWrapper(new Guid("{356E4A7E-B027-4321-BA40-E2677E6502FF}"));
643+
Assert.That(db.Shippers.Where(o => o.Reference == (Guid) id).ToList(), Is.Empty);
644+
645+
AssertTotalParameters(
646+
db.Shippers.Where(o => o.Reference == (Guid) id && (Guid) id == o.Reference),
647+
1);
648+
}
649+
650+
private struct GuidExplicitWrapper
651+
{
652+
public readonly Guid Id;
653+
654+
public GuidExplicitWrapper(Guid id)
655+
{
656+
Id = id;
657+
}
658+
659+
public static explicit operator Guid(GuidExplicitWrapper idWrapper)
660+
{
661+
return idWrapper.Id;
662+
}
663+
}
664+
607665
[Test]
608666
public void UsingParameterOnSelectors()
609667
{

src/NHibernate/Linq/Visitors/ExpressionParameterVisitor.cs

Lines changed: 61 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -135,49 +135,72 @@ protected override Expression VisitConstant(ConstantExpression expression)
135135
{
136136
if (!_parameters.ContainsKey(expression) && !typeof(IQueryable).IsAssignableFrom(expression.Type) && !IsNullObject(expression))
137137
{
138-
// We use null for the type to indicate that the caller should let HQL figure it out.
139-
object value = expression.Value;
140-
IType type = null;
141-
142-
// We have a bit more information about the null parameter value.
143-
// Figure out a type so that HQL doesn't break on the null. (Related to NH-2430)
144-
// In v5.3 types are calculated by ParameterTypeLocator, this logic is only for back compatibility.
145-
// TODO 6.0: Remove
146-
if (expression.Value == null)
147-
type = NHibernateUtil.GuessType(expression.Type);
148-
149-
// Constant characters should be sent as strings
150-
// TODO 6.0: Remove
151-
if (_queryVariables == null && expression.Type == typeof(char))
152-
{
153-
value = value.ToString();
154-
}
155-
156-
// There is more information available in the Linq expression than to HQL directly.
157-
// In some cases it might be advantageous to use the extra info. Assuming this
158-
// comes up, it would be nice to combine the HQL parameter type determination code
159-
// and the Expression information.
160-
161-
NamedParameter parameter = null;
162-
if (_queryVariables != null &&
163-
_queryVariables.TryGetValue(expression, out var variable) &&
164-
!_variableParameters.TryGetValue(variable, out parameter))
165-
{
166-
parameter = CreateParameter(expression, value, type);
167-
_variableParameters.Add(variable, parameter);
168-
}
138+
AddConstantExpressionParameter(expression, null);
139+
}
169140

170-
if (parameter == null)
171-
{
172-
parameter = CreateParameter(expression, value, type);
173-
}
141+
return base.VisitConstant(expression);
142+
}
174143

175-
_parameters.Add(expression, parameter);
144+
protected override Expression VisitUnary(UnaryExpression node)
145+
{
146+
// If we have an expression like "Convert(<constant>)" we do not want to lose the conversion operation
147+
// because it might be necessary if the types are incompatible with each other, which might happen if
148+
// the expression uses an implicitly or explicitly defined cast operator.
149+
if (node.NodeType == ExpressionType.Convert &&
150+
node.Method != null && // The implicit/explicit operator method
151+
node.Operand is ConstantExpression constantExpression)
152+
{
153+
// Instead of getting constantExpression.Value, we override the value by compiling and executing this subtree,
154+
// performing the cast.
155+
var lambda = Expression.Lambda<Func<object>>(Expression.Convert(node, typeof(object)));
156+
var compiledLambda = lambda.Compile();
176157

177-
return base.VisitConstant(expression);
158+
AddConstantExpressionParameter(constantExpression, compiledLambda());
178159
}
179160

180-
return base.VisitConstant(expression);
161+
return base.VisitUnary(node);
162+
}
163+
164+
private void AddConstantExpressionParameter(ConstantExpression expression, object overrideValue)
165+
{
166+
// We use null for the type to indicate that the caller should let HQL figure it out.
167+
object value = overrideValue ?? expression.Value;
168+
IType type = null;
169+
170+
// We have a bit more information about the null parameter value.
171+
// Figure out a type so that HQL doesn't break on the null. (Related to NH-2430)
172+
// In v5.3 types are calculated by ParameterTypeLocator, this logic is only for back compatibility.
173+
// TODO 6.0: Remove
174+
if (value == null)
175+
type = NHibernateUtil.GuessType(expression.Type);
176+
177+
// Constant characters should be sent as strings
178+
// TODO 6.0: Remove
179+
if (_queryVariables == null && expression.Type == typeof(char))
180+
{
181+
value = value.ToString();
182+
}
183+
184+
// There is more information available in the Linq expression than to HQL directly.
185+
// In some cases it might be advantageous to use the extra info. Assuming this
186+
// comes up, it would be nice to combine the HQL parameter type determination code
187+
// and the Expression information.
188+
189+
NamedParameter parameter = null;
190+
if (_queryVariables != null &&
191+
_queryVariables.TryGetValue(expression, out var variable) &&
192+
!_variableParameters.TryGetValue(variable, out parameter))
193+
{
194+
parameter = CreateParameter(expression, value, type);
195+
_variableParameters.Add(variable, parameter);
196+
}
197+
198+
if (parameter == null)
199+
{
200+
parameter = CreateParameter(expression, value, type);
201+
}
202+
203+
_parameters.Add(expression, parameter);
181204
}
182205

183206
private NamedParameter CreateParameter(ConstantExpression expression, object value, IType type)

0 commit comments

Comments
 (0)