Skip to content

Fix support for nullable struct components #3554

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jun 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In all cases the caller was passing className as propertyOwnerClassName argument.

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
Loading