Skip to content

Fix NRE with merge and ignore not-found #2657

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 3 commits into from
Feb 7, 2021
Merged
Show file tree
Hide file tree
Changes from 2 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
69 changes: 69 additions & 0 deletions src/NHibernate.Test/Async/NHSpecificTest/GH2627/Fixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
//------------------------------------------------------------------------------
// <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 System.Linq;
using NUnit.Framework;
using NHibernate.Linq;

namespace NHibernate.Test.NHSpecificTest.GH2627
{
using System.Threading.Tasks;
[TestFixture]
public class FixtureAsync : BugTestCase
{
protected override void OnSetUp()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
var e1 = new Entity {Name = "Bob"};
session.Save(e1);

transaction.Commit();
}
}

protected override void OnTearDown()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
session.CreateQuery("delete from Child").ExecuteUpdate();
session.CreateQuery("delete from System.Object").ExecuteUpdate();

transaction.Commit();
}
}

[Test]
public async Task NullRefInMergeAsync()
{
Child child;
using (var session = OpenSession())
using (var t = session.BeginTransaction())
{
child = new Child
{
Parent = await (session.Query<Entity>().FirstOrDefaultAsync()),
Name = "John"
};

await (t.CommitAsync());
}

using (var session = OpenSession())
using (var t = session.BeginTransaction())
{
await (session.MergeAsync(child));
await (t.CommitAsync());
}
}
}
}
11 changes: 11 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/GH2627/Child.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace NHibernate.Test.NHSpecificTest.GH2627
{
public class Child
{
public virtual Guid Id { get; set; }
public virtual Entity Parent { get; set; }
public virtual string Name { get; set; }
}
}
10 changes: 10 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/GH2627/Entity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;

namespace NHibernate.Test.NHSpecificTest.GH2627
{
public class Entity
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}
}
57 changes: 57 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/GH2627/Fixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System.Linq;
using NUnit.Framework;

namespace NHibernate.Test.NHSpecificTest.GH2627
{
[TestFixture]
public class Fixture : BugTestCase
{
protected override void OnSetUp()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
var e1 = new Entity {Name = "Bob"};
session.Save(e1);

transaction.Commit();
}
}

protected override void OnTearDown()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
session.CreateQuery("delete from Child").ExecuteUpdate();
session.CreateQuery("delete from System.Object").ExecuteUpdate();

transaction.Commit();
}
}

[Test]
public void NullRefInMerge()
{
Child child;
using (var session = OpenSession())
using (var t = session.BeginTransaction())
{
child = new Child
{
Parent = session.Query<Entity>().FirstOrDefault(),
Name = "John"
};

t.Commit();
}

using (var session = OpenSession())
using (var t = session.BeginTransaction())
{
session.Merge(child);
t.Commit();
}
}
}
}
16 changes: 16 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/GH2627/Mappings.hbm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernate.Test"
namespace="NHibernate.Test.NHSpecificTest.GH2627">

<class name="Entity">
<id name="Id" generator="guid.comb"/>
<property name="Name"/>
</class>

<class name="Child">
<id name="Id" generator="guid.comb"/>
<property name="Name"/>
<many-to-one name="Parent" not-found="ignore"/>
</class>

</hibernate-mapping>
1 change: 0 additions & 1 deletion src/NHibernate/Async/Type/ManyToOneType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
using System;
using System.Data.Common;
using NHibernate.Engine;
using NHibernate.Persister.Entity;
using NHibernate.SqlTypes;
using NHibernate.Util;

Expand Down
2 changes: 1 addition & 1 deletion src/NHibernate/Cfg/XmlHbmBinding/ClassCompositeIdBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ private void BindComponent(System.Type reflectedClass, string path, HbmComposite

if (keyManyToOneSchema != null)
{
var manyToOne = new ManyToOne(compositeId.Table);
var manyToOne = new ManyToOne(compositeId.Table, compositeId.Owner);

string propertyName = keyManyToOneSchema.name == null ? null : StringHelper.Qualify(path, keyManyToOneSchema.name);

Expand Down
6 changes: 3 additions & 3 deletions src/NHibernate/Cfg/XmlHbmBinding/CollectionBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -655,13 +655,13 @@ private void BindMapSecondPass(HbmMap mapMapping, Map model,
}
else if ((indexManyToManyMapping = mapMapping.Item as HbmIndexManyToMany) != null)
{
var manyToOne = new ManyToOne(model.CollectionTable);
var manyToOne = new ManyToOne(model.CollectionTable, model.Owner);
BindIndexManyToMany(indexManyToManyMapping, manyToOne, IndexedCollection.DefaultIndexColumnName, model.IsOneToMany);
model.Index = manyToOne;
}
else if ((mapKeyManyToManyMapping = mapMapping.Item as HbmMapKeyManyToMany) != null)
{
var manyToOne = new ManyToOne(model.CollectionTable);
var manyToOne = new ManyToOne(model.CollectionTable, model.Owner);
BindMapKeyManyToMany(mapKeyManyToManyMapping, manyToOne, IndexedCollection.DefaultIndexColumnName, model.IsOneToMany);
model.Index = manyToOne;
}
Expand Down Expand Up @@ -813,7 +813,7 @@ private void BindCollectionSecondPass(ICollectionPropertiesMapping collectionMap

private void BindManyToMany(HbmManyToMany manyToManyMapping, Mapping.Collection model)
{
var manyToMany = new ManyToOne(model.CollectionTable);
var manyToMany = new ManyToOne(model.CollectionTable, model.Owner);
model.Element = manyToMany;
new ValuePropertyBinder(manyToMany, Mappings).BindSimpleValue(manyToManyMapping,
Mapping.Collection.DefaultElementColumnName, false);
Expand Down
4 changes: 2 additions & 2 deletions src/NHibernate/Cfg/XmlHbmBinding/PropertiesBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public void Bind(IEnumerable<IEntityPropertyMapping> properties, Table table, ID
}
else if ((manyToOneMapping = entityPropertyMapping as HbmManyToOne) != null)
{
var value = new ManyToOne(table);
var value = new ManyToOne(table, persistentClass);
BindManyToOne(manyToOneMapping, value, propertyName, true);
property = CreateProperty(entityPropertyMapping, className, value, inheritedMetas);
BindManyToOneProperty(manyToOneMapping, property);
Expand Down Expand Up @@ -189,7 +189,7 @@ public void Bind(IEnumerable<IEntityPropertyMapping> properties, Table table, ID
}
else if ((keyManyToOneMapping = entityPropertyMapping as HbmKeyManyToOne) != null)
{
var value = new ManyToOne(table);
var value = new ManyToOne(table, persistentClass);
BindKeyManyToOne(keyManyToOneMapping, value, propertyName, componetDefaultNullable);
property = CreateProperty(entityPropertyMapping, className, value, inheritedMetas);
}
Expand Down
22 changes: 18 additions & 4 deletions src/NHibernate/Mapping/ManyToOne.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,24 @@ namespace NHibernate.Mapping
[Serializable]
public class ManyToOne : ToOne
{
private readonly string _entityName;

/// <summary>
/// Construct a many-to-one mapping.
/// </summary>
/// <param name="table">The table.</param>
/// <param name="owner">The association owner.</param>
public ManyToOne(Table table, PersistentClass owner) : base(table)
{
_entityName = owner?.EntityName;
}

/// <summary>
///
/// </summary>
/// <param name="table"></param>
public ManyToOne(Table table) : base(table)
[Obsolete("Use overload with owner instead.")]
public ManyToOne(Table table) : this(table, null)
{
}

Expand Down Expand Up @@ -41,7 +54,7 @@ public bool IsLogicalOneToOne
get { return isLogicalOneToOne; }
set { isLogicalOneToOne = value; }
}

public string PropertyName { get; set; }

private IType type;
Expand All @@ -51,8 +64,9 @@ public override IType Type
{
if (type == null)
{
type =
TypeFactory.ManyToOne(ReferencedEntityName, ReferencedPropertyName, IsLazy, UnwrapProxy, IsIgnoreNotFound, isLogicalOneToOne, PropertyName);
type = TypeFactory.ManyToOne(
ReferencedEntityName, ReferencedPropertyName, IsLazy, UnwrapProxy,
IsIgnoreNotFound, isLogicalOneToOne, _entityName, PropertyName);
}
return type;
}
Expand Down
4 changes: 3 additions & 1 deletion src/NHibernate/Mapping/OneToMany.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ public OneToMany(PersistentClass owner)

private EntityType EntityType
{
get { return TypeFactory.ManyToOne(ReferencedEntityName, null, false, false, IsIgnoreNotFound, false, null); }
get { return TypeFactory.ManyToOne(
ReferencedEntityName, null, false, false,
IsIgnoreNotFound, false, null, null); }
}

public bool IsIgnoreNotFound
Expand Down
42 changes: 35 additions & 7 deletions src/NHibernate/Type/ManyToOneType.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
using System;
using System.Data.Common;
using NHibernate.Engine;
using NHibernate.Persister.Entity;
using NHibernate.SqlTypes;
using NHibernate.Util;

Expand All @@ -15,6 +14,7 @@ public partial class ManyToOneType : EntityType
{
private readonly bool ignoreNotFound;
private readonly bool isLogicalOneToOne;
private readonly string _entityName;

public ManyToOneType(string className)
: this(className, false)
Expand All @@ -30,18 +30,41 @@ public ManyToOneType(string className, bool lazy)
}

//Since 5.3
[Obsolete("Use Constructor with property name")]
[Obsolete("Use Constructor with owner property name and property name")]
public ManyToOneType(string entityName, string uniqueKeyPropertyName, bool lazy, bool unwrapProxy, bool ignoreNotFound, bool isLogicalOneToOne)
: this(entityName, uniqueKeyPropertyName, lazy, unwrapProxy, ignoreNotFound, isLogicalOneToOne, null)
: this(entityName, uniqueKeyPropertyName, lazy, unwrapProxy, ignoreNotFound, isLogicalOneToOne, null, null)
{
}

//Since 5.3.6
[Obsolete("Use Constructor with owner entity name")]
public ManyToOneType(string entityName, string uniqueKeyPropertyName, bool lazy, bool unwrapProxy, bool ignoreNotFound, bool isLogicalOneToOne, string propertyName)
: base(entityName, uniqueKeyPropertyName, !lazy, unwrapProxy)
: this(entityName, uniqueKeyPropertyName, lazy, unwrapProxy, ignoreNotFound, isLogicalOneToOne, null, propertyName)
{
}

/// <summary>
/// Build a many-to-one relation.
/// </summary>
/// <param name="referencedEntityName">The referenced entity name.</param>
/// <param name="uniqueKeyPropertyName">The property-ref name, or <see langword="null" /> if we reference
/// the PK of the associated entity.</param>
/// <param name="lazy">Is the association lazily loaded?</param>
/// <param name="unwrapProxy">Is unwrapping of proxies allowed for this association; unwrapping says to return
/// the "implementation target" of lazy proxies; typically only possible with lazy="no-proxy".</param>
/// <param name="ignoreNotFound"><see langword="false" /> for throwing an exception if the referenced
/// entity is missing in the database, <see langword="true" /> for yielding <see langword="null" /> instead.</param>
/// <param name="isLogicalOneToOne"></param>
/// <param name="entityName">The property owner entity name.</param>
/// <param name="propertyName">The property name.</param>
public ManyToOneType(string referencedEntityName, string uniqueKeyPropertyName, bool lazy, bool unwrapProxy,
bool ignoreNotFound, bool isLogicalOneToOne, string entityName, string propertyName)
: base(referencedEntityName, uniqueKeyPropertyName, !lazy, unwrapProxy)
{
this.ignoreNotFound = ignoreNotFound;
this.isLogicalOneToOne = isLogicalOneToOne;
PropertyName = propertyName;
_entityName = entityName;
}

public override int GetColumnSpan(IMapping mapping)
Expand Down Expand Up @@ -162,11 +185,16 @@ private bool IsIdentifier(object value, ISessionImplementor session)

public override bool IsNull(object owner, ISessionImplementor session)
{
if (IsNullable && !string.IsNullOrEmpty(PropertyName))
if (IsNullable && !string.IsNullOrEmpty(PropertyName) && owner != null)
{
EntityEntry entry = session.PersistenceContext.GetEntry(owner);
var ownerPersister = session.Factory.GetEntityPersister(_entityName);
var id = session.GetContextEntityIdentifier(owner);
if (id == null)
return false;

var entityKey = session.GenerateEntityKey(id, ownerPersister);

return session.PersistenceContext.IsPropertyNull(entry.EntityKey, PropertyName);
return session.PersistenceContext.IsPropertyNull(entityKey, PropertyName);
}

return false;
Expand Down
Loading