Skip to content

Commit ff6fed0

Browse files
committed
Add support for querying dynamic objects with Linq
1 parent 5fb348c commit ff6fed0

File tree

5 files changed

+144
-2
lines changed

5 files changed

+144
-2
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
using System.Collections;
2+
using System.Linq;
3+
using NUnit.Framework;
4+
using System.Collections.Generic;
5+
using System.Linq.Dynamic.Core;
6+
7+
namespace NHibernate.Test.NHSpecificTest.NH2664Dynamic
8+
{
9+
[TestFixture]
10+
public class Fixture : TestCase
11+
{
12+
protected override string MappingsAssembly => "NHibernate.Test";
13+
14+
protected override IList Mappings => new[] {"NHSpecificTest.NH2664Dynamic.Mappings.hbm.xml"};
15+
16+
/// <summary>
17+
/// push some data into the database
18+
/// Really functions as a save test also
19+
/// </summary>
20+
protected override void OnSetUp()
21+
{
22+
using (var session = OpenSession())
23+
using (var tran = session.BeginTransaction())
24+
{
25+
session.Save(
26+
new Product
27+
{
28+
ProductId = "1",
29+
Properties = new Dictionary<string, object>
30+
{
31+
["Name"] = "First Product",
32+
["Description"] = "First Description"
33+
}
34+
});
35+
36+
session.Save(
37+
new Product
38+
{
39+
ProductId = "2",
40+
Properties = new Dictionary<string, object>
41+
{
42+
["Name"] = "Second Product",
43+
["Description"] = "Second Description"
44+
}
45+
});
46+
47+
session.Save(
48+
new Product
49+
{
50+
ProductId = "3",
51+
Properties = new Dictionary<string, object>
52+
{
53+
["Name"] = "val",
54+
["Description"] = "val"
55+
}
56+
});
57+
58+
tran.Commit();
59+
}
60+
}
61+
62+
protected override void OnTearDown()
63+
{
64+
using (var session = OpenSession())
65+
using (var tran = session.BeginTransaction())
66+
{
67+
session.CreateQuery("delete from Product").ExecuteUpdate();
68+
tran.Commit();
69+
}
70+
}
71+
72+
[Test]
73+
public void Query_DynamicComponent()
74+
{
75+
using (var session = OpenSession())
76+
{
77+
var product = session
78+
.Query<Product>()
79+
.Where("Properties.Name == @0", "First Product")
80+
.Single();
81+
82+
Assert.That(product, Is.Not.Null);
83+
Assert.That((object) product.Properties["Name"], Is.EqualTo("First Product"));
84+
}
85+
}
86+
87+
[Test]
88+
public void Multiple_Query_Does_Not_Cache()
89+
{
90+
using (var session = OpenSession())
91+
{
92+
// Query by name
93+
var product1 = session.Query<Product>().Where("Properties.Name == @0", "First Product").Single();
94+
Assert.That(product1.ProductId, Is.EqualTo("1"));
95+
96+
// Query by description (this test is to verify that the dictionary
97+
// index isn't cached from the query above.
98+
var product2 = session.Query<Product>().Where("Properties.Description == @0", "Second Description").Single();
99+
Assert.That(product2.ProductId, Is.EqualTo("2"));
100+
}
101+
}
102+
}
103+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernate.Test"
3+
namespace="NHibernate.Test.NHSpecificTest.NH2664Dynamic">
4+
<class name="Product" table="Products">
5+
<id name="ProductId" column="ProductId" type="String">
6+
<generator class="assigned" />
7+
8+
</id>
9+
<dynamic-component name="Properties">
10+
<property name="Description" column="Description" type="String"/>
11+
<property name="Name" column="Name" type="String" />
12+
13+
</dynamic-component>
14+
15+
</class>
16+
17+
</hibernate-mapping>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace NHibernate.Test.NHSpecificTest.NH2664Dynamic
2+
{
3+
public class Product
4+
{
5+
public virtual string ProductId { get; set; }
6+
7+
public virtual dynamic Properties { get; set; }
8+
}
9+
}

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
</ItemGroup>
4949
<ItemGroup>
5050
<PackageReference Include="log4net" Version="2.0.8" />
51-
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.0.8" />
51+
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.0.8.11" />
5252
<PackageReference Include="NSubstitute" Version="3.1.0" />
5353
<PackageReference Include="NUnit" Version="3.10.1" />
5454
<PackageReference Include="NUnit3TestAdapter" Version="3.10.0" />

src/NHibernate/Linq/Visitors/HqlGeneratorExpressionVisitor.cs

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using System;
2+
using System.Dynamic;
23
using System.Linq;
34
using System.Linq.Expressions;
5+
using System.Runtime.CompilerServices;
46
using NHibernate.Engine.Query;
57
using NHibernate.Hql.Ast;
68
using NHibernate.Linq.Expressions;
@@ -141,7 +143,6 @@ protected HqlTreeNode VisitExpression(Expression expression)
141143
}
142144
}
143145

144-
145146
private HqlTreeNode VisitTypeBinaryExpression(TypeBinaryExpression expression)
146147
{
147148
return BuildOfType(expression.Expression, expression.TypeOperand);
@@ -206,6 +207,18 @@ private HqlTreeNode VisitNhNominated(NhNominatedExpression nhNominatedExpression
206207

207208
private HqlTreeNode VisitInvocationExpression(InvocationExpression expression)
208209
{
210+
//This is an ugly workaround for dynamic expressions.
211+
//Unfortunately we can not tap into the expression tree earlier to intercept the dynamic expression
212+
if (expression.Arguments.Count == 2 &&
213+
expression.Arguments[0] is ConstantExpression constant &&
214+
constant.Value is CallSite site &&
215+
site.Binder is GetMemberBinder binder)
216+
{
217+
return _hqlTreeBuilder.Dot(
218+
VisitExpression(expression.Arguments[1]).AsExpression(),
219+
_hqlTreeBuilder.Ident(binder.Name));
220+
}
221+
209222
return VisitExpression(expression.Expression);
210223
}
211224

0 commit comments

Comments
 (0)