Skip to content
Open
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
52 changes: 31 additions & 21 deletions src/NHibernate.Test/Async/CacheTest/JsonSerializerCacheFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@ public class JsonSerializerCacheFixtureAsync : TestCase
protected override string[] Mappings => new[]
{
"CacheTest.ReadOnly.hbm.xml",
"CacheTest.ReadWrite.hbm.xml"
"CacheTest.ReadWrite.hbm.xml",
"CacheTest.NonStrictReadWrite.hbm.xml"
};

protected override string MappingsAssembly => "NHibernate.Test";
Expand All @@ -46,7 +47,7 @@ protected override void Configure(Configuration configuration)
serializer.RegisterType(typeof(Tuple<string, object>), "tso");
CoreDistributedCacheProvider.DefaultSerializer = serializer;
}

protected override void OnSetUp()
{
using (var s = Sfi.OpenSession())
Expand All @@ -55,36 +56,27 @@ protected override void OnSetUp()
var totalItems = 6;
for (var i = 1; i <= totalItems; i++)
{
var parent = new ReadOnly
{
Name = $"Name{i}"
};
var parent = new ReadOnly { Name = $"Name{i}" };
for (var j = 1; j <= totalItems; j++)
{
var child = new ReadOnlyItem
{
Parent = parent
};
parent.Items.Add(child);
parent.Items.Add(new ReadOnlyItem { Parent = parent });
}
s.Save(parent);
}
for (var i = 1; i <= totalItems; i++)
{
var parent = new ReadWrite
{
Name = $"Name{i}"
};
var parent = new ReadWrite { Name = $"Name{i}" };
for (var j = 1; j <= totalItems; j++)
{
var child = new ReadWriteItem
{
Parent = parent
};
parent.Items.Add(child);
parent.Items.Add(new ReadWriteItem { Parent = parent });
}
s.Save(parent);
}
for (var i = 1; i <= totalItems; i++)
{
var parent = new NonStrictReadWrite() { Name = $"Name{i}" };
s.Save(parent);
}
tx.Commit();
}
}
Expand All @@ -98,12 +90,13 @@ protected override void OnTearDown()
s.CreateQuery("delete from ReadWriteItem").ExecuteUpdate();
s.CreateQuery("delete from ReadOnly").ExecuteUpdate();
s.CreateQuery("delete from ReadWrite").ExecuteUpdate();
s.CreateQuery("delete from NonStrictReadWrite").ExecuteUpdate();
tx.Commit();
}
// Must rebuild the session factory, CoreDistribted cache being not clearable.
RebuildSessionFactory();
}

[Test]
public async Task CacheableScalarSqlQueryWithTransformerAsync()
{
Expand Down Expand Up @@ -532,5 +525,22 @@ public async Task QueryFetchEntityBatchCacheTestAsync(bool future)
Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count");
Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache hit count");
}

[Test]
public async Task LazyFormulaTestAsync()
{
using (var s = OpenSession())
using (var t = s.BeginTransaction())
{
var l = await (s.Query<NonStrictReadWrite>().ToListAsync());
foreach (var item in l)
{
// The lazy formula will puke if equality is not correct (due to comparison of deserialized
// UnfetchedLazyProperty vs LazyPropertyInitializer.UnfetchedProperty
Assert.AreEqual(1, item.Count);
}
await (t.CommitAsync());
}
}
}
}
11 changes: 11 additions & 0 deletions src/NHibernate.Test/CacheTest/CacheEntity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace NHibernate.Test.CacheTest;

public abstract class CacheEntity
{
public virtual int Id { get; protected set; }
}

public abstract class NamedCacheEntity : CacheEntity
{
public virtual string Name { get; set; }
}
52 changes: 31 additions & 21 deletions src/NHibernate.Test/CacheTest/JsonSerializerCacheFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ public class JsonSerializerCacheFixture : TestCase
protected override string[] Mappings => new[]
{
"CacheTest.ReadOnly.hbm.xml",
"CacheTest.ReadWrite.hbm.xml"
"CacheTest.ReadWrite.hbm.xml",
"CacheTest.NonStrictReadWrite.hbm.xml"
};

protected override string MappingsAssembly => "NHibernate.Test";
Expand All @@ -35,7 +36,7 @@ protected override void Configure(Configuration configuration)
serializer.RegisterType(typeof(Tuple<string, object>), "tso");
CoreDistributedCacheProvider.DefaultSerializer = serializer;
}

protected override void OnSetUp()
{
using (var s = Sfi.OpenSession())
Expand All @@ -44,36 +45,27 @@ protected override void OnSetUp()
var totalItems = 6;
for (var i = 1; i <= totalItems; i++)
{
var parent = new ReadOnly
{
Name = $"Name{i}"
};
var parent = new ReadOnly { Name = $"Name{i}" };
for (var j = 1; j <= totalItems; j++)
{
var child = new ReadOnlyItem
{
Parent = parent
};
parent.Items.Add(child);
parent.Items.Add(new ReadOnlyItem { Parent = parent });
}
s.Save(parent);
}
for (var i = 1; i <= totalItems; i++)
{
var parent = new ReadWrite
{
Name = $"Name{i}"
};
var parent = new ReadWrite { Name = $"Name{i}" };
for (var j = 1; j <= totalItems; j++)
{
var child = new ReadWriteItem
{
Parent = parent
};
parent.Items.Add(child);
parent.Items.Add(new ReadWriteItem { Parent = parent });
}
s.Save(parent);
}
for (var i = 1; i <= totalItems; i++)
{
var parent = new NonStrictReadWrite() { Name = $"Name{i}" };
s.Save(parent);
}
tx.Commit();
}
}
Expand All @@ -87,12 +79,13 @@ protected override void OnTearDown()
s.CreateQuery("delete from ReadWriteItem").ExecuteUpdate();
s.CreateQuery("delete from ReadOnly").ExecuteUpdate();
s.CreateQuery("delete from ReadWrite").ExecuteUpdate();
s.CreateQuery("delete from NonStrictReadWrite").ExecuteUpdate();
tx.Commit();
}
// Must rebuild the session factory, CoreDistribted cache being not clearable.
RebuildSessionFactory();
}

[Test]
public void CacheableScalarSqlQueryWithTransformer()
{
Expand Down Expand Up @@ -521,5 +514,22 @@ public void QueryFetchEntityBatchCacheTest(bool future)
Assert.That(Sfi.Statistics.QueryCacheMissCount, Is.EqualTo(0), "Unexpected cache miss count");
Assert.That(Sfi.Statistics.QueryCacheHitCount, Is.EqualTo(future ? 2 : 1), "Unexpected cache hit count");
}

[Test]
public void LazyFormulaTest()
{
using (var s = OpenSession())
using (var t = s.BeginTransaction())
{
var l = s.Query<NonStrictReadWrite>().ToList();
foreach (var item in l)
{
// The lazy formula will puke if equality is not correct (due to comparison of deserialized
// UnfetchedLazyProperty vs LazyPropertyInitializer.UnfetchedProperty
Assert.AreEqual(1, item.Count);
}
t.Commit();
}
}
}
}
7 changes: 7 additions & 0 deletions src/NHibernate.Test/CacheTest/NonStrictReadWrite.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace NHibernate.Test.CacheTest
{
public class NonStrictReadWrite : NamedCacheEntity
{
public virtual int Count { get; set; }
}
}
15 changes: 15 additions & 0 deletions src/NHibernate.Test/CacheTest/NonStrictReadWrite.hbm.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8" ?>
<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
assembly="NHibernate.Test"
namespace="NHibernate.Test.CacheTest">

<class name="NonStrictReadWrite" batch-size="3">
<cache usage="nonstrict-read-write" />
<id name="Id" unsaved-value="0">
<generator class="native"/>
</id>
<property name="Name" />
<property name="Count" formula="(select count(*) from NonStrictReadWrite x where x.Id = Id)" lazy="true"/>
</class>

</hibernate-mapping>
15 changes: 2 additions & 13 deletions src/NHibernate.Test/CacheTest/ReadOnly.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace NHibernate.Test.CacheTest
{
public class ReadOnly : CacheEntity
public class ReadOnly : NamedCacheEntity
{
public virtual string Name { get; set; }

public virtual ISet<ReadOnlyItem> Items { get; set; } = new HashSet<ReadOnlyItem>();
}

public class ReadOnlyItem : CacheEntity
{
public virtual ReadOnly Parent { get; set; }
}

public abstract class CacheEntity
{
public virtual int Id { get; protected set; }
}
}
10 changes: 2 additions & 8 deletions src/NHibernate.Test/CacheTest/ReadWrite.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,9 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace NHibernate.Test.CacheTest
{
public class ReadWrite : CacheEntity
public class ReadWrite : NamedCacheEntity
{
public virtual string Name { get; set; }

public virtual ISet<ReadWriteItem> Items { get; set; } = new HashSet<ReadWriteItem>();
}

Expand Down
1 change: 1 addition & 0 deletions src/NHibernate.Test/NHibernate.Test.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
<Compile Remove="**\NHSpecificTest\NH2484\**" />
<Compile Remove="**\NHSpecificTest\NH2188\**" />
<Compile Remove="**\NHSpecificTest\NH3121\**" />
<Compile Remove="CacheTest\EntityWithLazyFormula.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="log4net" Version="3.0.2" />
Expand Down
4 changes: 2 additions & 2 deletions src/NHibernate/Engine/IPersistenceContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -457,7 +457,7 @@ public static EntityEntry AddEntity(
existsInDatabase,
persister,
disableVersionIncrement,
loadedState?.Any(o => o == LazyPropertyInitializer.UnfetchedProperty) == true);
loadedState?.Any(o => Equals(o, LazyPropertyInitializer.UnfetchedProperty)) == true);
#pragma warning restore 618
}

Expand Down Expand Up @@ -505,7 +505,7 @@ public static EntityEntry AddEntry(
existsInDatabase,
persister,
disableVersionIncrement,
loadedState?.Any(o => o == LazyPropertyInitializer.UnfetchedProperty) == true);
loadedState?.Any(o => Equals(o, LazyPropertyInitializer.UnfetchedProperty)) == true);
#pragma warning restore 618
}
}
Expand Down
10 changes: 8 additions & 2 deletions src/NHibernate/Intercept/ILazyPropertyInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,14 @@
namespace NHibernate.Intercept
{
[Serializable]
public struct UnfetchedLazyProperty
public struct UnfetchedLazyProperty : IEquatable<UnfetchedLazyProperty>
{
/// <summary>As long as the other instance is the same type, it's considered equal. This
/// avoids the issue where a deserialized value fails the base Object.Equals() check due to
/// both objects being different references.</summary>
public bool Equals(UnfetchedLazyProperty other) => true;
public override bool Equals(object obj) => obj is UnfetchedLazyProperty;
public override int GetHashCode() => typeof(UnfetchedLazyProperty).GetHashCode();
}

public struct LazyPropertyInitializer
Expand All @@ -20,4 +26,4 @@ public interface ILazyPropertyInitializer
/// <summary> Initialize the property, and return its new value</summary>
object InitializeLazyProperty(string fieldName, object entity, ISessionImplementor session);
}
}
}
4 changes: 2 additions & 2 deletions src/NHibernate/Persister/Entity/AbstractEntityPersister.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1396,7 +1396,7 @@ public virtual object InitializeLazyProperty(string fieldName, object entity, IS
{
CacheEntry cacheEntry = (CacheEntry)CacheEntryStructure.Destructure(ce, factory);
var initializedValue = InitializeLazyPropertiesFromCache(fieldName, entity, session, entry, cacheEntry, uninitializedLazyProperties);
if (initializedValue != LazyPropertyInitializer.UnfetchedProperty)
if (!Equals(initializedValue, LazyPropertyInitializer.UnfetchedProperty))
{
// NOTE EARLY EXIT!!!
return initializedValue;
Expand Down Expand Up @@ -1567,7 +1567,7 @@ private object InitializeLazyPropertiesFromCache(
for (int j = 0; j < lazyPropertyNames.Length; j++)
{
var cachedValue = disassembledValues[lazyPropertyNumbers[j]];
if (cachedValue == LazyPropertyInitializer.UnfetchedProperty)
if (Equals(cachedValue, LazyPropertyInitializer.UnfetchedProperty))
{
if (fieldName == lazyPropertyNames[j])
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ public ISet<string> GetUninitializedLazyProperties(object[] entityState)
var uninitializedProperties = new HashSet<string>();
foreach (var propertyDescriptor in LazyPropertiesMetadata.LazyPropertyDescriptors)
{
if (entityState[propertyDescriptor.PropertyIndex] == LazyPropertyInitializer.UnfetchedProperty)
if (Equals(entityState[propertyDescriptor.PropertyIndex], LazyPropertyInitializer.UnfetchedProperty))
{
uninitializedProperties.Add(propertyDescriptor.Name);
}
Expand Down