以前MemoryCacheを使ってメモリ上でキャッシュをしてみましたが、今回はローカルストレージにファイルとしてキャッシュします。キャッシュの保存先のストレージが高速であればレスポンスが良くなるのではと考えています。
プロジェクト作成
mkdir <プロジェクト>
cd <プロジェクト>
dotnet new console -f net6.0
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Sqlite.Core
dotnet add package SQLitePCLRaw.bundle_green
ソースコード
ファイル名:FileCacheField.cs
using System.ComponentModel.DataAnnotations.Schema;
using System.ComponentModel.DataAnnotations;
namespace FileCache01;
class FileCacheField
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Key]
    public string Path { get; set; } = "";
    public long Size { get; set; } = 0;
    public string LastUpdate { get; set; } = "";
    public string GUID { get; set; }
    public FileCacheField()
    {
        LastUpdate = DateTime.Now.ToString("yyyyMMddHHmmss");
        GUID = Guid.NewGuid().ToString("N");
    }
}ファイル名:FileCacheDB.cs
namespace FileCache01;
class FileCacheDB : IDisposable
{
    SqliteDbContext _context;
    public FileCacheDB(string dbPath = @"./FileCache01.db")
    {
        _context = new SqliteDbContext()
        {
            DbPath = dbPath,
        };
        _context.Database.EnsureCreated();
    }
    public void Dispose()
    {
        _context.Dispose();
    }
    public FileCacheField[] Get(string path) => _context.FileCache.Where(x => x.Path == path).ToArray();
    public void Add(string path, DateTime lastUpdate, long size)
    {
        FileCacheField field = new()
        {
            Path = path,
            LastUpdate = lastUpdate.ToString("yyyyMMddHHmmss"),
            Size = size,
        };
        _context.FileCache.Add(field);
        _context.SaveChanges();
    }
    
    public void Delete(string path)
    {
        foreach(var r in _context.FileCache.Where(x => x.Path == path))
        {
            _context.FileCache.Remove(r);
            _context.SaveChanges();
        }
    }
}ファイル名:FileCacheManager.cs
using System.Data.Common;
using System.Diagnostics;
namespace FileCache01;
class FileCacheManager : IDisposable
{
    FileCacheDB _db;
    string _cacheDir;
    public FileCacheManager(string dbPath = @".\FileCache.db", string cacheDir = @".\.cache")
    {
        _db = new(dbPath);
        _cacheDir = cacheDir;
        if (!Path.Exists(_cacheDir))
            Directory.CreateDirectory(_cacheDir);
    }
    public MemoryStream Get(string path)
    {
        MemoryStream ms = new();
        FileInfo fi = new(path);
        Stopwatch sw = new();
        sw.Start();
        var r = _db.Get(path);
        if (r.Length > 0)
        {
            sw.Stop();
            Debug.Print($"_db.Get() {sw.ElapsedMilliseconds}ms");
            if( r[0].Size == fi.Length && r[0].LastUpdate == fi.LastWriteTime.ToString("yyyyMMddHHmmss"))
            {
                string cachePath = Path.Join(_cacheDir, r[0].GUID);
                if (!Path.Exists(cachePath))
                {
                    _db.Delete(path);
                    return ms;
                }
                using FileStream fs = new (cachePath, FileMode.Open, FileAccess.Read);
                fs.CopyTo(ms);
                ms.Seek(0, SeekOrigin.Begin);
                return ms;
            }
        }
        return ms;
    }
    public void Set(string path, MemoryStream ms)
    {
        FileInfo fi = new(path);
        Stopwatch sw = new();
        sw.Start();
        _db.Add(path, fi.LastWriteTime, fi.Length);
        sw.Stop();
        Debug.Print($"_db.Add() {sw.ElapsedMilliseconds}ms");
        var r = _db.Get(path);
        string cachePath = Path.Join(_cacheDir, r[0].GUID);
        if (Path.Exists(cachePath))
            File.Delete(cachePath);
        using FileStream fs = new (cachePath, FileMode.CreateNew, FileAccess.Write);
        ms.Seek(0, SeekOrigin.Begin);
        ms.CopyTo(fs);
    }
    public void Delete(string path) => _db.Delete(path);
    public void Dispose()
    {
        _db.Dispose();
    }
}ファイル名:SqliteDbContext.cs
using Microsoft.EntityFrameworkCore;
namespace FileCache01;
class SqliteDbContext : DbContext
{
    public DbSet<FileCacheField> FileCache =>
        Set<FileCacheField>();
    
    public string DbPath {get; set;} =
        @".\FileCache01.db";
    
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) =>
        optionsBuilder.UseSqlite($"Data Source={DbPath}");    
    protected override void OnModelCreating(ModelBuilder modelBuilder) =>
        modelBuilder.Entity<FileCacheField>().ToTable("FileCacheField");
}ファイル名:Program.cs
using System.Diagnostics;
using System.Formats.Tar;
using FileCache01;
class Program
{
    public static void Main()
    {
        System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
        var fcm = new FileCacheManager();
        const string tragetFile = @"H:\Pictures\202410081107.PNG";
        Stopwatch sw = new();
        sw.Start();
        using MemoryStream ms = fcm.Get(tragetFile);
        if (ms.Length > 0)
        {
            sw.Stop();
            Console.WriteLine($"キャッシュに有り サイズ:{ms.Length}Byte 時間:{sw.ElapsedMilliseconds}ms");
        }
        else
        {
            using FileStream fs = new (tragetFile, FileMode.Open, FileAccess.Read);
            ms.Seek(0, SeekOrigin.Begin);
            fs.CopyTo(ms);
            fcm.Set(tragetFile, ms);
            sw.Stop();
            Console.WriteLine($"キャッシュに無し サイズ:{ms.Length}Byte 時間:{sw.ElapsedMilliseconds}ms");
        }
    }
}
実行
実行初回
_db.Add() 306ms
キャッシュに無し サイズ:6955Byte 時間:757ms実行2回目
_db.Get() 457ms
キャッシュに有り サイズ:6955Byte 時間:465ms作り方に問題があるのかDBの操作が意外と時間がかかっており、このままだと使うことは出来ない感じです。
 
  
  
  
  
コメント