Skip to content

Commit 8141d63

Browse files
authored
Merge branch 'master' into comp-id/master
2 parents 9ff9634 + 67d06ee commit 8141d63

31 files changed

+278
-132
lines changed

src/NHibernate.Test/Async/VersionTest/Db/DbVersionFixture.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public async System.Threading.Tasks.Task CollectionVersionAsync()
5252
admin = await (s.GetAsync<Group>(admin.Id));
5353
guy.Groups.Add(admin);
5454
admin.Users.Add(guy);
55+
guy.NoOptimisticLock = "changed";
5556
await (t.CommitAsync());
5657
s.Close();
5758

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
using System;
2+
using NHibernate.Id;
3+
using NUnit.Framework;
4+
5+
namespace NHibernate.Test.IdGen.GuidComb;
6+
7+
[TestFixture]
8+
public class GuidCombFixture
9+
{
10+
class GuidCombGeneratorEx : GuidCombGenerator
11+
{
12+
public static Guid Generate(string guid, DateTime utcNow) => GenerateComb(Guid.Parse(guid), utcNow);
13+
}
14+
15+
[Test]
16+
public void CanGenerateSequentialGuid()
17+
{
18+
Assert.AreEqual(Guid.Parse("076a04fa-ef4e-4093-8479-b0e10103cdc5"),
19+
GuidCombGeneratorEx.Generate(
20+
"076a04fa-ef4e-4093-8479-8599e96f14cf",
21+
new DateTime(2023, 12, 23, 15, 45, 55, DateTimeKind.Utc)),
22+
"seed: 076a04fa");
23+
24+
Assert.AreEqual(Guid.Parse("81162ee2-a4cb-4611-9327-d61f0137e5b6"),
25+
GuidCombGeneratorEx.Generate(
26+
"81162ee2-a4cb-4611-9327-23bbda36176c",
27+
new DateTime(2050, 01, 29, 18, 55, 35, DateTimeKind.Utc)),
28+
"seed: 81162ee2");
29+
30+
}
31+
32+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
using System;
2+
3+
namespace NHibernate.Test.NHSpecificTest.GH3657
4+
{
5+
class Entity
6+
{
7+
public virtual Guid Id { get; set; }
8+
public virtual string Name { get; set; }
9+
}
10+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
using log4net;
2+
using NHibernate.Cfg;
3+
using NHibernate.Impl;
4+
using NUnit.Framework;
5+
6+
namespace NHibernate.Test.NHSpecificTest.GH3657
7+
{
8+
[TestFixture]
9+
public class Fixture
10+
{
11+
private static readonly ILog _log = LogManager.GetLogger(typeof(Fixture));
12+
private const string TestSessionFactoryName = "TestName";
13+
14+
private Configuration _cfg;
15+
private ISessionFactory _builtSessionFactory;
16+
17+
[OneTimeSetUp]
18+
public void TestFixtureSetUp()
19+
{
20+
_cfg = TestConfigurationHelper.GetDefaultConfiguration();
21+
var type = GetType();
22+
_cfg.AddResource(type.Namespace + ".Mappings.hbm.xml", type.Assembly);
23+
_cfg.SetProperty(Environment.SessionFactoryName, TestSessionFactoryName);
24+
}
25+
26+
[TearDown]
27+
public void TearDown()
28+
{
29+
_builtSessionFactory?.Dispose();
30+
_builtSessionFactory = null;
31+
}
32+
33+
private ISessionFactory SessionFactoryBuilder()
34+
{
35+
Assert.That(_builtSessionFactory, Is.Null, "SessionFactory was already built");
36+
37+
_builtSessionFactory = _cfg.BuildSessionFactory();
38+
_log.Info("Successfully built session factory");
39+
40+
return _builtSessionFactory;
41+
}
42+
43+
[Test]
44+
public void GetOrAddTwice()
45+
{
46+
var factory = SessionFactoryObjectFactory.GetOrBuildNamedInstance(TestSessionFactoryName, SessionFactoryBuilder);
47+
Assert.That(factory, Is.Not.Null, "Failed to get the factory once");
48+
49+
var factory2 = SessionFactoryObjectFactory.GetOrBuildNamedInstance(TestSessionFactoryName, SessionFactoryBuilder);
50+
Assert.That(factory2, Is.Not.Null, "Failed to get the factory twice");
51+
Assert.That(factory, Is.SameAs(factory2), "The two factories should be the same");
52+
}
53+
}
54+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="utf-8" ?>
2+
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="NHibernate.Test"
3+
namespace="NHibernate.Test.NHSpecificTest.GH3657">
4+
5+
<class name="Entity">
6+
<id name="Id" generator="guid.comb"/>
7+
<property name="Name"/>
8+
</class>
9+
10+
</hibernate-mapping>

src/NHibernate.Test/NHibernate.Test.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@
6868
<PackageReference Include="NHibernate.Caches.CoreDistributedCache.Memory" Version="5.9.0" />
6969
<PackageReference Include="NHibernate.Caches.Util.JsonSerializer" Version="5.9.0" />
7070
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.117" />
71-
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.3.14" />
71+
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.0.2" />
7272
<PackageReference Include="NSubstitute" Version="5.1.0" />
7373
<PackageReference Include="NUnit" Version="3.14.0" />
7474
<PackageReference Include="NUnit.Analyzers" Version="4.2.0" />

src/NHibernate.Test/VersionTest/Db/DbVersionFixture.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public void CollectionVersion()
4242
admin = s.Get<Group>(admin.Id);
4343
guy.Groups.Add(admin);
4444
admin.Users.Add(guy);
45+
guy.NoOptimisticLock = "changed";
4546
t.Commit();
4647
s.Close();
4748

src/NHibernate.Test/VersionTest/Db/User.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ public class User
1111

1212
public virtual string Username { get; set; }
1313

14+
public virtual string NoOptimisticLock { get; set; }
15+
1416
public virtual ISet<Group> Groups { get; set; }
1517

1618
public virtual ISet<Permission> Permissions { get; set; }
1719
}
18-
}
20+
}

src/NHibernate.Test/VersionTest/Db/User.hbm.xml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<?xml version="1.0" encoding="utf-8" ?>
1+
<?xml version="1.0" encoding="utf-8" ?>
22
<!--
33
Demonstrates how to control the optimistic locking behavior
44
of a collection (do changes to the collection result in
@@ -14,6 +14,7 @@
1414
</id>
1515
<timestamp name="Timestamp" column="ts" source="db"/>
1616
<property name="Username" column="user_name" type="string" unique="true"/>
17+
<property name="NoOptimisticLock" column="no_optimistic_lock" type="string" optimistic-lock="false"/>
1718
<set name="Groups" table="db_vers_user_group" batch-size="9" inverse="true" optimistic-lock="true" lazy="true" cascade="none" >
1819
<key column="user_id"/>
1920
<many-to-many column="group_id" class="Group" lazy="false" fetch="join" />
@@ -45,4 +46,4 @@
4546
<property name="Context" column="ctx" type="string"/>
4647
<property name="Access" column="priv" type="string"/>
4748
</class>
48-
</hibernate-mapping>
49+
</hibernate-mapping>

src/NHibernate/Async/Event/Default/DefaultFlushEntityEventListener.cs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -336,33 +336,35 @@ private async Task<object> GetNextVersionAsync(FlushEntityEvent @event, Cancella
336336
/// to synchronize its state to the database. Modifies the event by side-effect!
337337
/// Note: this method is quite slow, avoid calling if possible!
338338
/// </summary>
339-
protected Task<bool> IsUpdateNecessaryAsync(FlushEntityEvent @event, CancellationToken cancellationToken)
339+
protected async Task<bool> IsUpdateNecessaryAsync(FlushEntityEvent @event, CancellationToken cancellationToken)
340340
{
341-
if (cancellationToken.IsCancellationRequested)
342-
{
343-
return Task.FromCanceled<bool>(cancellationToken);
344-
}
341+
cancellationToken.ThrowIfCancellationRequested();
345342
IEntityPersister persister = @event.EntityEntry.Persister;
346343
Status status = @event.EntityEntry.Status;
347344

348345
if (!@event.DirtyCheckPossible)
349346
{
350-
return Task.FromResult<bool>(true);
347+
return true;
351348
}
352349
else
353350
{
351+
// call to HasDirtyCollections must not be optimized away because of its side effect
352+
bool hasDirtyCollections = await (HasDirtyCollectionsAsync(@event, persister, status, cancellationToken)).ConfigureAwait(false);
353+
354354
int[] dirtyProperties = @event.DirtyProperties;
355-
if (dirtyProperties != null && dirtyProperties.Length != 0)
356-
{
357-
return Task.FromResult<bool>(true); //TODO: suck into event class
358-
}
359-
else
360-
{
361-
return HasDirtyCollectionsAsync(@event, persister, status, cancellationToken);
362-
}
355+
return dirtyProperties != null && dirtyProperties.Length != 0 || hasDirtyCollections;
363356
}
364357
}
365358

359+
/// <summary>
360+
/// Check if there are any dirty collections.
361+
/// Has a side effect of setting the HasDirtyCollection property of the event.
362+
/// </summary>
363+
/// <param name="event"></param>
364+
/// <param name="persister"></param>
365+
/// <param name="status"></param>
366+
/// <param name="cancellationToken">A cancellation token that can be used to cancel the work</param>
367+
/// <returns></returns>
366368
private async Task<bool> HasDirtyCollectionsAsync(FlushEntityEvent @event, IEntityPersister persister, Status status, CancellationToken cancellationToken)
367369
{
368370
cancellationToken.ThrowIfCancellationRequested();

src/NHibernate/Async/Id/GuidCombGenerator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010

1111
using System;
12+
using System.Diagnostics;
1213
using NHibernate.Engine;
1314

1415
namespace NHibernate.Id

src/NHibernate/Criterion/EntityProjection.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
using System;
1+
using System;
22
using System.Collections.Generic;
33
using NHibernate.Engine;
4-
using NHibernate.Loader;
54
using NHibernate.Loader.Criteria;
65
using NHibernate.Persister.Entity;
76
using NHibernate.SqlCommand;
87
using NHibernate.Type;
8+
using NHibernate.Util;
9+
910
using IQueryable = NHibernate.Persister.Entity.IQueryable;
1011

1112
namespace NHibernate.Criterion
@@ -204,7 +205,7 @@ private void SetFields(ICriteriaQuery criteriaQuery)
204205
subcriteria,
205206
Persister.IdentifierPropertyName ?? string.Empty);
206207

207-
ColumnAliasSuffix = BasicLoader.GenerateSuffix(criteriaQuery.GetIndexForAlias());
208+
ColumnAliasSuffix = StringHelper.GenerateSuffix(criteriaQuery.GetIndexForAlias());
208209

209210
_identifierColumnAliases = Persister.GetIdentifierAliases(ColumnAliasSuffix);
210211

src/NHibernate/Event/Default/DefaultFlushEntityEventListener.cs

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -389,18 +389,22 @@ protected bool IsUpdateNecessary(FlushEntityEvent @event)
389389
}
390390
else
391391
{
392+
// call to HasDirtyCollections must not be optimized away because of its side effect
393+
bool hasDirtyCollections = HasDirtyCollections(@event, persister, status);
394+
392395
int[] dirtyProperties = @event.DirtyProperties;
393-
if (dirtyProperties != null && dirtyProperties.Length != 0)
394-
{
395-
return true; //TODO: suck into event class
396-
}
397-
else
398-
{
399-
return HasDirtyCollections(@event, persister, status);
400-
}
396+
return dirtyProperties != null && dirtyProperties.Length != 0 || hasDirtyCollections;
401397
}
402398
}
403399

400+
/// <summary>
401+
/// Check if there are any dirty collections.
402+
/// Has a side effect of setting the HasDirtyCollection property of the event.
403+
/// </summary>
404+
/// <param name="event"></param>
405+
/// <param name="persister"></param>
406+
/// <param name="status"></param>
407+
/// <returns></returns>
404408
private bool HasDirtyCollections(FlushEntityEvent @event, IEntityPersister persister, Status status)
405409
{
406410
if (IsCollectionDirtyCheckNecessary(persister, status))

src/NHibernate/Hql/Ast/ANTLR/HqlSqlWalker.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,7 +485,7 @@ internal string GetSuffix(FromElement fromElement)
485485
return suffix;
486486
}
487487

488-
suffix = _suffixes.Count == 0 ? string.Empty : _suffixes.Count.ToString() + '_';
488+
suffix = _suffixes.Count == 0 ? string.Empty : StringHelper.GenerateSuffix(_suffixes.Count);
489489
_suffixes.Add(fromElement, suffix);
490490

491491
return suffix;

src/NHibernate/Hql/Ast/ANTLR/Tree/FromElementType.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -532,7 +532,7 @@ private static string GetSuffix(int size, int sequence)
532532

533533
private static string GenerateSuffix(int size, int k)
534534
{
535-
String suffix = size == 1 ? "" : k.ToString() + '_';
535+
String suffix = size == 1 ? "" : StringHelper.GenerateSuffix(k);
536536
return suffix;
537537
}
538538

src/NHibernate/Id/GuidCombGenerator.cs

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System;
2+
using System.Diagnostics;
23
using NHibernate.Engine;
34

45
namespace NHibernate.Id
@@ -36,34 +37,32 @@ public partial class GuidCombGenerator : IIdentifierGenerator
3637
/// <returns>The new identifier as a <see cref="Guid"/>.</returns>
3738
public object Generate(ISessionImplementor session, object obj)
3839
{
39-
return GenerateComb();
40+
return GenerateComb(Guid.NewGuid(), DateTime.UtcNow);
4041
}
4142

4243
/// <summary>
4344
/// Generate a new <see cref="Guid"/> using the comb algorithm.
4445
/// </summary>
45-
private Guid GenerateComb()
46+
protected static Guid GenerateComb(Guid guid, DateTime utcNow)
4647
{
47-
byte[] guidArray = Guid.NewGuid().ToByteArray();
48-
49-
DateTime now = DateTime.UtcNow;
50-
48+
#if NETSTANDARD2_1_OR_GREATER || NETCOREAPP2_1_OR_GREATER
49+
Span<byte> guidArray = stackalloc byte[16];
50+
guid.TryWriteBytes(guidArray);
51+
#else
52+
var guidArray = guid.ToByteArray();
53+
#endif
5154
// Get the days and milliseconds which will be used to build the byte string
52-
TimeSpan days = new TimeSpan(now.Ticks - BaseDateTicks);
53-
TimeSpan msecs = now.TimeOfDay;
54-
55-
// Convert to a byte array
55+
var ts = new TimeSpan(utcNow.Ticks - BaseDateTicks);
56+
var days = ts.Days;
57+
guidArray[10] = (byte) (days >> 8);
58+
guidArray[11] = (byte) days;
59+
5660
// Note that SQL Server is accurate to 1/300th of a millisecond so we divide by 3.333333
57-
byte[] daysArray = BitConverter.GetBytes(days.Days);
58-
byte[] msecsArray = BitConverter.GetBytes((long) (msecs.TotalMilliseconds / 3.333333));
59-
60-
// Reverse the bytes to match SQL Servers ordering
61-
Array.Reverse(daysArray);
62-
Array.Reverse(msecsArray);
63-
64-
// Copy the bytes into the guid
65-
Array.Copy(daysArray, daysArray.Length - 2, guidArray, guidArray.Length - 6, 2);
66-
Array.Copy(msecsArray, msecsArray.Length - 4, guidArray, guidArray.Length - 4, 4);
61+
var msecs = (long) (utcNow.TimeOfDay.TotalMilliseconds / 3.333333);
62+
guidArray[12] = (byte) (msecs >> 24);
63+
guidArray[13] = (byte) (msecs >> 16);
64+
guidArray[14] = (byte) (msecs >> 8);
65+
guidArray[15] = (byte) msecs;
6766

6867
return new Guid(guidArray);
6968
}

0 commit comments

Comments
 (0)