PommaLabs.KVLite.AspNetCore 12.3.0

Prefix Reserved
This package has a SemVer 2.0.0 package version: 12.3.0+42575bc3.
dotnet add package PommaLabs.KVLite.AspNetCore --version 12.3.0                
NuGet\Install-Package PommaLabs.KVLite.AspNetCore -Version 12.3.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="PommaLabs.KVLite.AspNetCore" Version="12.3.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add PommaLabs.KVLite.AspNetCore --version 12.3.0                
#r "nuget: PommaLabs.KVLite.AspNetCore, 12.3.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install PommaLabs.KVLite.AspNetCore as a Cake Addin
#addin nuget:?package=PommaLabs.KVLite.AspNetCore&version=12.3.0

// Install PommaLabs.KVLite.AspNetCore as a Cake Tool
#tool nuget:?package=PommaLabs.KVLite.AspNetCore&version=12.3.0                

KVLite

License: MIT Donate Docs NuGet version NuGet downloads

standard-readme compliant GitLab pipeline status Quality gate Code coverage Renovate enabled

KVLite is a partition-based key-value cache built for SQL RDBMSs.

Library is feature complete and no further development is planned on this project, except for routine maintenance and bug fixes.

KVLite entries can be stored either in persistent or volatile fashion, and each key/value pair can have its own lifetime and refresh mode.

Entries are grouped by partition, where each key must be unique only inside the partition it belongs to.

Following RDBMSs are currently supported by KVLite:

  • MySQL and MariaDB (.NET and .NET Core, with integration tests for MySQL only)
  • PostgreSQL (.NET and .NET Core, with integration tests)
  • SQL Server (.NET and .NET Core, with integration tests)
  • SQLite (.NET and .NET Core, with integrations tests)

KVLite implements different caching adapters for various libraries and frameworks:

An in-memory driver is also provided for unit testing.

Table of Contents

Install

KVLite requires a driver for your RDBMS, packages are linked above. For example, if you use MySQL, the you should install:

dotnet add package PommaLabs.KVLite.MySql

After that, you could install specific adapters (like the one for ASP.NET Core) and you should set your DB up according to the documentation below.

Storage layout

KVLite stores cache entries in a dedicated table, whose schema is as much tuned as possible for each RDBMS. The logical schema for cache entries table is the following:

Column name Data type Content
kvle_id guid or long Automatically generated ID. This is the primary key.
kvle_hash long Hash of partition and key. This is the unique key.
kvle_expiry long When the entry will expire, expressed as seconds after UNIX epoch.
kvle_interval long How many seconds should be used to extend expiry time when the entry is retrieved.
kvle_value byte[] Serialized and optionally compressed content of this entry.
kvle_compressed bool Whether the entry content was compressed or not.
kvle_partition string A partition holds a group of related keys.
kvle_key string A key uniquely identifies an entry inside a partition.
kvle_creation long When the entry was created, expressed as seconds after UNIX epoch.
kvle_parent_hash0 long Optional parent entry hash, used to link entries in a hierarchical way.
kvle_parent_key0 string Optional parent entry key, used to link entries in a hierarchical way.
kvle_parent_hash1 long Optional parent entry hash, used to link entries in a hierarchical way.
kvle_parent_key1 string Optional parent entry key, used to link entries in a hierarchical way.
kvle_parent_hash2 long Optional parent entry hash, used to link entries in a hierarchical way.
kvle_parent_key2 string Optional parent entry key, used to link entries in a hierarchical way.

If SQL user chosen for KVLite has enough privileges, then cache entries table will be automatically created. Anyway, specialized schemas for supported RDBMS systems are available inside this project repository, at following links:

Each script might have a few comments suggesting how to further optimize cache entries table storage depending on the actual version of the specific RDBMS system.

Customizing SQL layout

Default name for cache entries table is kvle_cache_entries and default SQL schema is kvlite. However, those values can be easily changed at runtime, as we do in the following snippet:

// Change cache entries table name for PostgreSQL cache.
PostgreSqlCache.DefaultInstance.Settings.CacheEntriesTableName = "my_custom_name";

// Change SQL schema name for MySQL cache.
MySqlCache.DefaultInstance.Settings.CacheSchemaName = "my_schema_name";

// Change both table ans schema name for SQL Server cache.
SqlServerCache.DefaultInstance.Settings.CacheEntriesTableName = "my_custom_name";
SqlServerCache.DefaultInstance.Settings.CacheSchemaName = "my_schema_name";

Please perform those customizations as early as your application starts; for example, these are good places where to put the lines:

  • Program.cs for console and Windows applications.
  • Global.asax.cs for classic web applications.
  • Startup.cs for Owin-based web applications or ASP.NET Core.

Usage

Let's start with a simple example of what you can do with KVLite:

/// <summary>
///   Learn how to use KVLite by examples.
/// </summary>
internal static class Program
{
    /// <summary>
    ///   Learn how to use KVLite by examples.
    /// </summary>
    public static void Main()
    {
        // Some variables used in the examples.
        var examplePartition1 = "example partition 1";
        var examplePartition2 = "example partition 2";
        var exampleKey1 = "example key 1";
        var exampleKey2 = "example key 2";
        var simpleValue = Math.PI;
        var complexValue = new ComplexValue
        {
            Integer = 21,
            NullableBoolean = null,
            String = "Learning KVLite",
            Dictionary = new Dictionary<short, ComplexValue>
            {
                [1] = new ComplexValue { NullableBoolean = true },
                [2] = new ComplexValue { String = "Nested..." }
            }
        };

        /*
         * KVLite stores its values inside a given partition and each value is linked to a key.
         * KVLite can contain more than one partition and each partition can contain more than one key.
         *
         * Therefore, values are stored according to this logical layout:
         *
         * [partition1] --> key1/value1
         *              --> key2/value2
         * [partition2] --> key1/value1
         *              --> key2/value2
         *              --> key3/value3
         *
         * A key is unique inside a partition, not inside all cache.
         * A partition, instead, is unique inside all cache.
         */

        // You can start using the default caches immediately. Let's try to store some values in
        // a way similar to the figure above, using the default persistent cache.
        ICache persistentCache = PersistentCache.DefaultInstance;
        persistentCache.AddTimed(examplePartition1, exampleKey1, simpleValue, persistentCache.Clock.UtcNow + TimeSpan.FromMinutes(5));
        persistentCache.AddTimed(examplePartition1, exampleKey2, simpleValue, persistentCache.Clock.UtcNow + TimeSpan.FromMinutes(10));
        persistentCache.AddTimed(examplePartition2, exampleKey1, complexValue, persistentCache.Clock.UtcNow + TimeSpan.FromMinutes(10));
        persistentCache.AddTimed(examplePartition2, exampleKey2, complexValue, persistentCache.Clock.UtcNow + TimeSpan.FromMinutes(5));
        PrettyPrint(persistentCache);

        // Otherwise, you can customize you own cache... Let's see how we can use a volatile
        // cache. Let's define the settings that we will use in new volatile caches.
        var volatileCacheSettings = new VolatileCacheSettings
        {
            CacheName = "My In-Memory Cache", // The backend.
        };

        // Then the settings that we will use in new persistent caches.
        var persistentCacheSettings = new PersistentCacheSettings
        {
            CacheFile = "CustomCache.sqlite", // The SQLite DB used as the backend for the cache.
            ChancesOfAutoCleanup = 0.5, // Chance of an automatic a cache cleanup being issued.
        };

        // We create both a volatile and a persistent cache.
        var volatileCache = new VolatileCache(volatileCacheSettings);
        persistentCache = new PersistentCache(persistentCacheSettings);

        // Use the new volatile cache!
        volatileCache.AddTimed(examplePartition1, exampleKey1, Tuple.Create("Volatile!", 123), TimeSpan.FromMinutes(60));
        PrettyPrint(volatileCache);

        // Use the new persistent cache!
        persistentCache.AddTimed(examplePartition2, exampleKey2, Tuple.Create("Persistent!", 123), TimeSpan.FromMinutes(60));
        PrettyPrint(persistentCache);

        /*
         * An entry can be added to the cache in three different ways.
         *
         * "Timed" values last until the specified date and time, or for a specified timespan.
         * Reading them will not extend their lifetime.
         *
         * "Sliding" values last for the specified lifetime, but, if read,
         * their lifetime will be extended by the timespan specified initially.
         */

        // Let's clear the volatile cache and let's a value for each type.
        volatileCache.Clear();
        volatileCache.AddTimed(examplePartition1, exampleKey1, simpleValue, volatileCache.Clock.UtcNow + TimeSpan.FromMinutes(10));
        volatileCache.AddTimed(examplePartition1, exampleKey2, complexValue, TimeSpan.FromMinutes(15));
        volatileCache.AddSliding(examplePartition2, exampleKey2, complexValue, TimeSpan.FromMinutes(15));
        PrettyPrint(volatileCache);
    }

    private static void PrettyPrint(ICache cache)
    {
        Console.WriteLine($"Printing the contents of a {cache.GetType().Name}");

        // When we use "Peek*" methods, the expiration time of entries is left untouched.
        var cacheEntries = cache.PeekEntries<object>();
        foreach (var cacheEntry in cacheEntries.OrderBy(ci => ci.Partition).ThenBy(ci => ci.Key))
        {
            Console.WriteLine($"{cacheEntry.Partition} --> {cacheEntry.Key} --> {cacheEntry.Value}");
        }

        Console.WriteLine();
    }

    private sealed class ComplexValue
    {
        public int Integer { get; set; }
        public bool? NullableBoolean { get; set; }
        public string String { get; set; }
        public IDictionary<short, ComplexValue> Dictionary { get; set; }

        public override string ToString() => nameof(ComplexValue);
    }
}

Polly cache provider

Moreover, KVLite can be used as Polly cache provider:

// Every KVLite cache can be interfaced with Polly: Memory, MySQL, PostgreSQL, ...
var options = new KVLiteCacheProviderOptions();
var cacheProvider = new KVLiteSyncCacheProvider<string>(PersistentCache.DefaultInstance, options);
var cachePolicy = Policy.Cache(cacheProvider, TimeSpan.FromMinutes(10));

var myGuid1 = cachePolicy.Execute(() => Guid.NewGuid().ToString(), new Context("MyGuid"));
var myGuid2 = cachePolicy.Execute(() => Guid.NewGuid().ToString(), new Context("MyGuid"));

// Two GUIDs are equal because they share the same key.
Debug.Assert(myGuid1 == myGuid2);

myGuid1 = cachePolicy.Execute(() => Guid.NewGuid().ToString(), new KVLiteContext("My", "Complex", "Key", 1));
myGuid2 = cachePolicy.Execute(() => Guid.NewGuid().ToString(), new KVLiteContext("My", "Complex", "Key", 2));

// Two GUIDs are not equal because they do not share the same key.
Debug.Assert(myGuid1 != myGuid2);

Examples

Further examples can be found in the following projects:

  • ASP.NET Core: It shows how to register KVLite services and how to use it as a proper distributed cache implementation. Moreover, it shows how to use KVLite session extensions.

Maintainers

@pomma89.

Contributing

MRs accepted.

Small note: If editing the README, please conform to the standard-readme specification.

Editing

Visual Studio Code, with Remote Containers extension, is the recommended way to work on this project.

A development container has been configured with all required tools.

Visual Studio Community is also supported and an updated solution file, kvlite.sln, has been provided.

Restoring dependencies

When opening the development container, dependencies should be automatically restored.

Anyway, dependencies can be restored with following command:

dotnet restore

Running tests

Tests can be run with following command:

dotnet test

Tests can also be run with following command, which collects coverage information:

./build.sh --target run-tests 

License

MIT © 2014-2023 PommaLabs Team and Contributors

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
12.3.0 2,205 11/15/2023
12.2.1 2,825 12/18/2022
12.2.0 135 12/17/2022
12.1.0 136 12/16/2022
12.0.0 9,353 1/23/2022
11.1.0 2,527 11/28/2021
11.0.7 918 9/24/2021
11.0.5 2,066 4/25/2021
11.0.4 948 3/17/2021
11.0.3 458 3/14/2021
11.0.2 692 1/16/2021
11.0.1 485 12/27/2020
11.0.0 980 11/1/2020
10.1.1 533 10/26/2020
10.1.0 913 8/12/2020
10.0.5 563 6/19/2020
10.0.4 532 6/14/2020
10.0.3 546 6/3/2020
10.0.2 504 6/1/2020
10.0.0 609 3/9/2020
9.3.3 539 3/2/2020
9.2.7 586 2/7/2020
9.2.6 545 2/6/2020
9.2.5 544 2/5/2020
9.2.4 870 1/4/2020
9.2.3 751 1/2/2020
9.1.2 1,041 8/31/2019
9.0.6 972 5/5/2019
9.0.5 1,207 2/26/2019
9.0.4 623 2/25/2019
9.0.3 738 12/28/2018
8.1.3 906 8/17/2018
8.1.2 884 8/16/2018
8.1.0 874 8/14/2018
8.0.2 972 7/7/2018
8.0.1 885 6/12/2018
7.2.3 910 4/5/2018
7.2.2 946 4/1/2018
7.2.1 1,094 4/1/2018
7.1.1 1,104 3/24/2018
7.1.0 976 3/18/2018
7.0.3 979 3/6/2018
7.0.2 984 3/6/2018
6.4.4 1,190 11/1/2017
6.4.3 999 10/29/2017