Skip to content

Commit 41ef651

Browse files
mkasperskifredericDelaporte
authored andcommitted
Fix ExpressionKeyVisitor to produce unique keys for anon types (#1532)
1 parent f7dc55f commit 41ef651

File tree

4 files changed

+191
-3
lines changed

4 files changed

+191
-3
lines changed
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
using System;
2+
using System.Linq;
3+
using System.Linq.Expressions;
4+
5+
// Simulates compiler-generated, non-namespaced anonymous type with one property
6+
internal class AnonymousType1<TProp1>
7+
{
8+
public TProp1 Prop1 { get; set; }
9+
}
10+
11+
namespace NHibernate.DomainModel.NHSpecific
12+
{
13+
public class AnonymousTypeQueryExpressionProviderFromNHibernateDomainModelAssembly
14+
{
15+
private readonly TypedQueryExpressionProvider<AnonymousType1<string>> _provider
16+
= new TypedQueryExpressionProvider<AnonymousType1<string>>();
17+
18+
public System.Type GetAnonymousType()
19+
{
20+
return _provider.GetSuppliedType();
21+
}
22+
23+
public Expression GetExpressionOfMethodCall()
24+
{
25+
return _provider.GetExpressionOfMethodCall();
26+
}
27+
28+
public Expression GetExpressionOfNew()
29+
{
30+
return _provider.GetExpressionOfNew();
31+
}
32+
33+
public Expression GetExpressionOfTypeBinary()
34+
{
35+
return _provider.GetExpressionOfTypeBinary();
36+
}
37+
}
38+
39+
public class TypedQueryExpressionProvider<T> where T : new ()
40+
{
41+
public System.Type GetSuppliedType()
42+
{
43+
return typeof(T);
44+
}
45+
46+
public Expression GetExpressionOfMethodCall()
47+
{
48+
Expression<Func<object>> exp = () =>
49+
Enumerable.Empty<object>().Select(o => (T)o).ToList();
50+
51+
return exp;
52+
}
53+
54+
public Expression GetExpressionOfNew()
55+
{
56+
// adds .GetHashCode to make sure the .ToList is always of same generic type
57+
// so that the only variable part is the 'new T()'
58+
Expression<Func<object>> exp = () =>
59+
Enumerable.Empty<object>().Select(o => new T().GetHashCode()).ToList();
60+
61+
return exp;
62+
}
63+
64+
public Expression GetExpressionOfTypeBinary()
65+
{
66+
Expression<Func<object>> exp = () =>
67+
Enumerable.Empty<object>().Select(o => o is T).ToList();
68+
69+
return exp;
70+
}
71+
}
72+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
using System.Linq.Expressions;
2+
using NHibernate.DomainModel.NHSpecific;
3+
4+
// Simulates a compiler-generated, non-namespaced anonymous type with one property
5+
// Exactly the same as the one in NHibernate.DomainModel.NHSpecific
6+
internal class AnonymousType1<TProp1>
7+
{
8+
public TProp1 Prop1 { get; set; }
9+
}
10+
11+
namespace NHibernate.Test.NHSpecificTest.GH1526
12+
{
13+
// Produces an Expression that has the above AnonymousType1 embedded in it
14+
public class AnonymousTypeQueryExpressionProviderFromNHibernateTestAssembly
15+
{
16+
private readonly TypedQueryExpressionProvider<AnonymousType1<string>> _provider
17+
= new TypedQueryExpressionProvider<AnonymousType1<string>>();
18+
19+
public System.Type GetAnonymousType()
20+
{
21+
return _provider.GetSuppliedType();
22+
}
23+
24+
public Expression GetExpressionOfMethodCall()
25+
{
26+
return _provider.GetExpressionOfMethodCall();
27+
}
28+
29+
public Expression GetExpressionOfNew()
30+
{
31+
return _provider.GetExpressionOfNew();
32+
}
33+
34+
public Expression GetExpressionOfTypeBinary()
35+
{
36+
return _provider.GetExpressionOfTypeBinary();
37+
}
38+
}
39+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using System.Collections.Generic;
2+
using System.Linq.Expressions;
3+
using NHibernate.DomainModel.NHSpecific;
4+
using NHibernate.Linq.Visitors;
5+
using NHibernate.Param;
6+
using NUnit.Framework;
7+
8+
namespace NHibernate.Test.NHSpecificTest.GH1526
9+
{
10+
[TestFixture]
11+
public class Fixture
12+
{
13+
private readonly AnonymousTypeQueryExpressionProviderFromNHibernateTestAssembly _providerFromNHTest
14+
= new AnonymousTypeQueryExpressionProviderFromNHibernateTestAssembly();
15+
16+
private readonly AnonymousTypeQueryExpressionProviderFromNHibernateDomainModelAssembly _providerFromNHDoMo
17+
= new AnonymousTypeQueryExpressionProviderFromNHibernateDomainModelAssembly();
18+
19+
[OneTimeSetUp]
20+
public void OneTimeSetUp()
21+
{
22+
// all the tests in this fixture run under condition, that
23+
// the types used from the two providers are different,
24+
// but have exactly the same System.Type.FullName when inspected
25+
26+
var type1 = _providerFromNHTest.GetAnonymousType();
27+
var type2 = _providerFromNHDoMo.GetAnonymousType();
28+
29+
Assert.That(type1.FullName, Is.EqualTo(type2.FullName),
30+
"The two tested types must have the same FullName for demonstrating the bug.");
31+
32+
Assert.That(type1, Is.Not.EqualTo(type2),
33+
"The two tested types must not be the same for demonstrating the bug.");
34+
}
35+
36+
[Test]
37+
public void ShouldCreateDifferentKeys_MethodCallExpression()
38+
{
39+
var exp1 = _providerFromNHTest.GetExpressionOfMethodCall();
40+
var exp2 = _providerFromNHDoMo.GetExpressionOfMethodCall();
41+
42+
var key1 = GetCacheKey(exp1);
43+
var key2 = GetCacheKey(exp2);
44+
45+
Assert.That(key1, Is.Not.EqualTo(key2));
46+
}
47+
48+
[Test]
49+
public void ShouldCreateDifferentKeys_NewExpression()
50+
{
51+
var exp1 = _providerFromNHTest.GetExpressionOfNew();
52+
var exp2 = _providerFromNHDoMo.GetExpressionOfNew();
53+
54+
var key1 = GetCacheKey(exp1);
55+
var key2 = GetCacheKey(exp2);
56+
57+
Assert.That(key1, Is.Not.EqualTo(key2));
58+
}
59+
60+
[Test]
61+
public void ShouldCreateDifferentKeys_TypeBinaryExpression()
62+
{
63+
var exp1 = _providerFromNHTest.GetExpressionOfTypeBinary();
64+
var exp2 = _providerFromNHDoMo.GetExpressionOfTypeBinary();
65+
66+
var key1 = GetCacheKey(exp1);
67+
var key2 = GetCacheKey(exp2);
68+
69+
Assert.That(key1, Is.Not.EqualTo(key2));
70+
}
71+
72+
private static string GetCacheKey(Expression exp)
73+
{
74+
return ExpressionKeyVisitor.Visit(exp, new Dictionary<ConstantExpression, NamedParameter>());
75+
}
76+
}
77+
}

src/NHibernate/Linq/Visitors/ExpressionKeyVisitor.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ protected override Expression VisitMethodCall(MethodCallExpression expression)
192192
protected override Expression VisitNew(NewExpression expression)
193193
{
194194
_string.Append("new ");
195-
_string.Append(expression.Constructor.DeclaringType.Name);
195+
_string.Append(expression.Constructor.DeclaringType.AssemblyQualifiedName);
196196
_string.Append('(');
197197
Visit(expression.Arguments, AppendCommas);
198198
_string.Append(')');
@@ -212,7 +212,7 @@ protected override Expression VisitTypeBinary(TypeBinaryExpression expression)
212212
_string.Append("IsType(");
213213
Visit(expression.Expression);
214214
_string.Append(", ");
215-
_string.Append(expression.TypeOperand.FullName);
215+
_string.Append(expression.TypeOperand.AssemblyQualifiedName);
216216
_string.Append(")");
217217

218218
return expression;
@@ -240,7 +240,7 @@ private void VisitMethod(MethodInfo methodInfo)
240240
if (methodInfo.IsGenericMethod)
241241
{
242242
_string.Append('[');
243-
_string.Append(string.Join(",", methodInfo.GetGenericArguments().Select(a => a.FullName).ToArray()));
243+
_string.Append(string.Join(",", methodInfo.GetGenericArguments().Select(a => a.AssemblyQualifiedName).ToArray()));
244244
_string.Append(']');
245245
}
246246
}

0 commit comments

Comments
 (0)