Skip to content

Commit

Permalink
Merge pull request #56 from rengenesio/hotfix/table-cache
Browse files Browse the repository at this point in the history
Change cache to be indexed by connection string and table name
  • Loading branch information
mdalepiane authored Mar 23, 2021
2 parents 7d84ea5 + 7164212 commit 1b767d7
Show file tree
Hide file tree
Showing 6 changed files with 151 additions and 25 deletions.
8 changes: 4 additions & 4 deletions src/StrangerData/DbDialect.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StrangerData
{
public abstract class DbDialect : IDbDialect
{
public DbDialect(string connectionString)
public string ConnectionString { get; }

protected DbDialect(string connectionString)
{
this.ConnectionString = connectionString;
}

public virtual void DeleteAll(Stack<RecordIdentifier> recordIdentifiers)
Expand Down
12 changes: 4 additions & 8 deletions src/StrangerData/Generator/TableGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
using StrangerData;
using StrangerData.Utils;
using StrangerData.Utils;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.CompilerServices;

[assembly: InternalsVisibleTo("StrangerData.UnitTests")]
namespace StrangerData.Generator
{
internal class TableGenerator
Expand All @@ -24,10 +23,7 @@ private TableGenerator(IDbDialect dbDialect, string tableName, Stack<RecordIdent
_depth = depth;

// Try get the column info for this table in memory cache, else, get from dialect and store on cache
_tableColumnInfoList = MemoryCache.TryGetFromCache<IEnumerable<TableColumnInfo>>(tableName, () =>
{
return _dbDialect.GetTableSchemaInfo(tableName);
});
_tableColumnInfoList = MemoryCache.TryGetFromCache<IEnumerable<TableColumnInfo>>(_dbDialect.ConnectionString, tableName, () => this._dbDialect.GetTableSchemaInfo(tableName));
}

public TableGenerator(IDbDialect dbDialect, string tableName)
Expand Down
5 changes: 2 additions & 3 deletions src/StrangerData/IDbDialect.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StrangerData
{
public interface IDbDialect : IDisposable
{
string ConnectionString { get; }

TableColumnInfo[] GetTableSchemaInfo(string tableName);

IDictionary<string, object> Insert(string tableName, IEnumerable<TableColumnInfo> tableSchemaInfo, IDictionary<string, object> values);
Expand Down
20 changes: 10 additions & 10 deletions src/StrangerData/Utils/MemoryCache.cs
Original file line number Diff line number Diff line change
@@ -1,34 +1,34 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace StrangerData.Utils
{
public static class MemoryCache
{
private static IDictionary<string, object> _cache;
private static IDictionary<Tuple<string, string>, object> _cache;

private static IDictionary<string, object> Cache
private static IDictionary<Tuple<string, string>, object> Cache
{
get
{
if (_cache == null)
_cache = new Dictionary<string, object>();
_cache = new ConcurrentDictionary<Tuple<string, string>, object>();
return _cache;
}
}

public static T TryGetFromCache<T>(string key, Func<object> getValue)
public static T TryGetFromCache<T>(string primaryKey, string secondaryKey, Func<object> getValue)
where T : class
{
if (!Cache.ContainsKey(key))
Tuple<string, string> compositeKey = new Tuple<string, string>(primaryKey, secondaryKey);

if (!Cache.ContainsKey(compositeKey))
{
Cache[key] = getValue();
Cache[compositeKey] = getValue();
}

return Cache[key] as T;
return Cache[compositeKey] as T;
}
}
}
58 changes: 58 additions & 0 deletions test/StrangerData.UnitTests/Generator/TableGeneratorTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
using Moq;
using StrangerData.Generator;
using Xunit;

namespace StrangerData.UnitTests.Generator
{
public class TableGeneratorTests
{
[Fact]
public void Constructor_TablesFromDatabasesWithDifferentConnectionString_GetTableSchemaInfoFromBothDatabases()
{
// Arrange
Mock<IDbDialect> databaseOneDialectMock = new Mock<IDbDialect>();
databaseOneDialectMock.Setup(d => d.ConnectionString)
.Returns(Any.String());

Mock<IDbDialect> databaseTwoDialectMock = new Mock<IDbDialect>();
databaseTwoDialectMock.Setup(d => d.ConnectionString)
.Returns(Any.String());

string tableName = Any.String();

TableGenerator databaseOneTableGenerator = new TableGenerator(databaseOneDialectMock.Object, tableName);

// Act
TableGenerator databaseTwoTableGenerator = new TableGenerator(databaseTwoDialectMock.Object, tableName);

// Assert
databaseOneDialectMock.Verify(d => d.GetTableSchemaInfo(tableName), Times.Once);
databaseTwoDialectMock.Verify(d => d.GetTableSchemaInfo(tableName), Times.Once);
}

[Fact]
public void Constructor_TablesFromDifferentDialectsWithSameConnectionString_GetTableSchemaInfoFromCache()
{
// Arrange
string connectionString = Any.String();
string tableName = Any.String();

Mock<IDbDialect> databaseOneDialectMock = new Mock<IDbDialect>();
databaseOneDialectMock.Setup(d => d.ConnectionString)
.Returns(connectionString);

Mock<IDbDialect> databaseTwoDialectMock = new Mock<IDbDialect>();
databaseTwoDialectMock.Setup(d => d.ConnectionString)
.Returns(connectionString);

TableGenerator databaseOneTableGenerator = new TableGenerator(databaseOneDialectMock.Object, tableName);

// Act
TableGenerator databaseTwoTableGenerator = new TableGenerator(databaseTwoDialectMock.Object, tableName);

// Assert
databaseOneDialectMock.Verify(d => d.GetTableSchemaInfo(tableName), Times.Once);
databaseTwoDialectMock.Verify(d => d.GetTableSchemaInfo(tableName), Times.Never);
}
}
}
73 changes: 73 additions & 0 deletions test/StrangerData.UnitTests/Utils/MemoryCacheTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using FluentAssertions;
using Moq;
using StrangerData.Utils;
using System;
using Xunit;

namespace StrangerData.UnitTests.Utils
{
public class MemoryCacheTests
{
[Fact]
public void TryGetFromCache_BothKeysNotExists_InvokeFuncAndReturnFuncValue()
{
// Arrange
string expected = Any.String();

Mock<Func<object>> funcMock = new Mock<Func<object>>();
funcMock.Setup(f => f()).Returns(expected);

string nonExistentPrimaryKey = Any.String();
string nonExistentSecondaryKey = Any.String();

// Act
string actual = MemoryCache.TryGetFromCache<string>(nonExistentPrimaryKey, nonExistentSecondaryKey, funcMock.Object);

// Assert
actual.Should().Be(expected);
funcMock.Verify(f => f(), Times.Once);
}

[Fact]
public void TryGetFromCache_SecondaryKeyNotExists_InvokeFuncAndReturnFuncValue()
{
// Arrange
string expected = Any.String();

Mock<Func<object>> funcMock = new Mock<Func<object>>();
funcMock.Setup(f => f()).Returns(expected);

string primaryKey = Any.String();
MemoryCache.TryGetFromCache<string>(primaryKey, Any.String(), () => Any.String());

string nonExistentSecondaryKey = Any.String();

// Act
string actual = MemoryCache.TryGetFromCache<string>(primaryKey, nonExistentSecondaryKey, funcMock.Object);

// Assert
actual.Should().Be(expected);
funcMock.Verify(f => f(), Times.Once);
}

[Fact]
public void TryGetFromCache_BothKeysExists_NotInvokeFuncAndReturnExistentValue()
{
// Arrange
Mock<Func<object>> funcMock = new Mock<Func<object>>();
funcMock.Setup(f => f()).Returns(Any.String());

string primaryKey = Any.String();
string secondaryKey = Any.String();
string expected = Any.String();
MemoryCache.TryGetFromCache<string>(primaryKey, secondaryKey, () => expected);

// Act
string actual = MemoryCache.TryGetFromCache<string>(primaryKey, secondaryKey, funcMock.Object);

// Assert
actual.Should().Be(expected);
funcMock.Verify(f => f(), Times.Never);
}
}
}

0 comments on commit 1b767d7

Please sign in to comment.