Skip to content

Commit

Permalink
Fix support for nullable struct components (#3554)
Browse files Browse the repository at this point in the history
  • Loading branch information
hazzik committed Jun 6, 2024
1 parent f9fcdcd commit 3a651d0
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 76 deletions.
49 changes: 49 additions & 0 deletions src/NHibernate.Test/Async/NHSpecificTest/NH1284/Fixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by AsyncGenerator.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------


using NUnit.Framework;

namespace NHibernate.Test.NHSpecificTest.NH1284
{
using System.Threading.Tasks;
[TestFixture]
public class FixtureAsync : BugTestCase
{
protected override void OnTearDown()
{
using var s = OpenSession();
using var tx = s.BeginTransaction();
s.Delete("from Person");
tx.Commit();
}

[Test]
public async Task EmptyValueTypeComponentAsync()
{
Person jimmy;
using (var s = OpenSession())
using (var tx = s.BeginTransaction())
{
var p = new Person("Jimmy Hendrix");
await (s.SaveAsync(p));
await (tx.CommitAsync());
}

using (var s = OpenSession())
using (var tx = s.BeginTransaction())
{
jimmy = await (s.GetAsync<Person>("Jimmy Hendrix"));
await (tx.CommitAsync());
}

Assert.That(jimmy.Address, Is.Null);
}
}
}
32 changes: 17 additions & 15 deletions src/NHibernate.Test/NHSpecificTest/NH1284/Fixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,35 +2,37 @@

namespace NHibernate.Test.NHSpecificTest.NH1284
{
[TestFixture, Ignore("Not supported yet.")]
[TestFixture]
public class Fixture : BugTestCase
{
protected override void OnTearDown()
{
using var s = OpenSession();
using var tx = s.BeginTransaction();
s.Delete("from Person");
tx.Commit();
}

[Test]
public void EmptyValueTypeComponent()
{
Person jimmy;
using (ISession s = OpenSession())
using (ITransaction tx = s.BeginTransaction())
using (var s = OpenSession())
using (var tx = s.BeginTransaction())
{
Person p = new Person("Jimmy Hendrix");
var p = new Person("Jimmy Hendrix");
s.Save(p);
tx.Commit();
}

using (ISession s = OpenSession())
using (ITransaction tx = s.BeginTransaction())
using (var s = OpenSession())
using (var tx = s.BeginTransaction())
{
jimmy = (Person)s.Get(typeof(Person), "Jimmy Hendrix");
jimmy = s.Get<Person>("Jimmy Hendrix");
tx.Commit();
}
Assert.IsFalse(jimmy.Address.HasValue);

using (ISession s = OpenSession())
using (ITransaction tx = s.BeginTransaction())
{
s.Delete("from Person");
tx.Commit();
}
Assert.That(jimmy.Address, Is.Null);
}
}
}
}
2 changes: 1 addition & 1 deletion src/NHibernate.Test/NHSpecificTest/NH1284/Mappings.hbm.xml
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
<property name="GmtOffset"/>
</component>
</class>
</hibernate-mapping>
</hibernate-mapping>
4 changes: 2 additions & 2 deletions src/NHibernate/Cfg/XmlHbmBinding/ClassIdBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ private void CreateIdentifierProperty(HbmId idSchema, PersistentClass rootClass,
if (idSchema.name != null)
{
string access = idSchema.access ?? mappings.DefaultAccess;
id.SetTypeUsingReflection(rootClass.MappedClass == null ? null : rootClass.MappedClass.AssemblyQualifiedName, idSchema.name, access);
id.SetTypeUsingReflection(rootClass.MappedClass?.AssemblyQualifiedName, idSchema.name, access);

var property = new Property(id) { Name = idSchema.name };

Expand Down Expand Up @@ -85,4 +85,4 @@ private static void BindUnsavedValue(HbmId idSchema, SimpleValue id)
id.NullValue = idSchema.unsavedvalue ?? (id.IdentifierGeneratorStrategy == "assigned" ? "undefined" : null);
}
}
}
}
93 changes: 37 additions & 56 deletions src/NHibernate/Cfg/XmlHbmBinding/PropertiesBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using NHibernate.Mapping;
using System;
using NHibernate.Util;
using Array = System.Array;

namespace NHibernate.Cfg.XmlHbmBinding
{
Expand All @@ -14,7 +13,6 @@ public class PropertiesBinder : ClassBinder
private readonly Component component;
private readonly string entityName;
private readonly System.Type mappedClass;
private readonly string className;
private readonly bool componetDefaultNullable;
private readonly string propertyBasePath;

Expand All @@ -38,7 +36,6 @@ public PropertiesBinder(Mappings mappings, PersistentClass persistentClass)
this.persistentClass = persistentClass;
entityName = persistentClass.EntityName;
propertyBasePath = entityName;
className = persistentClass.ClassName;
mappedClass = persistentClass.MappedClass;
componetDefaultNullable = true;
component = null;
Expand All @@ -50,7 +47,6 @@ public PropertiesBinder(Mappings mappings, Component component, string className
persistentClass = component.Owner;
this.component = component;
entityName = className;
this.className = component.ComponentClassName;
mappedClass = component.ComponentClass;
propertyBasePath = path;
componetDefaultNullable = isNullable;
Expand Down Expand Up @@ -87,26 +83,14 @@ public void Bind(IEnumerable<IEntityPropertyMapping> properties, Table table, ID

string propertyName = entityPropertyMapping.Name;

ICollectionPropertiesMapping collectionMapping;
HbmManyToOne manyToOneMapping;
HbmAny anyMapping;
HbmOneToOne oneToOneMapping;
HbmProperty propertyMapping;
HbmComponent componentMapping;
HbmDynamicComponent dynamicComponentMapping;
HbmNestedCompositeElement nestedCompositeElementMapping;
HbmKeyProperty keyPropertyMapping;
HbmKeyManyToOne keyManyToOneMapping;
HbmProperties propertiesMapping;

if ((propertyMapping = entityPropertyMapping as HbmProperty) != null)
if (entityPropertyMapping is HbmProperty propertyMapping)
{
var value = new SimpleValue(table);
new ValuePropertyBinder(value, Mappings).BindSimpleValue(propertyMapping, propertyName, true);
property = CreateProperty(entityPropertyMapping, className, value, inheritedMetas);
property = CreateProperty(entityPropertyMapping, mappedClass, value, inheritedMetas);
BindValueProperty(propertyMapping, property);
}
else if ((collectionMapping = entityPropertyMapping as ICollectionPropertiesMapping) != null)
else if (entityPropertyMapping is ICollectionPropertiesMapping collectionMapping)
{
var collectionBinder = new CollectionBinder(Mappings);
string propertyPath = propertyName == null ? null : StringHelper.Qualify(propertyBasePath, propertyName);
Expand All @@ -116,59 +100,59 @@ public void Bind(IEnumerable<IEntityPropertyMapping> properties, Table table, ID

mappings.AddCollection(collection);

property = CreateProperty(collectionMapping, className, collection, inheritedMetas);
property = CreateProperty(collectionMapping, mappedClass, collection, inheritedMetas);
BindCollectionProperty(collectionMapping, property);
}
else if ((propertiesMapping = entityPropertyMapping as HbmProperties) != null)
else if (entityPropertyMapping is HbmProperties propertiesMapping)
{
var subpath = propertyName == null ? null : StringHelper.Qualify(propertyBasePath, propertyName);
var value = CreateNewComponent(table);
BindComponent(propertiesMapping, value, null, entityName, subpath, componetDefaultNullable, inheritedMetas);
property = CreateProperty(entityPropertyMapping, className, value, inheritedMetas);
property = CreateProperty(entityPropertyMapping, mappedClass, value, inheritedMetas);
BindComponentProperty(propertiesMapping, property, value);
}
else if ((manyToOneMapping = entityPropertyMapping as HbmManyToOne) != null)
else if (entityPropertyMapping is HbmManyToOne manyToOneMapping)
{
var value = new ManyToOne(table);
BindManyToOne(manyToOneMapping, value, propertyName, true);
property = CreateProperty(entityPropertyMapping, className, value, inheritedMetas);
property = CreateProperty(entityPropertyMapping, mappedClass, value, inheritedMetas);
BindManyToOneProperty(manyToOneMapping, property);
}
else if ((componentMapping = entityPropertyMapping as HbmComponent) != null)
else if (entityPropertyMapping is HbmComponent componentMapping)
{
string subpath = propertyName == null ? null : StringHelper.Qualify(propertyBasePath, propertyName);
var value = CreateNewComponent(table);
// NH: Modified from H2.1 to allow specifying the type explicitly using class attribute
System.Type reflectedClass = mappedClass == null ? null : GetPropertyType(componentMapping.Class, mappedClass, propertyName, componentMapping.Access);
BindComponent(componentMapping, value, reflectedClass, entityName, subpath, componetDefaultNullable, inheritedMetas);
property = CreateProperty(entityPropertyMapping, className, value, inheritedMetas);
property = CreateProperty(entityPropertyMapping, mappedClass, value, inheritedMetas);
BindComponentProperty(componentMapping, property, value);
}
else if ((oneToOneMapping = entityPropertyMapping as HbmOneToOne) != null)
else if (entityPropertyMapping is HbmOneToOne oneToOneMapping)
{
var value = new OneToOne(table, persistentClass);
BindOneToOne(oneToOneMapping, value);
property = CreateProperty(entityPropertyMapping, className, value, inheritedMetas);
property = CreateProperty(entityPropertyMapping, mappedClass, value, inheritedMetas);
BindOneToOneProperty(oneToOneMapping, property);
}
else if ((dynamicComponentMapping = entityPropertyMapping as HbmDynamicComponent) != null)
else if (entityPropertyMapping is HbmDynamicComponent dynamicComponentMapping)
{
string subpath = propertyName == null ? null : StringHelper.Qualify(propertyBasePath, propertyName);
var value = CreateNewComponent(table);
// NH: Modified from H2.1 to allow specifying the type explicitly using class attribute
System.Type reflectedClass = mappedClass == null ? null : GetPropertyType(dynamicComponentMapping.Class, mappedClass, propertyName, dynamicComponentMapping.Access);
BindComponent(dynamicComponentMapping, value, reflectedClass, entityName, subpath, componetDefaultNullable, inheritedMetas);
property = CreateProperty(entityPropertyMapping, className, value, inheritedMetas);
property = CreateProperty(entityPropertyMapping, mappedClass, value, inheritedMetas);
BindComponentProperty(dynamicComponentMapping, property, value);
}
else if ((anyMapping = entityPropertyMapping as HbmAny) != null)
else if (entityPropertyMapping is HbmAny anyMapping)
{
var value = new Any(table);
BindAny(anyMapping, value, true);
property = CreateProperty(entityPropertyMapping, className, value, inheritedMetas);
property = CreateProperty(entityPropertyMapping, mappedClass, value, inheritedMetas);
BindAnyProperty(anyMapping, property);
}
else if ((nestedCompositeElementMapping = entityPropertyMapping as HbmNestedCompositeElement) != null)
else if (entityPropertyMapping is HbmNestedCompositeElement nestedCompositeElementMapping)
{
if (component == null)
{
Expand All @@ -179,19 +163,19 @@ public void Bind(IEnumerable<IEntityPropertyMapping> properties, Table table, ID
// NH: Modified from H2.1 to allow specifying the type explicitly using class attribute
System.Type reflectedClass = mappedClass == null ? null : GetPropertyType(nestedCompositeElementMapping.Class, mappedClass, propertyName, nestedCompositeElementMapping.access);
BindComponent(nestedCompositeElementMapping, value, reflectedClass, entityName, subpath, componetDefaultNullable, inheritedMetas);
property = CreateProperty(entityPropertyMapping, className, value, inheritedMetas);
property = CreateProperty(entityPropertyMapping, mappedClass, value, inheritedMetas);
}
else if ((keyPropertyMapping = entityPropertyMapping as HbmKeyProperty) != null)
else if (entityPropertyMapping is HbmKeyProperty keyPropertyMapping)
{
var value = new SimpleValue(table);
new ValuePropertyBinder(value, Mappings).BindSimpleValue(keyPropertyMapping, propertyName, componetDefaultNullable);
property = CreateProperty(entityPropertyMapping, className, value, inheritedMetas);
property = CreateProperty(entityPropertyMapping, mappedClass, value, inheritedMetas);
}
else if ((keyManyToOneMapping = entityPropertyMapping as HbmKeyManyToOne) != null)
else if (entityPropertyMapping is HbmKeyManyToOne keyManyToOneMapping)
{
var value = new ManyToOne(table);
BindKeyManyToOne(keyManyToOneMapping, value, propertyName, componetDefaultNullable);
property = CreateProperty(entityPropertyMapping, className, value, inheritedMetas);
property = CreateProperty(entityPropertyMapping, mappedClass, value, inheritedMetas);
}

if (property != null)
Expand Down Expand Up @@ -402,30 +386,27 @@ private void BindCollectionProperty(ICollectionPropertiesMapping collectionMappi
property.Cascade = collectionMapping.Cascade ?? mappings.DefaultCascade;
}

private Property CreateProperty(IEntityPropertyMapping propertyMapping, string propertyOwnerClassName, IValue value, IDictionary<string, MetaAttribute> inheritedMetas)
private Property CreateProperty(IEntityPropertyMapping propertyMapping, System.Type propertyOwnerType, IValue value, IDictionary<string, MetaAttribute> inheritedMetas)
{
var type = propertyOwnerType?.UnwrapIfNullable();
if (string.IsNullOrEmpty(propertyMapping.Name))
{
throw new MappingException("A property mapping must define the name attribute [" + propertyOwnerClassName + "]");
}
throw new MappingException("A property mapping must define the name attribute [" + type + "]");

var propertyAccessorName = GetPropertyAccessorName(propertyMapping.Access);

if (!string.IsNullOrEmpty(propertyOwnerClassName) && value.IsSimpleValue)
value.SetTypeUsingReflection(propertyOwnerClassName, propertyMapping.Name, propertyAccessorName);
if (type != null && value.IsSimpleValue)
value.SetTypeUsingReflection(type.AssemblyQualifiedName, propertyMapping.Name, propertyAccessorName);

var property = new Property
{
Name = propertyMapping.Name,
PropertyAccessorName = propertyAccessorName,
Value = value,
IsLazy = propertyMapping.IsLazyProperty,
LazyGroup = propertyMapping.GetLazyGroup(),
IsOptimisticLocked = propertyMapping.OptimisticLock,
MetaAttributes = GetMetas(propertyMapping, inheritedMetas)
};

return property;
return new Property
{
Name = propertyMapping.Name,
PropertyAccessorName = propertyAccessorName,
Value = value,
IsLazy = propertyMapping.IsLazyProperty,
LazyGroup = propertyMapping.GetLazyGroup(),
IsOptimisticLocked = propertyMapping.OptimisticLock,
MetaAttributes = GetMetas(propertyMapping, inheritedMetas)
};
}

private string GetPropertyAccessorName(string propertyMappedAccessor)
Expand Down
5 changes: 3 additions & 2 deletions src/NHibernate/Tuple/Component/PocoComponentTuplizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using NHibernate.Bytecode.Lightweight;
using NHibernate.Intercept;
using NHibernate.Properties;
using NHibernate.Util;

namespace NHibernate.Tuple.Component
{
Expand Down Expand Up @@ -155,12 +156,12 @@ protected internal override IInstantiator BuildInstantiator(Mapping.Component co

protected internal override IGetter BuildGetter(Mapping.Component component, Mapping.Property prop)
{
return prop.GetGetter(component.ComponentClass);
return prop.GetGetter(component.ComponentClass.UnwrapIfNullable());
}

protected internal override ISetter BuildSetter(Mapping.Component component, Mapping.Property prop)
{
return prop.GetSetter(component.ComponentClass);
return prop.GetSetter(component.ComponentClass.UnwrapIfNullable());
}

protected void SetReflectionOptimizer()
Expand Down

0 comments on commit 3a651d0

Please sign in to comment.