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 version update on dirty collection with another property having optimistic-lock false #3632

Merged
merged 2 commits into from
Jan 14, 2025
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
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public async System.Threading.Tasks.Task CollectionVersionAsync()
admin = await (s.GetAsync<Group>(admin.Id));
guy.Groups.Add(admin);
admin.Users.Add(guy);
guy.NoOptimisticLock = "changed";
await (t.CommitAsync());
s.Close();

Expand Down
1 change: 1 addition & 0 deletions src/NHibernate.Test/VersionTest/Db/DbVersionFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public void CollectionVersion()
admin = s.Get<Group>(admin.Id);
guy.Groups.Add(admin);
admin.Users.Add(guy);
guy.NoOptimisticLock = "changed";
t.Commit();
s.Close();

Expand Down
4 changes: 3 additions & 1 deletion src/NHibernate.Test/VersionTest/Db/User.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ public class User

public virtual string Username { get; set; }

public virtual string NoOptimisticLock { get; set; }

public virtual ISet<Group> Groups { get; set; }

public virtual ISet<Permission> Permissions { get; set; }
}
}
}
5 changes: 3 additions & 2 deletions src/NHibernate.Test/VersionTest/Db/User.hbm.xml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8" ?>
<?xml version="1.0" encoding="utf-8" ?>
<!--
Demonstrates how to control the optimistic locking behavior
of a collection (do changes to the collection result in
Expand All @@ -14,6 +14,7 @@
</id>
<timestamp name="Timestamp" column="ts" source="db"/>
<property name="Username" column="user_name" type="string" unique="true"/>
<property name="NoOptimisticLock" column="no_optimistic_lock" type="string" optimistic-lock="false"/>
<set name="Groups" table="db_vers_user_group" batch-size="9" inverse="true" optimistic-lock="true" lazy="true" cascade="none" >
<key column="user_id"/>
<many-to-many column="group_id" class="Group" lazy="false" fetch="join" />
Expand Down Expand Up @@ -45,4 +46,4 @@
<property name="Context" column="ctx" type="string"/>
<property name="Access" column="priv" type="string"/>
</class>
</hibernate-mapping>
</hibernate-mapping>
Original file line number Diff line number Diff line change
Expand Up @@ -336,33 +336,35 @@ private async Task<object> GetNextVersionAsync(FlushEntityEvent @event, Cancella
/// to synchronize its state to the database. Modifies the event by side-effect!
/// Note: this method is quite slow, avoid calling if possible!
/// </summary>
protected Task<bool> IsUpdateNecessaryAsync(FlushEntityEvent @event, CancellationToken cancellationToken)
protected async Task<bool> IsUpdateNecessaryAsync(FlushEntityEvent @event, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<bool>(cancellationToken);
}
cancellationToken.ThrowIfCancellationRequested();
IEntityPersister persister = @event.EntityEntry.Persister;
Status status = @event.EntityEntry.Status;

if ([email protected])
{
return Task.FromResult<bool>(true);
return true;
}
else
{
// call to HasDirtyCollections must not be optimized away because of its side effect
bool hasDirtyCollections = await (HasDirtyCollectionsAsync(@event, persister, status, cancellationToken)).ConfigureAwait(false);

int[] dirtyProperties = @event.DirtyProperties;
if (dirtyProperties != null && dirtyProperties.Length != 0)
{
return Task.FromResult<bool>(true); //TODO: suck into event class
}
else
{
return HasDirtyCollectionsAsync(@event, persister, status, cancellationToken);
}
return dirtyProperties != null && dirtyProperties.Length != 0 || hasDirtyCollections;
}
}

/// <summary>
/// Check if there are any dirty collections.
/// Has a side effect of setting the HasDirtyCollection property of the event.
/// </summary>
/// <param name="event"></param>
/// <param name="persister"></param>
/// <param name="status"></param>
/// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param>
/// <returns></returns>
private async Task<bool> HasDirtyCollectionsAsync(FlushEntityEvent @event, IEntityPersister persister, Status status, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
Expand Down
20 changes: 12 additions & 8 deletions src/NHibernate/Event/Default/DefaultFlushEntityEventListener.cs
Original file line number Diff line number Diff line change
Expand Up @@ -389,18 +389,22 @@ protected bool IsUpdateNecessary(FlushEntityEvent @event)
}
else
{
// call to HasDirtyCollections must not be optimized away because of its side effect
bool hasDirtyCollections = HasDirtyCollections(@event, persister, status);

int[] dirtyProperties = @event.DirtyProperties;
if (dirtyProperties != null && dirtyProperties.Length != 0)
{
return true; //TODO: suck into event class
}
else
{
return HasDirtyCollections(@event, persister, status);
}
return dirtyProperties != null && dirtyProperties.Length != 0 || hasDirtyCollections;
}
}

/// <summary>
/// Check if there are any dirty collections.
/// Has a side effect of setting the HasDirtyCollection property of the event.
/// </summary>
/// <param name="event"></param>
/// <param name="persister"></param>
/// <param name="status"></param>
/// <returns></returns>
private bool HasDirtyCollections(FlushEntityEvent @event, IEntityPersister persister, Status status)
{
if (IsCollectionDirtyCheckNecessary(persister, status))
Expand Down