Skip to content

Commit

Permalink
fix data cache, cleanup tests
Browse files Browse the repository at this point in the history
  • Loading branch information
pwelter34 committed Sep 7, 2023
1 parent 057772f commit 77bb57f
Show file tree
Hide file tree
Showing 30 changed files with 914 additions and 363 deletions.
8 changes: 8 additions & 0 deletions .github/workflows/dotnet.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ name: Build
env:
DOTNET_NOLOGO: true
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
DOTNET_ENVIRONMENT: github
ASPNETCORE_ENVIRONMENT: github
BUILD_PATH: "${{github.workspace}}/artifacts"
COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
Expand Down Expand Up @@ -43,6 +44,13 @@ jobs:
volumes:
- postgres_data:/var/lib/postgresql/data

redis:
image: redis
ports:
- 6379:6379
volumes:
- redis_data:/data

steps:
- name: Checkout
uses: actions/checkout@v3
Expand Down
7 changes: 0 additions & 7 deletions FluentCommand.sln
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentCommand.SQLite.Tests"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentCommand.SqlServer.Tests", "test\FluentCommand.SqlServer.Tests\FluentCommand.SqlServer.Tests.csproj", "{5DE2495F-EC9E-49C5-8211-5ACB44A780A8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FluentCommand.Batch.Tests", "test\FluentCommand.Batch.Tests\FluentCommand.Batch.Tests.csproj", "{CCC34DD0-4706-44CB-AC03-A0E607F449A2}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{38D98603-4878-4CFA-ACF5-5BEC1218B149}"
ProjectSection(SolutionItems) = preProject
coverlet.runsettings = coverlet.runsettings
Expand Down Expand Up @@ -61,10 +59,6 @@ Global
{5DE2495F-EC9E-49C5-8211-5ACB44A780A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{5DE2495F-EC9E-49C5-8211-5ACB44A780A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{5DE2495F-EC9E-49C5-8211-5ACB44A780A8}.Release|Any CPU.Build.0 = Release|Any CPU
{CCC34DD0-4706-44CB-AC03-A0E607F449A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CCC34DD0-4706-44CB-AC03-A0E607F449A2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CCC34DD0-4706-44CB-AC03-A0E607F449A2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CCC34DD0-4706-44CB-AC03-A0E607F449A2}.Release|Any CPU.Build.0 = Release|Any CPU
{A003BB6E-286D-4F52-B980-992816892CEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A003BB6E-286D-4F52-B980-992816892CEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A003BB6E-286D-4F52-B980-992816892CEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -105,7 +99,6 @@ Global
{A1DC06EF-CBFB-4EE7-883D-4BF0369F2654} = {228D7B65-A886-4D86-AED0-F3CB0C61819B}
{0F3235EA-8397-4D99-AB07-DF7987409DD4} = {228D7B65-A886-4D86-AED0-F3CB0C61819B}
{5DE2495F-EC9E-49C5-8211-5ACB44A780A8} = {228D7B65-A886-4D86-AED0-F3CB0C61819B}
{CCC34DD0-4706-44CB-AC03-A0E607F449A2} = {228D7B65-A886-4D86-AED0-F3CB0C61819B}
{A003BB6E-286D-4F52-B980-992816892CEE} = {228D7B65-A886-4D86-AED0-F3CB0C61819B}
{CFCFEA34-ABFA-490E-85BF-A7DC12392245} = {228D7B65-A886-4D86-AED0-F3CB0C61819B}
{00F44ED1-561E-43DC-ABC1-A78411FFC6F3} = {228D7B65-A886-4D86-AED0-F3CB0C61819B}
Expand Down
84 changes: 74 additions & 10 deletions src/FluentCommand.Caching/DistributedDataCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,20 +8,40 @@

namespace FluentCommand.Caching;

/// <summary>
/// Distributed cache implemenation
/// </summary>
/// <seealso cref="FluentCommand.IDataCache" />
public partial class DistributedDataCache : IDataCache
{
private readonly ILogger<DistributedDataCache> _logger;
private readonly IDistributedCache _distributedCache;
private readonly IDistributedCacheSerializer _distributedCacheSerializer;

/// <summary>
/// Initializes a new instance of the <see cref="DistributedDataCache"/> class.
/// </summary>
/// <param name="logger">The logger.</param>
/// <param name="distributedCache">The distributed cache.</param>
/// <param name="distributedCacheSerializer">The distributed cache serializer.</param>
public DistributedDataCache(ILogger<DistributedDataCache> logger, IDistributedCache distributedCache, IDistributedCacheSerializer distributedCacheSerializer)
{
_logger = logger;
_distributedCache = distributedCache;
_distributedCacheSerializer = distributedCacheSerializer;
}

public T Get<T>(string key)
/// <summary>
/// Gets the specified cache entry from the cache as an object.
/// </summary>
/// <typeparam name="T">The type of item in cache</typeparam>
/// <param name="key">A unique identifier for the cache entry.</param>
/// <returns>
/// <para>Success is true if the key was found; otherwise false</para>
/// <para>Value is the cache entry that is identified by key</para>
/// </returns>
/// <exception cref="System.ArgumentException">'{nameof(key)}' cannot be null or empty. - key</exception>
public (bool Success, T Value) Get<T>(string key)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentException($"'{nameof(key)}' cannot be null or empty.", nameof(key));
Expand All @@ -31,17 +51,28 @@ public T Get<T>(string key)
if (cachedBuffer == null)
{
LogCacheMiss(_logger, key);
return default;
return (false, default);
}

var cachedItem = _distributedCacheSerializer.Deserialize<T>(cachedBuffer);

LogCacheHit(_logger, key);

return cachedItem;
return (true, cachedItem);
}

public async Task<T> GetAsync<T>(string key, CancellationToken cancellationToken = default)
/// <summary>
/// Gets the specified cache entry from the cache as an object.
/// </summary>
/// <typeparam name="T">The type of item in cache</typeparam>
/// <param name="key">A unique identifier for the cache entry.</param>
/// <param name="cancellationToken">The cancellation instruction.</param>
/// <returns>
/// <para>Success is true if the key was found; otherwise false</para>
/// <para>Value is the cache entry that is identified by key</para>
/// </returns>
/// <exception cref="System.ArgumentException">'{nameof(key)}' cannot be null or empty. - key</exception>
public async Task<(bool Success, T Value)> GetAsync<T>(string key, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(key))
throw new ArgumentException($"'{nameof(key)}' cannot be null or empty.", nameof(key));
Expand All @@ -53,7 +84,7 @@ public async Task<T> GetAsync<T>(string key, CancellationToken cancellationToken
if (cachedBuffer == null)
{
LogCacheMiss(_logger, key);
return default;
return (false, default);
}

var cachedItem = await _distributedCacheSerializer
Expand All @@ -62,9 +93,19 @@ public async Task<T> GetAsync<T>(string key, CancellationToken cancellationToken

LogCacheHit(_logger, key);

return cachedItem;
return (true, cachedItem);
}

/// <summary>
/// Inserts a cache entry into the cache, specifying information about how the entry will be evicted.
/// </summary>
/// <typeparam name="T">The type of item in cache</typeparam>
/// <param name="key">A unique identifier for the cache entry.</param>
/// <param name="value">The object to insert into cache.</param>
/// <param name="absoluteExpiration">The fixed date and time at which the cache entry will expire.</param>
/// <param name="slidingExpiration">A value that indicates whether a cache entry should be evicted if it has not been accessed in a given span of time.</param>
/// <exception cref="System.ArgumentException">'{nameof(key)}' cannot be null or empty. - key</exception>
/// <exception cref="System.ArgumentNullException">value</exception>
public void Set<T>(string key, T value, DateTimeOffset? absoluteExpiration = null, TimeSpan? slidingExpiration = null)
{
if (string.IsNullOrEmpty(key))
Expand All @@ -86,6 +127,17 @@ public void Set<T>(string key, T value, DateTimeOffset? absoluteExpiration = nul
LogCacheInsert(_logger, key);
}

/// <summary>
/// Inserts a cache entry into the cache, specifying information about how the entry will be evicted.
/// </summary>
/// <typeparam name="T">The type of item in cache</typeparam>
/// <param name="key">A unique identifier for the cache entry.</param>
/// <param name="value">The object to insert into cache.</param>
/// <param name="absoluteExpiration">The fixed date and time at which the cache entry will expire.</param>
/// <param name="slidingExpiration">A value that indicates whether a cache entry should be evicted if it has not been accessed in a given span of time.</param>
/// <param name="cancellationToken">The cancellation instruction.</param>
/// <exception cref="System.ArgumentException">'{nameof(key)}' cannot be null or empty. - key</exception>
/// <exception cref="System.ArgumentNullException">value</exception>
public async Task SetAsync<T>(string key, T value, DateTimeOffset? absoluteExpiration = null, TimeSpan? slidingExpiration = null, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(key))
Expand All @@ -112,6 +164,11 @@ await _distributedCache
LogCacheInsert(_logger, key);
}

/// <summary>
/// Removes the cache entry from the cache
/// </summary>
/// <param name="key">A unique identifier for the cache entry.</param>
/// <exception cref="System.ArgumentException">'{nameof(key)}' cannot be null or empty. - key</exception>
public void Remove(string key)
{
if (string.IsNullOrEmpty(key))
Expand All @@ -122,6 +179,12 @@ public void Remove(string key)
_distributedCache.Remove(key);
}

/// <summary>
/// Removes the cache entry from the cache
/// </summary>
/// <param name="key">A unique identifier for the cache entry.</param>
/// <param name="cancellationToken">The cancellation instruction.</param>
/// <exception cref="System.ArgumentException">'{nameof(key)}' cannot be null or empty. - key</exception>
public async Task RemoveAsync(string key, CancellationToken cancellationToken = default)
{
if (string.IsNullOrEmpty(key))
Expand All @@ -133,16 +196,17 @@ public async Task RemoveAsync(string key, CancellationToken cancellationToken =
}


[LoggerMessage(0, LogLevel.Trace, "Cache Hit; Key: '{cacheKey}'")]
[LoggerMessage(0, LogLevel.Information, "Cache Hit; Key: '{cacheKey}'")]
static partial void LogCacheHit(ILogger logger, string cacheKey);

[LoggerMessage(1, LogLevel.Trace, "Cache Miss; Key: '{cacheKey}'")]
[LoggerMessage(1, LogLevel.Information, "Cache Miss; Key: '{cacheKey}'")]
static partial void LogCacheMiss(ILogger logger, string cacheKey);

[LoggerMessage(2, LogLevel.Trace, "Cache Insert; Key: '{cacheKey}'")]
[LoggerMessage(2, LogLevel.Information, "Cache Insert; Key: '{cacheKey}'")]
static partial void LogCacheInsert(ILogger logger, string cacheKey);

[LoggerMessage(3, LogLevel.Trace, "Cache Remove; Key: '{cacheKey}'")]
[LoggerMessage(3, LogLevel.Information, "Cache Remove; Key: '{cacheKey}'")]
static partial void LogCacheRemove(ILogger logger, string cacheKey);


}
37 changes: 37 additions & 0 deletions src/FluentCommand.Caching/IDistributedCacheSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,49 @@
namespace FluentCommand.Caching;

/// <summary>
/// Interface defining a serializer of distrubted cache
/// </summary>
public interface IDistributedCacheSerializer
{
/// <summary>
/// Serializes the specified instance to a byte array for caching.
/// </summary>
/// <typeparam name="T">The type to serialize</typeparam>
/// <param name="instance">The instance to serialize.</param>
/// <returns>
/// The byte array of the serialized instance
/// </returns>
byte[] Serialize<T>(T instance);

/// <summary>
/// Serializes the specified instance to a byte array for caching.
/// </summary>
/// <typeparam name="T">The type to serialize</typeparam>
/// <param name="instance">The instance to serialize.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// The byte array of the serialized instance
/// </returns>
Task<byte[]> SerializeAsync<T>(T instance, CancellationToken cancellationToken = default);

/// <summary>
/// Deserializes the specified byte array into an instance of <typeparamref name="T" />.
/// </summary>
/// <typeparam name="T">The type to deserialize</typeparam>
/// <param name="byteArray">The byte array to deserialize.</param>
/// <returns>
/// An instance of <typeparamref name="T" /> deserialized
/// </returns>
T Deserialize<T>(byte[] byteArray);

/// <summary>
/// Deserializes the specified byte array into an instance of <typeparamref name="T" />.
/// </summary>
/// <typeparam name="T">The type to deserialize</typeparam>
/// <param name="byteArray">The byte array to deserialize.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// An instance of <typeparamref name="T" /> deserialized
/// </returns>
Task<T> DeserializeAsync<T>(byte[] byteArray, CancellationToken cancellationToken = default);
}
44 changes: 42 additions & 2 deletions src/FluentCommand.Caching/MessagePackCacheSerializer.cs
Original file line number Diff line number Diff line change
@@ -1,38 +1,78 @@
using System.Threading;

using MessagePack;
using MessagePack.Resolvers;

namespace FluentCommand.Caching;

/// <summary>
/// A MessagePack implementation of IDistributedCacheSerializer
/// </summary>
/// <seealso cref="FluentCommand.Caching.IDistributedCacheSerializer" />
public class MessagePackCacheSerializer : IDistributedCacheSerializer
{
private readonly MessagePackSerializerOptions _messagePackSerializerOptions;

/// <summary>
/// Initializes a new instance of the <see cref="MessagePackCacheSerializer"/> class.
/// </summary>
/// <param name="messagePackSerializerOptions">The message pack serializer options.</param>
public MessagePackCacheSerializer(MessagePackSerializerOptions messagePackSerializerOptions = null)
{
_messagePackSerializerOptions = messagePackSerializerOptions
?? ContractlessStandardResolver.Options.WithCompression(MessagePackCompression.Lz4BlockArray);
}

/// <summary>
/// Deserializes the specified byte array into an instance of <typeparamref name="T" />.
/// </summary>
/// <typeparam name="T">The type to deserialize</typeparam>
/// <param name="byteArray">The byte array to deserialize.</param>
/// <returns>
/// An instance of <typeparamref name="T" /> deserialized
/// </returns>
public T Deserialize<T>(byte[] byteArray)
{
var value = MessagePackSerializer.Deserialize<T>(byteArray, _messagePackSerializerOptions);
return value;
}

/// <summary>
/// Deserializes the specified byte array into an instance of <typeparamref name="T" />.
/// </summary>
/// <typeparam name="T">The type to deserialize</typeparam>
/// <param name="byteArray">The byte array to deserialize.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// An instance of <typeparamref name="T" /> deserialized
/// </returns>
public Task<T> DeserializeAsync<T>(byte[] byteArray, CancellationToken cancellationToken = default)
{
var value = MessagePackSerializer.Deserialize<T>(byteArray, _messagePackSerializerOptions, cancellationToken);
return Task.FromResult(value);
}

/// <summary>
/// Serializes the specified instance to a byte array for caching.
/// </summary>
/// <typeparam name="T">The type to serialize</typeparam>
/// <param name="instance">The instance to serialize.</param>
/// <returns>
/// The byte array of the serialized instance
/// </returns>
public byte[] Serialize<T>(T instance)
{
var value = MessagePackSerializer.Serialize(instance, _messagePackSerializerOptions);
return value;
}

/// <summary>
/// Serializes the specified instance to a byte array for caching.
/// </summary>
/// <typeparam name="T">The type to serialize</typeparam>
/// <param name="instance">The instance to serialize.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>
/// The byte array of the serialized instance
/// </returns>
public Task<byte[]> SerializeAsync<T>(T instance, CancellationToken cancellationToken = default)
{
var value = MessagePackSerializer.Serialize(instance, _messagePackSerializerOptions, cancellationToken);
Expand Down
13 changes: 13 additions & 0 deletions src/FluentCommand.SqlServer/Bulk/DataBulkCopy.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Data;
using System.Data.Common;

using FluentCommand.Extensions;

Expand Down Expand Up @@ -367,8 +368,10 @@ public void WriteToServer(DataTable table, DataRowState rowState)
{
// resolve auto map
if (_autoMap == true)
{
foreach (DataColumn column in table.Columns)
Mapping(column.ColumnName, column.ColumnName);
}

_dataSession.EnsureConnection();

Expand All @@ -394,6 +397,16 @@ public void WriteToServer(IDataReader reader)

try
{
// resolve auto map
if (_autoMap == true)
{
for (int i = 0; i < reader.FieldCount; i++)
{
var name = reader.GetName(i);
Mapping(name, name);
}
}

_dataSession.EnsureConnection();

using var bulkCopy = Create();
Expand Down
Loading

0 comments on commit 77bb57f

Please sign in to comment.