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
4 changes: 2 additions & 2 deletions src/Dapper.Oracle/TypeHandler/TypeHandlerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ private class DictionaryKeyComparer : IEqualityComparer<DictionaryKey>
{
public bool Equals(DictionaryKey x, DictionaryKey y)
{
if (x == null && y != null) return false;
if (x != null && y != null) return false;
if (x == null && y == null) return true;
if (x == null || y == null) return false;

return x.ParameterType == y.ParameterType && x.OracleTypeName.Equals(y.OracleTypeName);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Reflection;
using Dapper.Oracle.TypeHandler;
using Dapper.Oracle.TypeMapping;
using FluentAssertions;
Expand All @@ -17,21 +20,62 @@ public void ConvertTo()
var parameter = new OracleParameter();
var sut = new GuidRaw16TypeHandler();
sut.SetValue(parameter,input);

parameter.Value.Should().BeEquivalentTo(input.ToByteArray());
parameter.OracleDbType.Should().Be(OracleDbType.Raw);
}

[Fact]
public void ConvertFrom()
{
Guid input = Guid.NewGuid();

var sut = new GuidRaw16TypeHandler();
var result = sut.Parse(input.ToByteArray());
result.Should().Be(input);
}



/// <summary>
/// Verifies that TypeHandlerBase.OracleDbTypeProperty cache does not grow
/// unboundedly when SetValue is called multiple times with the same parameter type.
///
/// This test catches a bug in DictionaryKeyComparer.Equals where the condition
/// "if (x != null && y != null) return false" caused every cache lookup to fail,
/// resulting in unbounded cache growth and memory leaks.
/// </summary>
[Fact]
public void SetValue_ShouldReuseCache_WhenCalledMultipleTimes()
{
var sut = new GuidRaw16TypeHandler();

// Get the OracleDbTypeProperty cache via reflection
var baseType = typeof(GuidRaw16TypeHandler).BaseType;
var cacheField = baseType?.GetField("OracleDbTypeProperty",
BindingFlags.NonPublic | BindingFlags.Static);

cacheField.Should().NotBeNull("TypeHandlerBase should have OracleDbTypeProperty field");

var cache = cacheField.GetValue(null) as ICollection;
cache.Should().NotBeNull("OracleDbTypeProperty should be a collection");

// Record initial cache count
int initialCount = cache.Count;

// Call SetValue multiple times with the same parameter type
const int iterations = 100;
for (int i = 0; i < iterations; i++)
{
var parameter = new OracleParameter();
sut.SetValue(parameter, Guid.NewGuid());
}

// Cache should have grown by at most 1 entry (for the OracleParameter + "Raw" combination)
int finalCount = cache.Count;
int cacheGrowth = finalCount - initialCount;

cacheGrowth.Should().BeLessOrEqualTo(1,
$"cache should reuse entries for same parameter type, but grew by {cacheGrowth} entries after {iterations} calls. " +
"This indicates a bug in DictionaryKeyComparer.Equals causing cache entries to never match.");
}
}
}