C#でファイルをキャッシュするプログラム(EntityFramework-Sqlite)

C# コンピュータ
C#

以前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の操作が意外と時間がかかっており、このままだと使うことは出来ない感じです。

コメント