Skip to content

Commit e8be9d1

Browse files
BhaaLseNfredericDelaporte
authored andcommitted
NH-3114 - Collection inside Component cannot be mapped to a different table (#691)
* NH-3114 - create a few test cases The first test verifies mapping-by-code generates a correct mapping, while the second test inserts an entity with the first component set but currently also gets the second component collection initialized with the same contents after retrieving. This fixture is providing explicit column- and table-names to make sure everything ends up where we expect it. * NH-3114 - include the full property path in collection mappings Since PropertyPath is used as dictionary key during mapping/customization, only considering the local part may lead to customizations being applied to the wrong members (or being applied multiple times, leaving only the last invocation each as effective result). With the parent path included, collection mappings on nested contexts (such as components) now work properly even when the nested context is repeated on the same type (such as having multiple properties of the same component type on a root or subclass).
1 parent 83d438c commit e8be9d1

File tree

4 files changed

+226
-10
lines changed

4 files changed

+226
-10
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//------------------------------------------------------------------------------
2+
// <auto-generated>
3+
// This code was generated by AsyncGenerator.
4+
//
5+
// Changes to this file may cause incorrect behavior and will be lost if
6+
// the code is regenerated.
7+
// </auto-generated>
8+
//------------------------------------------------------------------------------
9+
10+
11+
using System;
12+
using System.Collections.Generic;
13+
using System.Linq;
14+
using NHibernate.Cfg.MappingSchema;
15+
using NHibernate.Mapping.ByCode;
16+
using NUnit.Framework;
17+
18+
namespace NHibernate.Test.NHSpecificTest.NH3114
19+
{
20+
using System.Threading.Tasks;
21+
[TestFixture]
22+
public class ExplicitByCodeFixtureAsync : TestCaseMappingByCode
23+
{
24+
protected override HbmMapping GetMappings()
25+
{
26+
var mapper = new ModelMapper();
27+
mapper.Class<Entity>(rc =>
28+
{
29+
rc.Id(i => i.Id, m => m.Generator(Generators.GuidComb));
30+
rc.Component(p => p.FirstComponent,
31+
m =>
32+
{
33+
m.Set(c => c.ComponentCollection,
34+
c => c.Table("FirstTable"),
35+
c => c.Element());
36+
m.Property(p => p.ComponentProperty, p => p.Column("FirstProperty"));
37+
});
38+
rc.Component(p => p.SecondComponent,
39+
m =>
40+
{
41+
m.Set(c => c.ComponentCollection,
42+
c => c.Table("SecondTable"),
43+
c => c.Element());
44+
m.Property(p => p.ComponentProperty, p => p.Column("SecondProperty"));
45+
});
46+
});
47+
48+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
49+
}
50+
51+
protected override void OnTearDown()
52+
{
53+
using (ISession session = OpenSession())
54+
using (ITransaction transaction = session.BeginTransaction())
55+
{
56+
session.Delete("from Entity");
57+
58+
session.Flush();
59+
transaction.Commit();
60+
}
61+
}
62+
63+
[Test]
64+
public async Task Component_WithSameType_ButDifferentTables_IsStoredInTheCorrectTableAndCollectionAsync()
65+
{
66+
Guid previouslySavedId;
67+
using (var session = OpenSession())
68+
{
69+
var entity = new Entity();
70+
entity.FirstComponent = new Component();
71+
entity.FirstComponent.ComponentProperty = "First";
72+
entity.FirstComponent.ComponentCollection = new List<string> { "FirstOne", "FirstTwo", "FirstThree" };
73+
// not setting entity.SecondComponent; it must not contain the contents of entity.FirstComponent later
74+
await (session.SaveOrUpdateAsync(entity));
75+
await (session.FlushAsync());
76+
previouslySavedId = entity.Id;
77+
}
78+
79+
using (var session = OpenSession())
80+
{
81+
var entity = await (session.GetAsync<Entity>(previouslySavedId));
82+
Assert.IsNotNull(entity);
83+
Assert.IsNotNull(entity.FirstComponent);
84+
Assert.AreEqual("First", entity.FirstComponent.ComponentProperty);
85+
CollectionAssert.AreEquivalent(new[] { "FirstOne", "FirstTwo", "FirstThree" }, entity.FirstComponent.ComponentCollection);
86+
//Assert.IsNull(entity.SecondComponent); // cannot check SecondComponent for null, since components are apparently always initialized
87+
Assert.AreNotEqual("First", entity.SecondComponent.ComponentProperty);
88+
CollectionAssert.IsEmpty(entity.SecondComponent.ComponentCollection);
89+
}
90+
}
91+
}
92+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
using System;
2+
using System.Collections.Generic;
3+
4+
namespace NHibernate.Test.NHSpecificTest.NH3114
5+
{
6+
public class Component
7+
{
8+
public virtual string ComponentProperty { get; set; }
9+
public virtual ICollection<string> ComponentCollection { get; set; } = new List<string>();
10+
}
11+
public class Entity
12+
{
13+
public virtual Guid Id { get; set; }
14+
public virtual Component FirstComponent { get; set; }
15+
public virtual Component SecondComponent { get; set; }
16+
}
17+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using NHibernate.Cfg.MappingSchema;
5+
using NHibernate.Mapping.ByCode;
6+
using NUnit.Framework;
7+
8+
namespace NHibernate.Test.NHSpecificTest.NH3114
9+
{
10+
[TestFixture]
11+
public class ExplicitByCodeFixture : TestCaseMappingByCode
12+
{
13+
protected override HbmMapping GetMappings()
14+
{
15+
var mapper = new ModelMapper();
16+
mapper.Class<Entity>(rc =>
17+
{
18+
rc.Id(i => i.Id, m => m.Generator(Generators.GuidComb));
19+
rc.Component(p => p.FirstComponent,
20+
m =>
21+
{
22+
m.Set(c => c.ComponentCollection,
23+
c => c.Table("FirstTable"),
24+
c => c.Element());
25+
m.Property(p => p.ComponentProperty, p => p.Column("FirstProperty"));
26+
});
27+
rc.Component(p => p.SecondComponent,
28+
m =>
29+
{
30+
m.Set(c => c.ComponentCollection,
31+
c => c.Table("SecondTable"),
32+
c => c.Element());
33+
m.Property(p => p.ComponentProperty, p => p.Column("SecondProperty"));
34+
});
35+
});
36+
37+
return mapper.CompileMappingForAllExplicitlyAddedEntities();
38+
}
39+
40+
protected override void OnTearDown()
41+
{
42+
using (ISession session = OpenSession())
43+
using (ITransaction transaction = session.BeginTransaction())
44+
{
45+
session.Delete("from Entity");
46+
47+
session.Flush();
48+
transaction.Commit();
49+
}
50+
}
51+
52+
[Test]
53+
public void Component_WithSameType_ButDifferentTables_ShouldBeMappedAccordingly()
54+
{
55+
var mappings = GetMappings();
56+
var modelMapping = mappings.Items.OfType<HbmClass>().FirstOrDefault();
57+
Assert.IsNotNull(modelMapping);
58+
var lists = modelMapping.Items.OfType<HbmComponent>();
59+
Assert.AreEqual(2, lists.Count());
60+
var firstMapping = lists.FirstOrDefault(l => l.Name == nameof(Entity.FirstComponent));
61+
Assert.IsNotNull(firstMapping);
62+
var firstMember = firstMapping.Properties.OfType<HbmProperty>().FirstOrDefault(p => p.Name == nameof(Component.ComponentProperty));
63+
Assert.IsNotNull(firstMember);
64+
Assert.AreEqual("FirstProperty", firstMember.column);
65+
var firstCollection = firstMapping.Items.OfType<HbmSet>().FirstOrDefault();
66+
Assert.IsNotNull(firstCollection);
67+
Assert.AreEqual("FirstTable", firstCollection.Table);
68+
var secondMapping = lists.FirstOrDefault(l => l.Name == nameof(Entity.SecondComponent));
69+
Assert.IsNotNull(secondMapping);
70+
var secondMember = secondMapping.Properties.OfType<HbmProperty>().FirstOrDefault(p => p.Name == nameof(Component.ComponentProperty));
71+
Assert.IsNotNull(secondMember);
72+
Assert.AreEqual("SecondProperty", secondMember.column);
73+
var secondCollection = secondMapping.Items.OfType<HbmSet>().FirstOrDefault();
74+
Assert.IsNotNull(secondCollection);
75+
Assert.AreEqual("SecondTable", secondCollection.Table);
76+
}
77+
78+
[Test]
79+
public void Component_WithSameType_ButDifferentTables_IsStoredInTheCorrectTableAndCollection()
80+
{
81+
Guid previouslySavedId;
82+
using (var session = OpenSession())
83+
{
84+
var entity = new Entity();
85+
entity.FirstComponent = new Component();
86+
entity.FirstComponent.ComponentProperty = "First";
87+
entity.FirstComponent.ComponentCollection = new List<string> { "FirstOne", "FirstTwo", "FirstThree" };
88+
// not setting entity.SecondComponent; it must not contain the contents of entity.FirstComponent later
89+
session.SaveOrUpdate(entity);
90+
session.Flush();
91+
previouslySavedId = entity.Id;
92+
}
93+
94+
using (var session = OpenSession())
95+
{
96+
var entity = session.Get<Entity>(previouslySavedId);
97+
Assert.IsNotNull(entity);
98+
Assert.IsNotNull(entity.FirstComponent);
99+
Assert.AreEqual("First", entity.FirstComponent.ComponentProperty);
100+
CollectionAssert.AreEquivalent(new[] { "FirstOne", "FirstTwo", "FirstThree" }, entity.FirstComponent.ComponentCollection);
101+
//Assert.IsNull(entity.SecondComponent); // cannot check SecondComponent for null, since components are apparently always initialized
102+
Assert.AreNotEqual("First", entity.SecondComponent.ComponentProperty);
103+
CollectionAssert.IsEmpty(entity.SecondComponent.ComponentCollection);
104+
}
105+
}
106+
}
107+
}

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

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ public void OneToOne<TProperty>(string notVisiblePropertyOrFieldName, Action<IOn
157157
if (typeof(TProperty) != propertyOrFieldType)
158158
{
159159
throw new MappingException(string.Format("Wrong relation type. For the property/field '{0}' of {1} was expected a one-to-one with {2} but was {3}",
160-
notVisiblePropertyOrFieldName, typeof (TEntity).FullName, typeof (TProperty).Name, propertyOrFieldType.Name));
160+
notVisiblePropertyOrFieldName, typeof(TEntity).FullName, typeof(TProperty).Name, propertyOrFieldType.Name));
161161
}
162162
var memberOf = member.GetMemberFromReflectedType(typeof(TEntity));
163163
RegisterOneToOneMapping(mapping, member, memberOf);
@@ -222,7 +222,7 @@ protected void RegisterSetMapping<TElement>(Action<ISetPropertiesMapper<TEntity,
222222
{
223223
foreach (var member in members)
224224
{
225-
collectionMapping(new SetPropertiesCustomizer<TEntity, TElement>(explicitDeclarationsHolder, new PropertyPath(null, member), CustomizersHolder));
225+
collectionMapping(new SetPropertiesCustomizer<TEntity, TElement>(explicitDeclarationsHolder, new PropertyPath(PropertyPath, member), CustomizersHolder));
226226
mapping(new CollectionElementRelationCustomizer<TElement>(explicitDeclarationsHolder, new PropertyPath(PropertyPath, member), CustomizersHolder));
227227
}
228228
}
@@ -249,7 +249,7 @@ protected void RegisterBagMapping<TElement>(Action<IBagPropertiesMapper<TEntity,
249249
{
250250
foreach (var member in members)
251251
{
252-
collectionMapping(new BagPropertiesCustomizer<TEntity, TElement>(explicitDeclarationsHolder, new PropertyPath(null, member), CustomizersHolder));
252+
collectionMapping(new BagPropertiesCustomizer<TEntity, TElement>(explicitDeclarationsHolder, new PropertyPath(PropertyPath, member), CustomizersHolder));
253253
mapping(new CollectionElementRelationCustomizer<TElement>(explicitDeclarationsHolder, new PropertyPath(PropertyPath, member), CustomizersHolder));
254254
}
255255
}
@@ -276,7 +276,7 @@ protected void RegisterListMapping<TElement>(Action<IListPropertiesMapper<TEntit
276276
{
277277
foreach (var member in members)
278278
{
279-
collectionMapping(new ListPropertiesCustomizer<TEntity, TElement>(explicitDeclarationsHolder, new PropertyPath(null, member), CustomizersHolder));
279+
collectionMapping(new ListPropertiesCustomizer<TEntity, TElement>(explicitDeclarationsHolder, new PropertyPath(PropertyPath, member), CustomizersHolder));
280280
mapping(new CollectionElementRelationCustomizer<TElement>(explicitDeclarationsHolder, new PropertyPath(PropertyPath, member), CustomizersHolder));
281281
}
282282
}
@@ -338,11 +338,11 @@ protected virtual void RegisterIdBagMapping<TElement>(Expression<Func<TEntity, I
338338
RegisterIdBagMapping(collectionMapping, mapping, memberOf);
339339
}
340340

341-
protected virtual void RegisterIdBagMapping<TElement>(Action<IIdBagPropertiesMapper<TEntity, TElement>> collectionMapping, Action<ICollectionElementRelation<TElement>> mapping,params MemberInfo[] members)
341+
protected virtual void RegisterIdBagMapping<TElement>(Action<IIdBagPropertiesMapper<TEntity, TElement>> collectionMapping, Action<ICollectionElementRelation<TElement>> mapping, params MemberInfo[] members)
342342
{
343343
foreach (var member in members)
344344
{
345-
collectionMapping(new IdBagPropertiesCustomizer<TEntity, TElement>(explicitDeclarationsHolder, new PropertyPath(null, member), CustomizersHolder));
345+
collectionMapping(new IdBagPropertiesCustomizer<TEntity, TElement>(explicitDeclarationsHolder, new PropertyPath(PropertyPath, member), CustomizersHolder));
346346
mapping(new CollectionElementRelationCustomizer<TElement>(explicitDeclarationsHolder, new PropertyPath(PropertyPath, member), CustomizersHolder));
347347
}
348348
}
@@ -361,11 +361,11 @@ private static void AssertCollectionElementType<TElement>(string propertyOrField
361361
{
362362
System.Type collectionElementType = memberInfo.GetPropertyOrFieldType().DetermineCollectionElementType();
363363

364-
if (typeof (TElement) != collectionElementType)
364+
if (typeof(TElement) != collectionElementType)
365365
{
366366
var message = string.Format(
367367
"Wrong collection element type. For the property/field '{0}' of {1} was expected a generic collection of {2} but was {3}",
368-
propertyOrFieldName, typeof (TEntity).FullName, typeof (TElement).Name,
368+
propertyOrFieldName, typeof(TEntity).FullName, typeof(TElement).Name,
369369
collectionElementType != null ? collectionElementType.Name : "unknown");
370370
throw new MappingException(message);
371371
}
@@ -414,7 +414,7 @@ public void Map<TKey, TElement>(string notVisiblePropertyOrFieldName, Action<IMa
414414
if (!typeof(TElement).Equals(collectionElementType) || !typeof(TKey).Equals(keyType))
415415
{
416416
throw new MappingException(string.Format("Wrong collection element type. For the property/field '{0}' of {1} was expected a dictionary of {2}/{3} but was {4}/{5}",
417-
notVisiblePropertyOrFieldName, typeof(TEntity).FullName, typeof(TKey).Name, keyType.Name ,typeof(TElement).Name, collectionElementType.Name));
417+
notVisiblePropertyOrFieldName, typeof(TEntity).FullName, typeof(TKey).Name, keyType.Name, typeof(TElement).Name, collectionElementType.Name));
418418
}
419419
MemberInfo memberOf = member.GetMemberFromReflectedType(typeof(TEntity));
420420
RegisterMapMapping<TKey, TElement>(collectionMapping, keyMapping, mapping, member, memberOf);
@@ -505,4 +505,4 @@ public static MemberInfo GetPropertyOrFieldMatchingNameOrThrow(string memberName
505505
return result;
506506
}
507507
}
508-
}
508+
}

0 commit comments

Comments
 (0)