diff --git a/src/StrangerData/DbDialect.cs b/src/StrangerData/DbDialect.cs index d4f5c35..1a80b21 100644 --- a/src/StrangerData/DbDialect.cs +++ b/src/StrangerData/DbDialect.cs @@ -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 recordIdentifiers) diff --git a/src/StrangerData/Generator/TableGenerator.cs b/src/StrangerData/Generator/TableGenerator.cs index d8c1b08..525d17b 100644 --- a/src/StrangerData/Generator/TableGenerator.cs +++ b/src/StrangerData/Generator/TableGenerator.cs @@ -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 @@ -24,10 +23,7 @@ private TableGenerator(IDbDialect dbDialect, string tableName, Stack>(tableName, () => - { - return _dbDialect.GetTableSchemaInfo(tableName); - }); + _tableColumnInfoList = MemoryCache.TryGetFromCache>(_dbDialect.ConnectionString, tableName, () => this._dbDialect.GetTableSchemaInfo(tableName)); } public TableGenerator(IDbDialect dbDialect, string tableName) diff --git a/src/StrangerData/IDbDialect.cs b/src/StrangerData/IDbDialect.cs index 6570a91..01fb2b2 100644 --- a/src/StrangerData/IDbDialect.cs +++ b/src/StrangerData/IDbDialect.cs @@ -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 Insert(string tableName, IEnumerable tableSchemaInfo, IDictionary values); diff --git a/src/StrangerData/Utils/MemoryCache.cs b/src/StrangerData/Utils/MemoryCache.cs index f0f991f..78e1222 100644 --- a/src/StrangerData/Utils/MemoryCache.cs +++ b/src/StrangerData/Utils/MemoryCache.cs @@ -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 _cache; + private static IDictionary, object> _cache; - private static IDictionary Cache + private static IDictionary, object> Cache { get { if (_cache == null) - _cache = new Dictionary(); + _cache = new ConcurrentDictionary, object>(); return _cache; } } - public static T TryGetFromCache(string key, Func getValue) + public static T TryGetFromCache(string primaryKey, string secondaryKey, Func getValue) where T : class { - if (!Cache.ContainsKey(key)) + Tuple compositeKey = new Tuple(primaryKey, secondaryKey); + + if (!Cache.ContainsKey(compositeKey)) { - Cache[key] = getValue(); + Cache[compositeKey] = getValue(); } - return Cache[key] as T; + return Cache[compositeKey] as T; } } } diff --git a/test/StrangerData.UnitTests/Generator/TableGeneratorTests.cs b/test/StrangerData.UnitTests/Generator/TableGeneratorTests.cs new file mode 100644 index 0000000..60fd31d --- /dev/null +++ b/test/StrangerData.UnitTests/Generator/TableGeneratorTests.cs @@ -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 databaseOneDialectMock = new Mock(); + databaseOneDialectMock.Setup(d => d.ConnectionString) + .Returns(Any.String()); + + Mock databaseTwoDialectMock = new Mock(); + 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 databaseOneDialectMock = new Mock(); + databaseOneDialectMock.Setup(d => d.ConnectionString) + .Returns(connectionString); + + Mock databaseTwoDialectMock = new Mock(); + 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); + } + } +} diff --git a/test/StrangerData.UnitTests/Utils/MemoryCacheTests.cs b/test/StrangerData.UnitTests/Utils/MemoryCacheTests.cs new file mode 100644 index 0000000..dcabd90 --- /dev/null +++ b/test/StrangerData.UnitTests/Utils/MemoryCacheTests.cs @@ -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> funcMock = new Mock>(); + funcMock.Setup(f => f()).Returns(expected); + + string nonExistentPrimaryKey = Any.String(); + string nonExistentSecondaryKey = Any.String(); + + // Act + string actual = MemoryCache.TryGetFromCache(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> funcMock = new Mock>(); + funcMock.Setup(f => f()).Returns(expected); + + string primaryKey = Any.String(); + MemoryCache.TryGetFromCache(primaryKey, Any.String(), () => Any.String()); + + string nonExistentSecondaryKey = Any.String(); + + // Act + string actual = MemoryCache.TryGetFromCache(primaryKey, nonExistentSecondaryKey, funcMock.Object); + + // Assert + actual.Should().Be(expected); + funcMock.Verify(f => f(), Times.Once); + } + + [Fact] + public void TryGetFromCache_BothKeysExists_NotInvokeFuncAndReturnExistentValue() + { + // Arrange + Mock> funcMock = new Mock>(); + funcMock.Setup(f => f()).Returns(Any.String()); + + string primaryKey = Any.String(); + string secondaryKey = Any.String(); + string expected = Any.String(); + MemoryCache.TryGetFromCache(primaryKey, secondaryKey, () => expected); + + // Act + string actual = MemoryCache.TryGetFromCache(primaryKey, secondaryKey, funcMock.Object); + + // Assert + actual.Should().Be(expected); + funcMock.Verify(f => f(), Times.Never); + } + } +}