Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Asynchronous Operations for Cache Repositories #562

Open
wants to merge 23 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
93243a0
Updated AddRepository Methods to use asyn operations
mf0zz13 Sep 17, 2024
587e44e
Updated CacheManager class to be Async (#88)
mf0zz13 Sep 17, 2024
545a52a
Updated ICacheRepository making methods async
mf0zz13 Sep 17, 2024
065e147
Updated methods to async and updated method names
mf0zz13 Sep 17, 2024
79d359f
Updated Tests to test new Async Methods
mf0zz13 Sep 17, 2024
881bf37
Merge branch 'main' into main
guibranco Sep 17, 2024
0c78634
Update appveyor.yml
guibranco Sep 17, 2024
bb6897e
CSharpier linter
guibranco Sep 17, 2024
51e123d
Merge branch 'main' into main
guibranco Sep 17, 2024
1636cd4
Merge branch 'guibranco:main' into main
mf0zz13 Sep 18, 2024
24e874f
Updated Method names in CacheManager Class
mf0zz13 Sep 18, 2024
98a4bf0
Updated name of Task Remove in CacheManager
mf0zz13 Sep 18, 2024
c2f6569
Updated GetAsync Task and overloaded GetAsyncTask
mf0zz13 Sep 18, 2024
b5cba17
Updated Get and overladed task to utilize Task.FromResult when callin…
mf0zz13 Sep 18, 2024
8c09fd3
Updated Tests to run asynchronously
mf0zz13 Sep 18, 2024
0a57595
Updated SetSpecificAsync Task
mf0zz13 Sep 18, 2024
abb8458
Merge branch 'main' into main
guibranco Oct 8, 2024
1e436b6
Merge branch 'main' into main
guibranco Oct 12, 2024
e56a544
Updated ResolveDatabase to ResolveDatabaseAsync making it a async task
mf0zz13 Oct 15, 2024
c1ce45f
Updated call names to new async tasks
mf0zz13 Oct 15, 2024
f0266bd
Merge branch 'main' into main
guibranco Oct 15, 2024
a3abef8
Merge branch 'main' into main
guibranco Oct 17, 2024
9ab6038
Merge branch 'main' into main
guibranco Oct 23, 2024
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
106 changes: 50 additions & 56 deletions Src/CrispyWaffle.CouchDB/Cache/CouchDBCacheRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,16 @@
/// Finally, it converts the filtered results to a list and returns the count of those documents.
/// This is useful for determining how many valid instances of a specific document type exist in the database.
/// </remarks>
public int GetDocCount<T>()
public async Task<int> GetDocCountAsync<T>()

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Refactor this method to use all type parameters in the parameter list to enable type inference. Warning

Refactor this method to use all type parameters in the parameter list to enable type inference.
where T : CouchDBCacheDocument
{
return ResolveDatabase<T>().Where(x => x.Id != null).ToList().Count;
return (await ResolveDatabaseAsync<T>()).Where(x => x.Id != null).ToList().Count;
Comment on lines +41 to +44
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Refactor method to enable type inference for generic type parameter

The method GetDocCountAsync<T>() does not utilize the type parameter T in its parameters in a way that allows for type inference. This may require callers to explicitly specify the type parameter when invoking the method. Consider modifying the method to include T in the parameter list or refactor the method to eliminate the unnecessary generic type parameter.

🧰 Tools
🪛 GitHub Check: Sonarcsharp (reported by Codacy)

[warning] 41-41:
Refactor this method to use all type parameters in the parameter list to enable type inference.

}

/// <inheritdoc />
public void Clear()
public async Task ClearAsync()
{
Clear<CouchDBCacheDocument>();
await Task.Run(() => ClearAsync<CouchDBCacheDocument>());
}

/// <summary>
Expand All @@ -62,12 +62,12 @@
/// This method does not return any value and modifies the state of the database by removing documents.
/// </remarks>
/// <exception cref="Exception">Thrown when an error occurs during the deletion process, unless exceptions are suppressed.</exception>
public void Clear<T>()
public async Task ClearAsync<T>()
where T : CouchDBCacheDocument
{
try
{
var db = ResolveDatabase<T>();
var db = await ResolveDatabaseAsync<T>();
var docs = db.Where(x => x.Id != null).ToList();
var tasks = new List<Task>(docs.Count);

Expand All @@ -76,7 +76,7 @@
tasks.Add(db.DeleteAsync(doc));
}

Task.WaitAll(tasks.ToArray());
await Task.Run(() => tasks.ToArray());
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use Task.WhenAll to await completion of all tasks

In the ClearAsync<T>() method, using await Task.Run(() => tasks.ToArray()); is not the optimal way to wait for multiple tasks to complete. Instead, use await Task.WhenAll(tasks); to efficiently await all tasks concurrently.

Apply this diff to correct the implementation:

- await Task.Run(() => tasks.ToArray());
+ await Task.WhenAll(tasks);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await Task.Run(() => tasks.ToArray());
await Task.WhenAll(tasks);

}
catch (Exception e)
{
Expand All @@ -90,14 +90,14 @@
}

/// <inheritdoc />
public T Get<T>(string key)
public async Task<T> GetAsync<T>(string key)

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Refactor this method to use all type parameters in the parameter list to enable type inference. Warning

Refactor this method to use all type parameters in the parameter list to enable type inference.
{
if (!typeof(CouchDBCacheDocument).IsAssignableFrom(typeof(T)))
{
return default;
}

return (T)(object)GetSpecific<CouchDBCacheDocument>(key);
return (T)(object)await GetSpecificAsync<CouchDBCacheDocument>(key);
Comment on lines +93 to +100
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Refactor GetAsync<T> to enable type inference

The method GetAsync<T>(string key) may prevent type inference because the generic type parameter T is not used in a way that allows the compiler to infer it. This can necessitate explicit type declarations when calling the method. Consider adjusting the method signature or parameters to facilitate type inference.

🧰 Tools
🪛 GitHub Check: Sonarcsharp (reported by Codacy)

[warning] 93-93:
Refactor this method to use all type parameters in the parameter list to enable type inference.

}

/// <summary>
Expand All @@ -111,37 +111,38 @@
/// </returns>
/// <remarks>
/// This method checks if the specified type <typeparamref name="T"/> is assignable from <see cref="CouchDBCacheDocument"/>.
/// If it is, it calls the method <see cref="GetSpecific{CouchDBCacheDocument}"/> to retrieve the cached document associated with the provided keys.
/// If it is, it calls the method <see cref="GetSpecificAsync{CouchDBCacheDocument}"/> to retrieve the cached document associated with the provided keys.
/// If the type is not assignable, it returns the default value for that type, which could be null for reference types or zero for numeric types.
/// This method is useful for retrieving cached data in a type-safe manner, ensuring that only compatible types are processed.
/// </remarks>
public T Get<T>(string key, string subKey)
public async Task<T> GetAsync<T>(string key, string subKey)

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Refactor this method to use all type parameters in the parameter list to enable type inference. Warning

Refactor this method to use all type parameters in the parameter list to enable type inference.
{
if (!typeof(CouchDBCacheDocument).IsAssignableFrom(typeof(T)))
{
return default;
}

return (T)(object)GetSpecific<CouchDBCacheDocument>(key, subKey);
return (T)(object)await Task.FromResult(GetSpecificAsync<CouchDBCacheDocument>(key, subKey));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Simplify redundant use of Task.FromResult

At line 125, await Task.FromResult(GetSpecificAsync<CouchDBCacheDocument>(key, subKey)) introduces unnecessary wrapping of the task. Since GetSpecificAsync already returns a Task, you can await it directly without wrapping it in Task.FromResult.

Apply this diff to simplify the code:

- return (T)(object)await Task.FromResult(GetSpecificAsync<CouchDBCacheDocument>(key, subKey));
+ return (T)(object)await GetSpecificAsync<CouchDBCacheDocument>(key, subKey);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return (T)(object)await Task.FromResult(GetSpecificAsync<CouchDBCacheDocument>(key, subKey));
return (T)(object)await GetSpecificAsync<CouchDBCacheDocument>(key, subKey);

}


/// <summary>
/// Gets from a class specified database instead of the general <see cref="CouchDBCacheDocument"/> database.
/// </summary>
/// <typeparam name="T">Type T with base type <see cref="CouchDBCacheDocument"/>.</typeparam>
/// <param name="key">A uniquely identifiable key to get document from the specified database.</param>
/// <returns>The document if found.</returns>
/// <exception cref="InvalidOperationException">Thrown in case the operation fails.</exception>
public T GetSpecific<T>(string key)
public async Task<T> GetSpecificAsync<T>(string key)

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Refactor this method to use all type parameters in the parameter list to enable type inference. Warning

Refactor this method to use all type parameters in the parameter list to enable type inference.
where T : CouchDBCacheDocument
{
try
{
var doc = ResolveDatabase<T>().Where(x => x.Key == key).FirstOrDefault();
var doc = (await ResolveDatabaseAsync<T>()).Where(x => x.Key == key).FirstOrDefault();

if (doc != default && doc.ExpiresAt != default && doc.ExpiresAt <= DateTime.UtcNow)
{
RemoveSpecific<T>(key);
await RemoveSpecificAsync<T>(key);
return default;
}

Expand Down Expand Up @@ -175,18 +176,16 @@
/// If no document is found and no exceptions are thrown, an <see cref="InvalidOperationException"/> is thrown indicating that the item could not be retrieved.
/// </remarks>
/// <exception cref="InvalidOperationException">Thrown when unable to retrieve the item with the specified key and sub key.</exception>
public T GetSpecific<T>(string key, string subKey)
public async Task<T> GetSpecificAsync<T>(string key, string subKey)

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Refactor this method to use all type parameters in the parameter list to enable type inference. Warning

Refactor this method to use all type parameters in the parameter list to enable type inference.
where T : CouchDBCacheDocument
{
try
{
var doc = ResolveDatabase<T>()
.Where(x => x.Key == key && x.SubKey == subKey)
.FirstOrDefault();
var doc = (await ResolveDatabaseAsync<T>()).Where(x => x.Key == key && x.SubKey == subKey).FirstOrDefault();

if (doc != default && doc.ExpiresAt != default && doc.ExpiresAt <= DateTime.UtcNow)
{
RemoveSpecific<T>(key, subKey);
await RemoveSpecificAsync<T>(key, subKey);
return default;
}

Expand All @@ -208,10 +207,7 @@
}

/// <inheritdoc />
public void Remove(string key)
{
RemoveSpecific<CouchDBCacheDocument>(key);
}
public async Task RemoveAsync(string key) => await RemoveSpecificAsync<CouchDBCacheDocument>(key);

/// <summary>
/// Removes a specific entry from the cache based on the provided key and subKey.
Expand All @@ -224,17 +220,14 @@
/// It is important to ensure that both keys are correctly specified to successfully remove the intended entry from the cache.
/// If the specified entry does not exist, no action will be taken, and no exceptions will be thrown.
/// </remarks>
public void Remove(string key, string subKey)
{
RemoveSpecific<CouchDBCacheDocument>(key, subKey);
}
public async Task RemoveAsync(string key, string subKey) => await RemoveSpecificAsync<CouchDBCacheDocument>(key, subKey);

/// <summary>
/// Removes from a class specified database instead of the general <see cref="CouchDBCacheDocument"/> database.
/// </summary>
/// <typeparam name="T">Type T with base type <see cref="CouchDBCacheDocument"/>.</typeparam>
/// <param name="key">A uniquely identifiable key to remove document from the specified database.</param>
public void RemoveSpecific<T>(string key)
public async Task RemoveSpecificAsync<T>(string key)

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Refactor this method to use all type parameters in the parameter list to enable type inference. Warning

Refactor this method to use all type parameters in the parameter list to enable type inference.
where T : CouchDBCacheDocument
{
try
Expand All @@ -244,7 +237,7 @@

if (doc != default)
{
db.DeleteAsync(doc).Wait();
await db.DeleteAsync(doc);
}
}
catch (Exception e)
Expand All @@ -271,7 +264,7 @@
/// <see cref="ShouldPropagateExceptions"/>. If exceptions are not propagated, they are logged using the
/// <see cref="LogConsumer"/>. This method does not return any value.
/// </remarks>
public void RemoveSpecific<T>(string key, string subKey)
public async Task RemoveSpecificAsync<T>(string key, string subKey)

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Refactor this method to use all type parameters in the parameter list to enable type inference. Warning

Refactor this method to use all type parameters in the parameter list to enable type inference.
where T : CouchDBCacheDocument
{
try
Expand All @@ -281,7 +274,7 @@

if (doc != default)
{
db.DeleteAsync(doc).Wait();
await db.DeleteAsync(doc);
}
}
catch (Exception e)
Expand All @@ -296,14 +289,14 @@
}

/// <inheritdoc />
public void Set<T>(T value, string key, TimeSpan? ttl = null)
public async Task SetAsync<T>(T value, string key, TimeSpan? ttl = null)

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Use the overloading mechanism instead of the optional parameters. Warning

Use the overloading mechanism instead of the optional parameters.
{
if (!typeof(CouchDBCacheDocument).IsAssignableFrom(typeof(T)))
{
return;
}

SetSpecific((CouchDBCacheDocument)(object)value, key, ttl);
await SetSpecificAsync((CouchDBCacheDocument)(object)value, key, ttl);
Comment on lines +292 to +299
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use method overloading instead of optional parameters

The method SetAsync<T>(T value, string key, TimeSpan? ttl = null) uses an optional parameter ttl. Utilizing method overloading instead of optional parameters can enhance clarity and reduce potential issues with default values. Consider creating overloaded methods to handle different scenarios.

🧰 Tools
🪛 GitHub Check: Sonarcsharp (reported by Codacy)

[warning] 292-292:
Use the overloading mechanism instead of the optional parameters.

}

/// <summary>
Expand All @@ -316,17 +309,17 @@
/// <remarks>
/// This method checks if the provided type <typeparamref name="T"/> is assignable from <see cref="CouchDBCacheDocument"/>.
/// If it is not, the method returns without performing any action.
/// If the type is valid, it calls the <see cref="SetSpecific"/> method to set the value in the cache.
/// If the type is valid, it calls the <see cref="SetSpecificAsync"/> method to set the value in the cache.
/// This allows for type-safe caching of documents that inherit from <see cref="CouchDBCacheDocument"/>.
/// </remarks>
public void Set<T>(T value, string key, string subKey)
public async Task SetAsync<T>(T value, string key, string subKey)
{
if (!typeof(CouchDBCacheDocument).IsAssignableFrom(typeof(T)))
{
return;
}

SetSpecific((CouchDBCacheDocument)(object)value, key, subKey);
await SetSpecificAsync((CouchDBCacheDocument)(object)value, key, subKey);
}

/// <summary>
Expand All @@ -336,7 +329,7 @@
/// <param name="value">The value of type T to be persisted.</param>
/// <param name="key">A uniquely identifiable key to remove document from the specified database.</param>
/// <param name="ttl">How long the value should be stored.</param>
public void SetSpecific<T>(T value, string key, TimeSpan? ttl = null)
public async Task SetSpecificAsync<T>(T value, string key, TimeSpan? ttl = null)

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Use the overloading mechanism instead of the optional parameters. Warning

Use the overloading mechanism instead of the optional parameters.
where T : CouchDBCacheDocument
{
try
Expand All @@ -349,7 +342,7 @@
value.ExpiresAt = DateTime.UtcNow.Add(ttl.Value);
}

ResolveDatabase<T>().CreateAsync(value).Wait();
await (await ResolveDatabaseAsync<T>()).CreateAsync(value);
}
catch (Exception e)
{
Expand Down Expand Up @@ -377,15 +370,15 @@
/// is rethrown; otherwise, it is logged using the LogConsumer.
/// </remarks>
/// <exception cref="Exception">Thrown when an error occurs during the database operation, unless exceptions are suppressed.</exception>
public void SetSpecific<T>(T value, string key, string subKey)
public async Task SetSpecificAsync<T>(T value, string key, string subKey)
where T : CouchDBCacheDocument
{
try
{
value.Key = key;
value.SubKey = subKey;

ResolveDatabase<T>().CreateOrUpdateAsync(value).Wait();
await (await ResolveDatabaseAsync<T>()).CreateOrUpdateAsync(value);
}
catch (Exception e)
{
Expand All @@ -399,18 +392,18 @@
}

/// <inheritdoc />
public bool TryGet<T>(string key, out T value)
public async Task<(bool Exists, T value)> TryGetAsync<T>(string key)

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Refactor this method to use all type parameters in the parameter list to enable type inference. Warning

Refactor this method to use all type parameters in the parameter list to enable type inference.
{
var get = Get<CouchDBCacheDocument>(key);

var get = await GetAsync<CouchDBCacheDocument>(key);
T value;
if (get != default)
{
value = (T)(object)get;
return true;
return (true, value);
}

value = default;
return false;
return (false, value);
}

/// <summary>
Expand All @@ -427,18 +420,18 @@
/// If no document is found, <paramref name="value"/> is set to its default value, and the method returns false.
/// This is useful for safely attempting to retrieve values without throwing exceptions if the keys do not exist in the cache.
/// </remarks>
public bool TryGet<T>(string key, string subKey, out T value)
public async Task<(bool Exists, T value)> TryGetAsync<T>(string key, string subKey)

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Refactor this method to use all type parameters in the parameter list to enable type inference. Warning

Refactor this method to use all type parameters in the parameter list to enable type inference.
{
var get = Get<CouchDBCacheDocument>(key, subKey);

var get = await GetAsync<CouchDBCacheDocument>(key, subKey);
T value;
if (get != default)
{
value = (T)(object)get;
return true;
return (true, value);
}

value = default;
return false;
return (false, value);
}

/// <summary>
Expand All @@ -454,9 +447,10 @@
/// This method assumes that the key exists in the cache; if it does not, the behavior will depend
/// on the implementation of the Get method.
/// </remarks>
public TimeSpan TTL(string key)
public async Task<TimeSpan> TTLAsync(string key)

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Rename method 'TTLAsync' to match pascal case naming rules, consider using 'TtlAsync'. Warning

Rename method 'TTLAsync' to match pascal case naming rules, consider using 'TtlAsync'.
{
return Get<CouchDBCacheDocument>(key).TTL;
var result = await GetAsync<CouchDBCacheDocument>(key);
return result.TTL;
Comment on lines +452 to +453
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add null check to prevent potential NullReferenceException

In the TTLAsync method, if result is null, accessing result.TTL will throw a NullReferenceException. Add a null check to handle cases where the key does not exist.

Apply this change to handle the null case:

var result = await GetAsync<CouchDBCacheDocument>(key);
if (result == null)
{
    // Handle the null case appropriately
    return TimeSpan.Zero; // or throw an exception if preferred
}
return result.TTL;

}

/// <summary>
Expand All @@ -471,16 +465,16 @@
/// ensuring that it handles database creation and retrieval efficiently. The use of generics allows for flexibility in specifying the type of documents
/// that will be stored in the database, making this method suitable for various CouchDBCacheDocument types.
/// </remarks>
private CouchDatabase<T> ResolveDatabase<T>(string dbName = default)
private async Task<CouchDatabase<T>> ResolveDatabaseAsync<T>(string dbName = default)

Check warning

Code scanning / Sonarcsharp (reported by Codacy)

Refactor this method to use all type parameters in the parameter list to enable type inference. Warning

Refactor this method to use all type parameters in the parameter list to enable type inference.
where T : CouchDBCacheDocument
{
if (string.IsNullOrEmpty(dbName))
{
dbName = $"{typeof(T).Name.ToLowerInvariant()}s";
}

return !_connector.CouchDBClient.GetDatabasesNamesAsync().Result.Contains(dbName)
? _connector.CouchDBClient.CreateDatabaseAsync<T>().Result
return !(await _connector.CouchDBClient.GetDatabasesNamesAsync()).Contains(dbName)
? (await _connector.CouchDBClient.CreateDatabaseAsync<T>())
Comment on lines +468 to +477
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Refactor method to enable type inference for generic type parameter

The method ResolveDatabaseAsync<T>(string dbName = default) does not utilize the type parameter T in its parameters in a way that allows for type inference. This may require callers to explicitly specify the type parameter when invoking the method. Consider modifying the method signature to facilitate type inference.

🧰 Tools
🪛 GitHub Check: Sonarcsharp (reported by Codacy)

[warning] 468-468:
Refactor this method to use all type parameters in the parameter list to enable type inference.

: _connector.CouchDBClient.GetDatabase<T>();
}

Expand Down
Loading
Loading