Skip to content

Commit ac5e116

Browse files
committed
Add support for dynamic components
1 parent 7fd6dc7 commit ac5e116

12 files changed

+256
-90
lines changed
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Collections;
2+
3+
namespace NHibernate.DomainModel.Northwind.Entities
4+
{
5+
public class DynamicUser
6+
{
7+
public virtual int Id { get; set; }
8+
9+
public virtual dynamic Properties { get; set; }
10+
11+
public virtual IDictionary Settings { get; set; }
12+
}
13+
}

src/NHibernate.DomainModel/Northwind/Entities/Northwind.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,11 @@ public IQueryable<User> Users
6969
get { return _session.Query<User>(); }
7070
}
7171

72+
public IQueryable<DynamicUser> DynamicUsers
73+
{
74+
get { return _session.Query<DynamicUser>(); }
75+
}
76+
7277
public IQueryable<PatientRecord> PatientRecords
7378
{
7479
get { return _session.Query<PatientRecord>(); }
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" namespace="NHibernate.DomainModel.Northwind.Entities" assembly="NHibernate.DomainModel">
3+
<class name="DynamicUser" mutable="false">
4+
<subselect>
5+
select * from Users
6+
</subselect>
7+
8+
<id name="Id" column="UserId" type="Int32">
9+
<generator class="assigned" />
10+
</id>
11+
12+
<dynamic-component name="Properties">
13+
<property name="Name" type="AnsiString" />
14+
<property name="Enum1" type="NHibernate.DomainModel.Northwind.Entities.EnumStoredAsStringType, NHibernate.DomainModel">
15+
<column name="Enum1" length="12" />
16+
</property>
17+
<many-to-one name="CreatedBy" class="User" not-null="true" lazy="false">
18+
<column name="CreatedById" not-null="true" />
19+
</many-to-one>
20+
</dynamic-component>
21+
22+
<dynamic-component name="Settings">
23+
<property name="Property1" type="AnsiString" />
24+
<property name="Property2" type="AnsiString" />
25+
<many-to-one name="ModifiedBy" class="User" lazy="false">
26+
<column name="ModifiedById" />
27+
</many-to-one>
28+
</dynamic-component>
29+
</class>
30+
</hibernate-mapping>

src/NHibernate.Test/Linq/LinqTestCase.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ protected override string[] Mappings
3434
"Northwind.Mappings.User.hbm.xml",
3535
"Northwind.Mappings.TimeSheet.hbm.xml",
3636
"Northwind.Mappings.Animal.hbm.xml",
37-
"Northwind.Mappings.Patient.hbm.xml"
37+
"Northwind.Mappings.Patient.hbm.xml",
38+
"Northwind.Mappings.DynamicUser.hbm.xml"
3839
};
3940
}
4041
}

src/NHibernate.Test/Linq/ConstantTypeLocatorTests.cs renamed to src/NHibernate.Test/Linq/ParameterTypeLocatorTests.cs

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Globalization;
44
using System.Linq;
5+
using System.Linq.Dynamic.Core;
56
using System.Linq.Expressions;
67
using NHibernate.DomainModel.Northwind.Entities;
78
using NHibernate.Engine.Query;
@@ -13,7 +14,7 @@
1314

1415
namespace NHibernate.Test.Linq
1516
{
16-
public class ConstantTypeLocatorTests : LinqTestCase
17+
public class ParameterTypeLocatorTests : LinqTestCase
1718
{
1819
[Test]
1920
public void AddIntegerTest()
@@ -172,8 +173,7 @@ public void ConditionalTest()
172173
new Dictionary<string, Predicate<IType>>
173174
{
174175
{"2", o => o is EnumStoredAsStringType},
175-
{"Unspecified", o => o is EnumStoredAsStringType},
176-
{"null", o => o is PersistentEnumType}, // HasValue
176+
{"Unspecified", o => o is EnumStoredAsStringType}
177177
},
178178
db.Users.Where(o => (o.NullableEnum2.HasValue ? o.Enum1 : EnumStoredAsString.Unspecified) == EnumStoredAsString.Medium),
179179
db.Users.Where(o => EnumStoredAsString.Medium == (o.NullableEnum2.HasValue ? EnumStoredAsString.Unspecified : o.Enum1))
@@ -189,8 +189,7 @@ public void DoubleConditionalTest()
189189
{"0", o => o is PersistentEnumType},
190190
{"2", o => o is EnumStoredAsStringType},
191191
{"Small", o => o is EnumStoredAsStringType},
192-
{"Unspecified", o => o is EnumStoredAsStringType},
193-
{"null", o => o is PersistentEnumType}, // HasValue
192+
{"Unspecified", o => o is EnumStoredAsStringType}
194193
},
195194
db.Users.Where(o => (o.Enum2 != EnumStoredAsInt32.Unspecified
196195
? (o.NullableEnum2.HasValue ? o.Enum1 : EnumStoredAsString.Unspecified)
@@ -228,6 +227,36 @@ public void ConditionalMemberTest()
228227
);
229228
}
230229

230+
[Test]
231+
public void DynamicMemberTest()
232+
{
233+
AssertResults(
234+
new Dictionary<string, Predicate<IType>>
235+
{
236+
{"\"test\"", o => o is AnsiStringType},
237+
},
238+
db.DynamicUsers.Where("Properties.Name == @0", "test"),
239+
db.DynamicUsers.Where("@0 == Properties.Name", "test")
240+
);
241+
}
242+
243+
[Test]
244+
public void DynamicDictionaryMemberTest()
245+
{
246+
AssertResults(
247+
new Dictionary<string, Predicate<IType>>
248+
{
249+
{"\"test\"", o => o is AnsiStringType},
250+
},
251+
#pragma warning disable CS0252
252+
db.DynamicUsers.Where(o => o.Settings["Property1"] == "test"),
253+
#pragma warning restore CS0252
254+
#pragma warning disable CS0253
255+
db.DynamicUsers.Where(o => "test" == o.Settings["Property1"])
256+
#pragma warning restore CS0253
257+
);
258+
}
259+
231260
[Test]
232261
public void AssignMemberTest()
233262
{
@@ -373,19 +402,20 @@ private void AssertResult(
373402
System.Type targetType)
374403
{
375404
var result = NhRelinqQueryParser.PreTransform(expression, new PreTransformationParameters(queryMode, Sfi));
405+
var parameters = ExpressionParameterVisitor.Visit(result);
376406
expression = result.Expression;
377407
var queryModel = NhRelinqQueryParser.Parse(expression);
378-
var types = ConstantTypeLocator.GetTypes(queryModel, targetType, Sfi);
379-
Assert.That(types.Count, Is.EqualTo(expectedResults.Count), "Incorrect number of constants");
380-
foreach (var pair in types)
408+
ParameterTypeLocator.SetParameterTypes(parameters, queryModel, targetType, Sfi);
409+
Assert.That(parameters.Count, Is.EqualTo(expectedResults.Count), "Incorrect number of parameters");
410+
foreach (var pair in parameters)
381411
{
382412
var origCulture = CultureInfo.CurrentCulture;
383413
try
384414
{
385415
CultureInfo.CurrentCulture = CultureInfo.InvariantCulture;
386416
var expressionText = pair.Key.ToString();
387417
Assert.That(expectedResults.ContainsKey(expressionText), Is.True, $"{expressionText} constant is not expected");
388-
Assert.That(expectedResults[expressionText](pair.Value), Is.True, $"Invalid type, actual type: {pair.Value?.Name ?? "null"}");
418+
Assert.That(expectedResults[expressionText](pair.Value.Type), Is.True, $"Invalid type, actual type: {pair.Value?.Name ?? "null"}");
389419
}
390420
finally
391421
{

src/NHibernate/Linq/NhLinqExpression.cs

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ public IASTNode Translate(ISessionFactoryImplementor sessionFactory, bool filter
9090

9191
var requiredHqlParameters = new List<NamedParameterDescriptor>();
9292
var queryModel = NhRelinqQueryParser.Parse(_expression);
93-
SetParameterTypes(sessionFactory, queryModel);
93+
ParameterTypeLocator.SetParameterTypes(_constantToParameterMap, queryModel, TargetType, sessionFactory, true);
9494
var visitorParameters = new VisitorParameters(sessionFactory, _constantToParameterMap, requiredHqlParameters,
9595
new QuerySourceNamer(), TargetType, QueryMode);
9696

@@ -121,24 +121,6 @@ internal void CopyExpressionTranslation(NhLinqExpression other)
121121
Type = other.Type;
122122
}
123123

124-
private void SetParameterTypes(
125-
ISessionFactoryImplementor sessionFactory,
126-
QueryModel queryModel)
127-
{
128-
if (_constantToParameterMap.Count == 0)
129-
{
130-
return;
131-
}
132-
133-
foreach (var pair in ConstantTypeLocator.GetTypes(queryModel, TargetType, sessionFactory, true))
134-
{
135-
if (_constantToParameterMap.TryGetValue(pair.Key, out var parameter))
136-
{
137-
parameter.Type = pair.Value;
138-
}
139-
}
140-
}
141-
142124
private static IASTNode DuplicateTree(IASTNode ast)
143125
{
144126
var thisNode = ast.DupNode();

src/NHibernate/Linq/Visitors/ExpressionKeyVisitor.cs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using NHibernate.Engine;
1111
using NHibernate.Param;
1212
using NHibernate.Type;
13+
using NHibernate.Util;
1314
using Remotion.Linq.Parsing;
1415

1516
namespace NHibernate.Linq.Visitors
@@ -206,6 +207,19 @@ protected override Expression VisitMember(MemberExpression expression)
206207
return expression;
207208
}
208209

210+
protected override Expression VisitInvocation(InvocationExpression expression)
211+
{
212+
if (ExpressionsHelper.TryGetDynamicMemberBinder(expression, out var memberBinder))
213+
{
214+
Visit(expression.Arguments[1]);
215+
_string.Append(".");
216+
_string.Append(memberBinder.Name);
217+
return expression;
218+
}
219+
220+
return base.VisitInvocation(expression);
221+
}
222+
209223
protected override Expression VisitMethodCall(MethodCallExpression expression)
210224
{
211225
Visit(expression.Object);

src/NHibernate/Linq/Visitors/ExpressionParameterVisitor.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,18 @@ protected override Expression VisitMethodCall(MethodCallExpression expression)
106106
return base.VisitMethodCall(expression);
107107
}
108108

109+
protected override Expression VisitInvocation(InvocationExpression expression)
110+
{
111+
if (ExpressionsHelper.TryGetDynamicMemberBinder(expression, out _))
112+
{
113+
// Avoid adding System.Runtime.CompilerServices.CallSite instance as a parameter
114+
base.Visit(expression.Arguments[1]);
115+
return expression;
116+
}
117+
118+
return base.VisitInvocation(expression);
119+
}
120+
109121
protected override Expression VisitConstant(ConstantExpression expression)
110122
{
111123
if (!_parameters.ContainsKey(expression) && !typeof(IQueryable).IsAssignableFrom(expression.Type) && !IsNullObject(expression))

src/NHibernate/Linq/Visitors/HqlGeneratorExpressionVisitor.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -226,12 +226,7 @@ private HqlTreeNode VisitNhNominated(NhNominatedExpression nhNominatedExpression
226226

227227
private HqlTreeNode VisitInvocationExpression(InvocationExpression expression)
228228
{
229-
//This is an ugly workaround for dynamic expressions.
230-
//Unfortunately we can not tap into the expression tree earlier to intercept the dynamic expression
231-
if (expression.Arguments.Count == 2 &&
232-
expression.Arguments[0] is ConstantExpression constant &&
233-
constant.Value is CallSite site &&
234-
site.Binder is GetMemberBinder binder)
229+
if (ExpressionsHelper.TryGetDynamicMemberBinder(expression, out var binder))
235230
{
236231
return _hqlTreeBuilder.Dot(
237232
VisitExpression(expression.Arguments[1]).AsExpression(),

0 commit comments

Comments
 (0)