Skip to content

Commit f13368c

Browse files
rjpereshazzikfredericDelaporte
committed
Allow setting dynamic component templates from dictionary in ByCode
Fixes #913 - NH-3704 Co-authored-by: Alexander Zaytsev <hazzik@gmail.com> Co-authored-by: Frédéric Delaporte <12201973+fredericdelaporte@users.noreply.github.com>
1 parent e8288a5 commit f13368c

File tree

5 files changed

+344
-23
lines changed

5 files changed

+344
-23
lines changed

src/NHibernate.Test/MappingByCode/ExplicitMappingTests/DynamicComponentMappingTests.cs

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
using NHibernate.Mapping.ByCode;
77
using NUnit.Framework;
88

9-
namespace NHibernate.Test.MappingByCode.ExpliticMappingTests
9+
namespace NHibernate.Test.MappingByCode.ExplicitMappingTests
1010
{
1111
[TestFixture]
1212
public class DynamicComponentMappingTests
@@ -33,6 +33,141 @@ public IDictionary<string, object> Info
3333
}
3434
}
3535

36+
private class PersonWithDynamicInfo
37+
{
38+
public int Id { get; set; }
39+
public dynamic Info { get; set; }
40+
}
41+
42+
[Test]
43+
public void WhenMapDynCompoByDictionaryThenMapItAndItsProperties()
44+
{
45+
//NH-3704
46+
var mapper = new ModelMapper();
47+
mapper.Class<Person>(
48+
map =>
49+
{
50+
map.Id(x => x.Id, idmap => { });
51+
map.Component(
52+
x => x.Info,
53+
new Dictionary<string, System.Type>
54+
{{"MyInt", typeof(int)}, {"MyDate", typeof(DateTime)}},
55+
z => { z.Property("MyInt", pm => pm.Column("MY_COLUMN")); });
56+
});
57+
58+
var hbmMapping = mapper.CompileMappingFor(new[] { typeof(Person) });
59+
var hbmClass = hbmMapping.RootClasses[0];
60+
var hbmDynamicComponent = hbmClass.Properties.OfType<HbmDynamicComponent>().SingleOrDefault();
61+
Assert.That(hbmDynamicComponent, Is.Not.Null);
62+
Assert.That(
63+
hbmDynamicComponent.Properties.Select(x => x.Name),
64+
Is.EquivalentTo(new[] { "MyInt", "MyDate" }));
65+
}
66+
67+
[Test]
68+
public void WhenMapDynCompoByDictionaryThenMapItAndItsPropertiesGeneric()
69+
{
70+
//NH-3704
71+
var mapper = new ModelMapper();
72+
mapper.Class<PersonWithGenericInfo>(
73+
map =>
74+
{
75+
map.Id(x => x.Id, idmap => { });
76+
map.Component(
77+
x => x.Info,
78+
new Dictionary<string, System.Type>
79+
{{"MyInt", typeof(int)}, {"MyDate", typeof(DateTime)}},
80+
z => { z.Property("MyInt", pm => pm.Column("MY_COLUMN")); });
81+
});
82+
83+
var hbmMapping = mapper.CompileMappingFor(new[] { typeof(PersonWithGenericInfo) });
84+
var hbmClass = hbmMapping.RootClasses[0];
85+
var hbmDynamicComponent = hbmClass.Properties.OfType<HbmDynamicComponent>().SingleOrDefault();
86+
Assert.That(hbmDynamicComponent, Is.Not.Null);
87+
Assert.That(
88+
hbmDynamicComponent.Properties.Select(x => x.Name),
89+
Is.EquivalentTo(new[] { "MyInt", "MyDate" }));
90+
}
91+
92+
[Test]
93+
public void WhenMapDynCompoByDictionaryThenMapItAndItsPropertiesDynamic()
94+
{
95+
//NH-3704
96+
var mapper = new ModelMapper();
97+
mapper.Class<PersonWithDynamicInfo>(
98+
map =>
99+
{
100+
map.Id(x => x.Id, idmap => { });
101+
map.Component(
102+
nameof(PersonWithDynamicInfo.Info),
103+
new Dictionary<string, System.Type>
104+
{{"MyInt", typeof(int)}, {"MyDate", typeof(DateTime)}},
105+
z =>
106+
{
107+
z.Property("MyInt", pm => pm.Column("MY_COLUMN"));
108+
z.Component<DateTime>("MyDate");
109+
});
110+
});
111+
112+
var hbmMapping = mapper.CompileMappingFor(new[] { typeof(PersonWithDynamicInfo) });
113+
var hbmClass = hbmMapping.RootClasses[0];
114+
var hbmDynamicComponent = hbmClass.Properties.OfType<HbmDynamicComponent>().SingleOrDefault();
115+
Assert.That(hbmDynamicComponent, Is.Not.Null);
116+
Assert.That(
117+
hbmDynamicComponent.Properties.Select(x => x.Name),
118+
Is.EquivalentTo(new[] { "MyInt", "MyDate" }));
119+
}
120+
121+
[Test]
122+
public void WhenMapPrivateDynCompoByDictionaryThenMapItAndItsProperties()
123+
{
124+
//NH-3704
125+
var mapper = new ModelMapper();
126+
mapper.Class<Person>(
127+
map =>
128+
{
129+
map.Id(x => x.Id, idmap => { });
130+
map.Component(
131+
"Info",
132+
new Dictionary<string, System.Type>
133+
{{"MyInt", typeof(int)}, {"MyDate", typeof(DateTime)}},
134+
z => { z.Property("MyInt", pm => pm.Column("MY_COLUMN")); });
135+
});
136+
137+
var hbmMapping = mapper.CompileMappingFor(new[] { typeof(Person) });
138+
var hbmClass = hbmMapping.RootClasses[0];
139+
var hbmDynamicComponent = hbmClass.Properties.OfType<HbmDynamicComponent>().SingleOrDefault();
140+
Assert.That(hbmDynamicComponent, Is.Not.Null);
141+
Assert.That(
142+
hbmDynamicComponent.Properties.Select(x => x.Name),
143+
Is.EquivalentTo(new[] { "MyInt", "MyDate" }));
144+
}
145+
146+
[Test]
147+
public void WhenMapPrivateDynCompoByDictionaryThenMapItAndItsPropertiesGeneric()
148+
{
149+
//NH-3704
150+
var mapper = new ModelMapper();
151+
mapper.Class<PersonWithGenericInfo>(
152+
map =>
153+
{
154+
map.Id(x => x.Id, idmap => { });
155+
map.Component(
156+
"Info",
157+
new Dictionary<string, System.Type>
158+
{{"MyInt", typeof(int)}, {"MyDate", typeof(DateTime)}},
159+
z => { z.Property("MyInt", pm => pm.Column("MY_COLUMN")); });
160+
});
161+
162+
var hbmMapping = mapper.CompileMappingFor(new[] { typeof(PersonWithGenericInfo) });
163+
var hbmClass = hbmMapping.RootClasses[0];
164+
var hbmDynamicComponent = hbmClass.Properties.OfType<HbmDynamicComponent>().SingleOrDefault();
165+
Assert.That(hbmDynamicComponent, Is.Not.Null);
166+
Assert.That(
167+
hbmDynamicComponent.Properties.Select(x => x.Name),
168+
Is.EquivalentTo(new[] { "MyInt", "MyDate" }));
169+
}
170+
36171
[Test]
37172
public void WhenMapDynCompoThenMapItAndItsProperties()
38173
{

src/NHibernate/Mapping/ByCode/IPlainPropertyContainerMapper.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,29 @@ public interface IBasePlainPropertyContainerMapper<TContainer> : IMinimalPlainPr
4242
{
4343
void Component<TComponent>(Expression<Func<TContainer, TComponent>> property, Action<IComponentMapper<TComponent>> mapping);
4444
void Component<TComponent>(Expression<Func<TContainer, TComponent>> property);
45+
/// <summary>
46+
/// Maps a non-generic dictionary property as a dynamic component.
47+
/// </summary>
48+
/// <param name="property">The property to map.</param>
49+
/// <param name="dynamicComponentTemplate">The template for the component. It should either be a (usually
50+
/// anonymous) type having the same properties than the component, or an
51+
/// <c>IDictionary&lt;string, System.Type&gt;</c> of property names with their type.</param>
52+
/// <param name="mapping">The mapping of the component.</param>
53+
/// <typeparam name="TComponent">The type of the template.</typeparam>
4554
void Component<TComponent>(Expression<Func<TContainer, IDictionary>> property, TComponent dynamicComponentTemplate, Action<IDynamicComponentMapper<TComponent>> mapping);
4655

4756
void Component<TComponent>(string notVisiblePropertyOrFieldName, Action<IComponentMapper<TComponent>> mapping);
4857
void Component<TComponent>(string notVisiblePropertyOrFieldName);
58+
/// <summary>
59+
/// Maps a property or field as a dynamic component. The property can be a C# <c>dynamic</c> or a dictionary of
60+
/// property names to their value.
61+
/// </summary>
62+
/// <param name="notVisiblePropertyOrFieldName">The property or field name to map.</param>
63+
/// <param name="dynamicComponentTemplate">The template for the component. It should either be a (usually
64+
/// anonymous) type having the same properties than the component, or an
65+
/// <c>IDictionary&lt;string, System.Type&gt;</c> of property names with their type.</param>
66+
/// <param name="mapping">The mapping of the component.</param>
67+
/// <typeparam name="TComponent">The type of the template.</typeparam>
4968
void Component<TComponent>(string notVisiblePropertyOrFieldName, TComponent dynamicComponentTemplate, Action<IDynamicComponentMapper<TComponent>> mapping);
5069

5170
void Any<TProperty>(Expression<Func<TContainer, TProperty>> property, System.Type idTypeOfMetaType, Action<IAnyMapper> mapping) where TProperty : class;
@@ -61,6 +80,17 @@ public interface IPlainPropertyContainerMapper<TContainer> : IBasePlainPropertyC
6180
public static class BasePlainPropertyContainerMapperExtensions
6281
{
6382
//6.0 TODO: Merge into IBasePlainPropertyContainerMapper<> interface
83+
/// <summary>
84+
/// Maps a generic <c>IDictionary&lt;string, object&gt;</c> property as a dynamic component.
85+
/// </summary>
86+
/// <param name="mapper">The mapper.</param>
87+
/// <param name="property">The property to map.</param>
88+
/// <param name="dynamicComponentTemplate">The template for the component. It should either be a (usually
89+
/// anonymous) type having the same properties than the component, or an
90+
/// <c>IDictionary&lt;string, System.Type&gt;</c> of property names with their type.</param>
91+
/// <param name="mapping">The mapping of the component.</param>
92+
/// <typeparam name="TContainer">The type of the mapped class.</typeparam>
93+
/// <typeparam name="TComponent">The type of the template.</typeparam>
6494
public static void Component<TContainer, TComponent>(
6595
this IBasePlainPropertyContainerMapper<TContainer> mapper,
6696
Expression<Func<TContainer, IDictionary<string, object>>> property,

src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/ComponentCustomizer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ public void Parent<TProperty>(Expression<Func<TComponent, TProperty>> parent) wh
3939

4040
public void Parent(string notVisiblePropertyOrFieldName, Action<IComponentParentMapper> parentMapping)
4141
{
42-
MemberInfo member = GetPropertyOrFieldMatchingNameOrThrow(notVisiblePropertyOrFieldName);
42+
MemberInfo member = GetRequiredPropertyOrFieldByName(notVisiblePropertyOrFieldName);
4343
AddCustomizer(m => m.Parent(member, parentMapping));
4444
}
4545

src/NHibernate/Mapping/ByCode/Impl/CustomizersImpl/DynamicComponentCustomizer.cs

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,40 @@
11
using System;
2+
using System.Reflection;
23

34
namespace NHibernate.Mapping.ByCode.Impl.CustomizersImpl
45
{
5-
public class DynamicComponentCustomizer<TComponent> : PropertyContainerCustomizer<TComponent>, IDynamicComponentMapper<TComponent>
6+
public class DynamicComponentCustomizer<TComponent> : PropertyContainerCustomizer<TComponent>,
7+
IDynamicComponentMapper<TComponent>
68
{
7-
public DynamicComponentCustomizer(IModelExplicitDeclarationsHolder explicitDeclarationsHolder, ICustomizersHolder customizersHolder, PropertyPath propertyPath)
9+
private readonly System.Type _componentType;
10+
11+
public DynamicComponentCustomizer(
12+
IModelExplicitDeclarationsHolder explicitDeclarationsHolder,
13+
ICustomizersHolder customizersHolder,
14+
PropertyPath propertyPath)
15+
: this(typeof(TComponent), explicitDeclarationsHolder, customizersHolder, propertyPath)
16+
{
17+
}
18+
19+
internal DynamicComponentCustomizer(
20+
System.Type componentType,
21+
IModelExplicitDeclarationsHolder explicitDeclarationsHolder,
22+
ICustomizersHolder customizersHolder,
23+
PropertyPath propertyPath)
824
: base(explicitDeclarationsHolder, customizersHolder, propertyPath)
925
{
1026
if (propertyPath == null)
1127
{
12-
throw new ArgumentNullException("propertyPath");
28+
throw new ArgumentNullException(nameof(propertyPath));
1329
}
30+
1431
if (explicitDeclarationsHolder == null)
1532
{
16-
throw new ArgumentNullException("explicitDeclarationsHolder");
33+
throw new ArgumentNullException(nameof(explicitDeclarationsHolder));
1734
}
18-
explicitDeclarationsHolder.AddAsDynamicComponent(propertyPath.LocalMember, typeof(TComponent));
35+
36+
_componentType = componentType;
37+
explicitDeclarationsHolder.AddAsDynamicComponent(propertyPath.LocalMember, _componentType);
1938
}
2039

2140
#region IDynamicComponentMapper<TComponent> Members
@@ -51,5 +70,15 @@ public void Unique(bool unique)
5170
}
5271

5372
#endregion
73+
74+
protected override MemberInfo GetRequiredPropertyOrFieldByName(string memberName)
75+
{
76+
var result = _componentType.GetPropertyOrFieldMatchingName(memberName);
77+
if (result == null)
78+
{
79+
throw new MappingException(string.Format("Member not found. The member '{0}' does not exists in type {1}", memberName, _componentType.FullName));
80+
}
81+
return result;
82+
}
5483
}
55-
}
84+
}

0 commit comments

Comments
 (0)