Skip to content
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

Fix #1419 - ISession.IsDirty() shouldn't throw exception for transient many-to-one object in a session #1420

Merged
merged 8 commits into from
Nov 3, 2017
128 changes: 128 additions & 0 deletions src/NHibernate.Test/Async/NHSpecificTest/GH1419/FixtureByCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
//------------------------------------------------------------------------------
// <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;
using System.Linq;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Mapping.ByCode;
using NUnit.Framework;

namespace NHibernate.Test.NHSpecificTest.GH1419
{
using System.Threading.Tasks;
using System.Threading;
[TestFixture]
public class ByCodeFixtureAsync : TestCaseMappingByCode
{
private Guid ParentId;

protected override HbmMapping GetMappings()
{
var mapper = new ModelMapper();
mapper.Class<EntityParent>(rc =>
{
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
rc.Property(x => x.Name);
rc.ManyToOne(ep => ep.Child);
rc.ManyToOne(ep => ep.ChildAssigned);
});

mapper.Class<EntityChild>(rc =>
{
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
rc.Property(x => x.Name);
});

mapper.Class<EntityChildAssigned>(rc =>
{
rc.Id(x => x.Id, m => m.Generator(Generators.Assigned));
rc.Property(x => x.Name);
});

return mapper.CompileMappingForAllExplicitlyAddedEntities();
}

protected override void OnSetUp()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
var child = new EntityChild { Name = "InitialChild" };

var assigned = new EntityChildAssigned { Id = 1, Name = "InitialChild" };

var parent = new EntityParent
{
Name = "InitialParent",
Child = child,
ChildAssigned = assigned
};
session.Save(child);
session.Save(parent);
session.Save(assigned);

session.Flush();
transaction.Commit();
ParentId = parent.Id;
}
}

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

session.Flush();
transaction.Commit();
}
}

[Test]
public async Task SessionIsDirtyShouldNotFailForNewManyToOneObjectAsync()
{
using (var session = OpenSession())
using (session.BeginTransaction())
{
var parent = await (GetParentAsync(session));

//parent.Child entity is not cascaded, I want to save it explictilty later
parent.Child = new EntityChild { Name = "NewManyToOneChild" };

var isDirty = false;
Assert.That(async () => isDirty = await (session.IsDirtyAsync()), Throws.Nothing, "ISession.IsDirty() call should not fail for transient many-to-one object referenced in session.");
Assert.That(isDirty, "ISession.IsDirty() call should return true.");
}
}

[Test]
public async Task SessionIsDirtyShouldNotFailForNewManyToOneObjectWithAssignedIdAsync()
{
using (var session = OpenSession())
using (session.BeginTransaction())
{
var parent = await (GetParentAsync(session));

//parent.ChildAssigned entity is not cascaded, I want to save it explictilty later
parent.ChildAssigned = new EntityChildAssigned { Id = 2, Name = "NewManyToOneChildAssignedId" };

var isDirty = false;
Assert.That(async () => isDirty = await (session.IsDirtyAsync()), Throws.Nothing, "ISession.IsDirty() call should not fail for transient many-to-one object referenced in session.");
Assert.That(isDirty, "ISession.IsDirty() call should return true.");
}
}

private Task<EntityParent> GetParentAsync(ISession session, CancellationToken cancellationToken = default(CancellationToken))
{
return session.GetAsync<EntityParent>(ParentId, cancellationToken);
}
}
}
25 changes: 25 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/GH1419/Entities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;

namespace NHibernate.Test.NHSpecificTest.GH1419
{
public class EntityChild
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
}

public class EntityParent
{
public virtual Guid Id { get; set; }
public virtual string Name { get; set; }
public virtual EntityChild Child { get; set; }
public virtual EntityChildAssigned ChildAssigned { get; set; }
}

public class EntityChildAssigned
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
}
}
116 changes: 116 additions & 0 deletions src/NHibernate.Test/NHSpecificTest/GH1419/FixtureByCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
using System;
using System.Linq;
using NHibernate.Cfg.MappingSchema;
using NHibernate.Mapping.ByCode;
using NUnit.Framework;

namespace NHibernate.Test.NHSpecificTest.GH1419
{
[TestFixture]
public class ByCodeFixture : TestCaseMappingByCode
{
private Guid ParentId;

protected override HbmMapping GetMappings()
{
var mapper = new ModelMapper();
mapper.Class<EntityParent>(rc =>
{
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
rc.Property(x => x.Name);
rc.ManyToOne(ep => ep.Child);
rc.ManyToOne(ep => ep.ChildAssigned);
});

mapper.Class<EntityChild>(rc =>
{
rc.Id(x => x.Id, m => m.Generator(Generators.GuidComb));
rc.Property(x => x.Name);
});

mapper.Class<EntityChildAssigned>(rc =>
{
rc.Id(x => x.Id, m => m.Generator(Generators.Assigned));
rc.Property(x => x.Name);
});

return mapper.CompileMappingForAllExplicitlyAddedEntities();
}

protected override void OnSetUp()
{
using (var session = OpenSession())
using (var transaction = session.BeginTransaction())
{
var child = new EntityChild { Name = "InitialChild" };

var assigned = new EntityChildAssigned { Id = 1, Name = "InitialChild" };

var parent = new EntityParent
{
Name = "InitialParent",
Child = child,
ChildAssigned = assigned
};
session.Save(child);
session.Save(parent);
session.Save(assigned);

session.Flush();
transaction.Commit();
ParentId = parent.Id;
}
}

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

session.Flush();
transaction.Commit();
}
}

[Test]
public void SessionIsDirtyShouldNotFailForNewManyToOneObject()
{
using (var session = OpenSession())
using (session.BeginTransaction())
{
var parent = GetParent(session);

//parent.Child entity is not cascaded, I want to save it explictilty later
parent.Child = new EntityChild { Name = "NewManyToOneChild" };

var isDirty = false;
Assert.That(() => isDirty = session.IsDirty(), Throws.Nothing, "ISession.IsDirty() call should not fail for transient many-to-one object referenced in session.");
Assert.That(isDirty, "ISession.IsDirty() call should return true.");
}
}

[Test]
public void SessionIsDirtyShouldNotFailForNewManyToOneObjectWithAssignedId()
{
using (var session = OpenSession())
using (session.BeginTransaction())
{
var parent = GetParent(session);

//parent.ChildAssigned entity is not cascaded, I want to save it explictilty later
parent.ChildAssigned = new EntityChildAssigned { Id = 2, Name = "NewManyToOneChildAssignedId" };

var isDirty = false;
Assert.That(() => isDirty = session.IsDirty(), Throws.Nothing, "ISession.IsDirty() call should not fail for transient many-to-one object referenced in session.");
Assert.That(isDirty, "ISession.IsDirty() call should return true.");
}
}

private EntityParent GetParent(ISession session)
{
return session.Get<EntityParent>(ParentId);
}
}
}
48 changes: 30 additions & 18 deletions src/NHibernate/Async/Type/ManyToOneType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -135,37 +135,49 @@ private Task<object> AssembleIdAsync(object oid, ISessionImplementor session, Ca
}
}

public override async Task<bool> IsDirtyAsync(object old, object current, ISessionImplementor session, CancellationToken cancellationToken)
public override Task<bool> IsDirtyAsync(object old, object current, ISessionImplementor session, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (IsSame(old, current))
if (cancellationToken.IsCancellationRequested)
{
return false;
return Task.FromCanceled<bool>(cancellationToken);
}
return IsDirtyManyToOneAsync(old, current, null, session, cancellationToken);
}

object oldid = await (GetIdentifierAsync(old, session, cancellationToken)).ConfigureAwait(false);
object newid = await (GetIdentifierAsync(current, session, cancellationToken)).ConfigureAwait(false);
return await (GetIdentifierType(session).IsDirtyAsync(oldid, newid, session, cancellationToken)).ConfigureAwait(false);
public override Task<bool> IsDirtyAsync(object old, object current, bool[] checkable, ISessionImplementor session, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<bool>(cancellationToken);
}
return IsDirtyManyToOneAsync(old, current, IsAlwaysDirtyChecked ? null : checkable, session, cancellationToken);
}

public override async Task<bool> IsDirtyAsync(object old, object current, bool[] checkable, ISessionImplementor session, CancellationToken cancellationToken)
private async Task<bool> IsDirtyManyToOneAsync(object old, object current, bool[] checkable, ISessionImplementor session, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
if (IsAlwaysDirtyChecked)
if (IsSame(old, current))
{
return await (IsDirtyAsync(old, current, session, cancellationToken)).ConfigureAwait(false);
return false;
}
else

if (old == null || current == null)
{
if (IsSame(old, current))
{
return false;
}
return true;
}

object oldid = await (GetIdentifierAsync(old, session, cancellationToken)).ConfigureAwait(false);
object newid = await (GetIdentifierAsync(current, session, cancellationToken)).ConfigureAwait(false);
return await (GetIdentifierType(session).IsDirtyAsync(oldid, newid, checkable, session, cancellationToken)).ConfigureAwait(false);
if ((await (ForeignKeys.IsTransientFastAsync(GetAssociatedEntityName(), current, session, cancellationToken)).ConfigureAwait(false)).GetValueOrDefault())
{
return true;
}

object oldid = await (GetIdentifierAsync(old, session, cancellationToken)).ConfigureAwait(false);
object newid = await (GetIdentifierAsync(current, session, cancellationToken)).ConfigureAwait(false);
IType identifierType = GetIdentifierType(session);

return checkable == null
? await (identifierType.IsDirtyAsync(oldid, newid, session, cancellationToken)).ConfigureAwait(false)
: await (identifierType.IsDirtyAsync(oldid, newid, checkable, session, cancellationToken)).ConfigureAwait(false);
}
}
}
Loading