Skip to content

Commit 34c0693

Browse files
committed
Add ability to use dynamic component in any format
1 parent ff6fed0 commit 34c0693

File tree

11 files changed

+414
-55
lines changed

11 files changed

+414
-55
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using NUnit.Framework;
4+
5+
namespace NHibernate.Test.NHSpecificTest.NH1039Dynamic
6+
{
7+
[TestFixture]
8+
public class Fixture : BugTestCase
9+
{
10+
public override string BugNumber => "NH1039Dynamic";
11+
12+
protected override void OnTearDown()
13+
{
14+
base.OnTearDown();
15+
using (var s = OpenSession())
16+
using (var tx = s.BeginTransaction())
17+
{
18+
s.Delete("from Person");
19+
tx.Commit();
20+
}
21+
}
22+
23+
[Test]
24+
public void Test()
25+
{
26+
using (var s = OpenSession())
27+
using (var tx = s.BeginTransaction())
28+
{
29+
var person = new Person("1")
30+
{
31+
Name = "John Doe",
32+
Properties =
33+
{
34+
Phones = new HashSet<object> {"555-1234", "555-4321"}
35+
}
36+
};
37+
38+
s.Save(person);
39+
tx.Commit();
40+
}
41+
using (var s = OpenSession())
42+
using (s.BeginTransaction())
43+
{
44+
var person = (Person) s.CreateCriteria(typeof(Person)).UniqueResult();
45+
46+
Assert.That(person.ID, Is.EqualTo("1"));
47+
Assert.That(person.Name, Is.EqualTo("John Doe"));
48+
var phones = person.Properties.Phones as ISet<object>;
49+
Assert.That(phones, Is.Not.Null);
50+
Assert.That(phones.Contains("555-1234"), Is.True);
51+
Assert.That(phones.Contains("555-4321"), Is.True);
52+
}
53+
}
54+
}
55+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
3+
namespace="NHibernate.Test.NHSpecificTest.NH1039Dynamic"
4+
assembly="NHibernate.Test">
5+
6+
<class name="Person" table="NH1039_Person">
7+
<id name="ID" type="string" length="32">
8+
<generator class="assigned"/>
9+
</id>
10+
11+
<property name="Name"/>
12+
13+
<dynamic-component name="Properties">
14+
<set name="Phones" table="NH1039_Phone">
15+
<key column="PersonId"/>
16+
<element column="`Number`" type="string"/>
17+
</set>
18+
</dynamic-component>
19+
</class>
20+
21+
</hibernate-mapping>
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using System;
2+
using System.Collections;
3+
using System.Collections.Generic;
4+
using System.Dynamic;
5+
using System.Text;
6+
7+
namespace NHibernate.Test.NHSpecificTest.NH1039Dynamic
8+
{
9+
public class Person
10+
{
11+
public Person()
12+
{
13+
}
14+
15+
public Person(string id)
16+
{
17+
ID = id;
18+
}
19+
20+
public virtual string ID { get; set; }
21+
22+
public virtual string Name { get; set; }
23+
24+
public virtual dynamic Properties { get; set; } = new ExpandoObject();
25+
}
26+
}

src/NHibernate.Test/NHSpecificTest/NH2664Dynamic/Fixture.cs

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Linq;
33
using NUnit.Framework;
44
using System.Collections.Generic;
5+
using System.Dynamic;
56
using System.Linq.Dynamic.Core;
67

78
namespace NHibernate.Test.NHSpecificTest.NH2664Dynamic
@@ -22,37 +23,38 @@ protected override void OnSetUp()
2223
using (var session = OpenSession())
2324
using (var tran = session.BeginTransaction())
2425
{
26+
var properties1 = new Dictionary<string, object>
27+
{
28+
["Name"] = "First Product",
29+
["Description"] = "First Description"
30+
};
2531
session.Save(
2632
new Product
2733
{
2834
ProductId = "1",
29-
Properties = new Dictionary<string, object>
30-
{
31-
["Name"] = "First Product",
32-
["Description"] = "First Description"
33-
}
35+
Properties = properties1
3436
});
3537

38+
var properties2 = new
39+
{
40+
Name = "Second Product",
41+
Description = "Second Description"
42+
};
3643
session.Save(
3744
new Product
3845
{
3946
ProductId = "2",
40-
Properties = new Dictionary<string, object>
41-
{
42-
["Name"] = "Second Product",
43-
["Description"] = "Second Description"
44-
}
47+
Properties = properties2
4548
});
4649

50+
dynamic properties3 = new ExpandoObject();
51+
properties3.Name = "Third Product";
52+
properties3.Description = "Third Description";
4753
session.Save(
4854
new Product
4955
{
5056
ProductId = "3",
51-
Properties = new Dictionary<string, object>
52-
{
53-
["Name"] = "val",
54-
["Description"] = "val"
55-
}
57+
Properties = properties3
5658
});
5759

5860
tran.Commit();
@@ -81,6 +83,7 @@ public void Query_DynamicComponent()
8183

8284
Assert.That(product, Is.Not.Null);
8385
Assert.That((object) product.Properties["Name"], Is.EqualTo("First Product"));
86+
Assert.That((object) product.Properties.Name, Is.EqualTo("First Product"));
8487
}
8588
}
8689

@@ -100,4 +103,4 @@ public void Multiple_Query_Does_Not_Cache()
100103
}
101104
}
102105
}
103-
}
106+
}

src/NHibernate.Test/NHSpecificTest/NH2664Dynamic/Mappings.hbm.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,4 @@
1414

1515
</class>
1616

17-
</hibernate-mapping>
17+
</hibernate-mapping>

src/NHibernate.Test/NHSpecificTest/NH2664Dynamic/Product.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@ public class Product
66

77
public virtual dynamic Properties { get; set; }
88
}
9-
}
9+
}

src/NHibernate/NHibernate.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
<Reference Include="System.ServiceModel" />
5050
<Reference Include="System.Transactions" />
5151
<Reference Include="System.Configuration" />
52+
<Reference Include="Microsoft.CSharp" />
5253
</ItemGroup>
5354

5455
<ItemGroup Condition="'$(TargetFramework)' == 'netcoreapp2.0'">
@@ -63,6 +64,7 @@
6364
<PackageReference Include="System.Security.Permissions" Version="4.4.1" />
6465
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
6566
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.3.0" />
67+
<PackageReference Include="Microsoft.CSharp" Version="4.4.1" />
6668
</ItemGroup>
6769

6870
<ItemGroup>

src/NHibernate/Properties/MapAccessor.cs

Lines changed: 66 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
1+
using System;
12
using System.Collections;
3+
using System.Collections.Concurrent;
4+
using System.Collections.Generic;
25
using System.Reflection;
6+
using System.Runtime.CompilerServices;
7+
using Microsoft.CSharp.RuntimeBinder;
38
using NHibernate.Engine;
4-
using System;
9+
using Binder = Microsoft.CSharp.RuntimeBinder.Binder;
10+
511
namespace NHibernate.Properties
612
{
713
[Serializable]
814
public class MapAccessor : IPropertyAccessor
915
{
10-
#region IPropertyAccessor Members
11-
1216
public IGetter GetGetter(System.Type theClass, string propertyName)
1317
{
1418
return new MapGetter(propertyName);
@@ -19,70 +23,100 @@ public ISetter GetSetter(System.Type theClass, string propertyName)
1923
return new MapSetter(propertyName);
2024
}
2125

22-
public bool CanAccessThroughReflectionOptimizer
23-
{
24-
get { return false; }
25-
}
26+
public bool CanAccessThroughReflectionOptimizer => false;
2627

27-
#endregion
2828
[Serializable]
2929
public sealed class MapSetter : ISetter
3030
{
31+
private static readonly ConcurrentDictionary<Tuple<System.Type, string>, CallSite<Func<CallSite, object, object, object>>>
32+
SetMemberSites = new ConcurrentDictionary<Tuple<System.Type, string>, CallSite<Func<CallSite, object, object, object>>>();
33+
3134
private readonly string name;
3235

3336
internal MapSetter(string name)
3437
{
3538
this.name = name;
3639
}
3740

38-
public MethodInfo Method
39-
{
40-
get { return null; }
41-
}
41+
public MethodInfo Method => null;
4242

43-
public string PropertyName
44-
{
45-
get { return null; }
46-
}
43+
public string PropertyName => null;
4744

4845
public void Set(object target, object value)
4946
{
50-
((IDictionary)target)[name] = value;
47+
switch (target)
48+
{
49+
case IDictionary d:
50+
d[name] = value;
51+
break;
52+
case IDictionary<string, object> d:
53+
d[name] = value;
54+
break;
55+
default:
56+
var site = SetMemberSites.GetOrAdd(
57+
System.Tuple.Create(target.GetType(), name),
58+
t => CallSite<Func<CallSite, object, object, object>>.Create(
59+
Binder.GetMember(
60+
CSharpBinderFlags.None,
61+
t.Item2,
62+
t.Item1,
63+
new[]
64+
{
65+
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
66+
CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
67+
})));
68+
69+
site.Target(site, target, value);
70+
break;
71+
}
5172
}
5273
}
74+
5375
[Serializable]
5476
public sealed class MapGetter : IGetter
5577
{
78+
private static readonly ConcurrentDictionary<Tuple<System.Type, string>, CallSite<Func<CallSite, object, object>>>
79+
GetMemberSites = new ConcurrentDictionary<Tuple<System.Type, string>, CallSite<Func<CallSite, object, object>>>();
80+
5681
private readonly string name;
5782

5883
internal MapGetter(string name)
5984
{
6085
this.name = name;
6186
}
6287

63-
public MethodInfo Method
64-
{
65-
get { return null; }
66-
}
88+
public MethodInfo Method => null;
6789

68-
public object GetForInsert(object owner, IDictionary mergeMap, ISessionImplementor session)
69-
{
70-
return Get(owner);
71-
}
90+
public string PropertyName => null;
7291

73-
public string PropertyName
74-
{
75-
get { return null; }
76-
}
92+
public System.Type ReturnType => typeof(object);
7793

78-
public System.Type ReturnType
94+
public object GetForInsert(object owner, IDictionary mergeMap, ISessionImplementor session)
7995
{
80-
get { return typeof(object); }
96+
return Get(owner);
8197
}
8298

8399
public object Get(object target)
84100
{
85-
return ((IDictionary)target)[name];
101+
switch (target)
102+
{
103+
case IDictionary d:
104+
return d[name];
105+
case IDictionary<string, object> d:
106+
d.TryGetValue(name, out var result);
107+
return result;
108+
default:
109+
var site = GetMemberSites.GetOrAdd(
110+
System.Tuple.Create(target.GetType(), name),
111+
t => CallSite<Func<CallSite, object, object>>.Create(
112+
Binder.GetMember(
113+
CSharpBinderFlags.None,
114+
t.Item2,
115+
t.Item1,
116+
new[] {CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)})));
117+
118+
return site.Target(site, target);
119+
}
86120
}
87121
}
88122
}
Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
using System;
2+
using System.Collections;
23
using System.Collections.Generic;
4+
using NHibernate.Util;
35

46
namespace NHibernate.Tuple.Component
57
{
@@ -8,8 +10,10 @@ internal class DynamicComponentInstantiator : IInstantiator
810
{
911
public object Instantiate(object id) => Instantiate();
1012

11-
public object Instantiate() => new Dictionary<string, object>();
13+
public object Instantiate() => new DynamicComponent();
1214

13-
public bool IsInstance(object obj) => obj is Dictionary<string, object>;
15+
public bool IsInstance(object obj) => obj is DynamicComponent ||
16+
obj is IDictionary<string, object> ||
17+
obj is IDictionary;
1418
}
1519
}

0 commit comments

Comments
 (0)