Reputation: 67898
I've got a Module
that I'm wanting to use to cache some stuff. It's pretty simple. I wanted to shy away from the ConcurrentDictionary
because it needs to be a guaranteed operation.
Public Module SchemaTableCache
Private lockObject As New Object
Private columnCache As New Dictionary(Of String, SortedSet(Of String))
<Extension>
Public Sub CacheSchemaTable(dataReader As IDataReader, name As String)
SyncLock lockObject
Dim rows As New List(Of DataRow)
If columnCache.ContainsKey(name) Then
Return
End If
rows = dataReader.GetSchemaTable().Rows.OfType(Of DataRow)().ToList()
columnCache.Add(name, New SortedSet(Of String)(rows.Select(Function(r) r.Field(Of String)("ColumnName"))))
End SyncLock
End Sub
<Extension>
Public Function HasColumn(name As String, column As String) As Boolean
SyncLock lockObject
Dim cols As New SortedSet(Of String)
If Not columnCache.TryGetValue(name, cols) Then
Return False
End If
Return cols.Contains(column)
End SyncLock
End Function
End Module
Here's the thing. I have some unit tests that test the code that leverages the HasColumn
function. I set these tests up like this:
dataReader.Setup(Function(x) x(field)).Returns(val)
' setup the schema table
Dim table As New DataTable()
table.Columns.Add("ColumnName", GetType(String))
If setupTable Then
table.Rows.Add(field)
End If
dataReader.Setup(Function(x) x.GetSchemaTable()) _
.Returns(table)
dataReader.Object.CacheSchemaTable("table")
Then they test this function:
Dim typeName = GetType(T).Name
Debug.WriteLine($"IDataReader_Value({schemaTableName}.{column})")
If Not schemaTableName.HasColumn(column) Then
Debug.WriteLine($"Could not find column {column}; returning default value.")
Return typeName.DefaultValue()
End If
Dim input = dr(column)
Debug.WriteLine($"Found column {column}; returning value {input}.")
Return Value(Of T)(input)
You can see here where I hit the HasColumn
method. Here's the thing. If I execute these tests individually they succeed; however, they fail if I execute the entire set of tests.
Clearly there is a thread-safety issue here, but I can't for the life of me figure out what I did wrong. Can somebody help me see where I went wrong?
The output of a test when it's failing is:
Test Name: IDataReader_ValueBoolean
Test Outcome: Failed
Result Message: Assert.AreEqual failed. Expected:<True>. Actual:<False>.
Result StandardOutput:
Debug Trace:
IDataReader_Value(table.field)
Could not find column field; returning default value.
The output of a test when it succeeds is:
Test Name: IDataReader_ValueBoolean
Test Outcome: Passed
Result StandardOutput:
Debug Trace:
IDataReader_Value(table.field)
Found column field; returning value True.
Upvotes: 0
Views: 148
Reputation: 67898
I figured it out. The issue wasn't with SyncLock
, it was just with my logic. Each test is hitting a different problem. Some are testing the missing column, while some are expecting it to exist. Because of this I needed to be able to update the cache.
Here is the new logic:
SyncLock lockObject
Debug.WriteLine($"Caching schema table {name}.")
Dim rows As New List(Of DataRow)
If Not columnCache.ContainsKey(name) Then
Debug.WriteLine($"Adding cache key for {name}.")
columnCache.Add(name, New SortedSet(Of String)())
End If
rows = dataReader.GetSchemaTable().Rows.OfType(Of DataRow)().ToList()
Debug.WriteLine($"Schema table rows count: {rows.Count}")
columnCache(name) = New SortedSet(Of String)(rows.Select(Function(r) r.Field(Of String)("ColumnName")))
Debug.WriteLine($"Successfully cached {name}.")
End SyncLock
Upvotes: 1