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

C# コンピュータ
C#

EntityFrameworkとSQLiteの組み合わせが遅かったので、今回はSQLiteのみで同じプログラムを書いてみました。

ソースコード

ファイル名:FileCacheDB.cs

using System.Data.SQLite;

namespace FileCache03;

class FileCacheDB : IDisposable
{
    SQLiteConnection _conn;

    public FileCacheDB(string dbPath = @".\FileCache.db")
    {
        _conn = new SQLiteConnection();
        _conn.ConnectionString =
            string.Format("Data Source = {0}", dbPath);
        _conn.Open();

        using (SQLiteCommand cmd = new (_conn))
        {
            cmd.CommandText = 
                $"CREATE TABLE IF NOT EXISTS FILE_CACHE (PATH string, LENGTH number, LAST_UPDATE string, GUID string, primary key(PATH))";
            cmd.ExecuteNonQuery();
        }
    }

    public void Dispose()
    {
        _conn.Dispose();
    }
    public FileCacheEntity? Get(string path)
    {
        using SQLiteCommand cmd = new (_conn);
        cmd.CommandText = 
            $"SELECT LENGTH, LAST_UPDATE, GUID FROM FILE_CACHE WHERE PATH = '{path}'";
        using SQLiteDataReader rec = cmd.ExecuteReader();

        if (rec.HasRows == false) return null;

        if (rec.Read() == false) throw new Exception($"レコードが存在しない。:{path}");

        return new FileCacheEntity()
        {
            Path = path,
            Length = rec.GetInt64(0),
            LastUpdate = rec.GetString(1),
            GUID = rec.GetString(2),
        };
    }
    public void Set(string path, string lastUpdate, long length)
    {
        string guid = Guid.NewGuid().ToString("N");

        using SQLiteCommand cmd = new (_conn);
        cmd.CommandText = 
            $"INSERT INTO FILE_CACHE (PATH, LENGTH, LAST_UPDATE, GUID) values ('{path}',{length},'{lastUpdate}','{guid}')";
        cmd.ExecuteNonQuery();
    }
    public void Delete(string path)
    {
        using SQLiteCommand cmd = new (_conn);

        cmd.CommandText =
            $"DELETE FROM FILE_CACHE WHERE PATH = '{path}'";
        cmd.ExecuteNonQuery();
    }
}

ファイル名:FileCacheEntity.cs

using System.Dynamic;

class FileCacheEntity
{
    public string Path { get; set; } = "";
    public string LastUpdate { get; set; } = "";
    public long Length { get; set; } = 0;
    public string GUID { get; set; } = "";
}

ファイル名:FileCacheManager.cs

using System.Data.Common;
using System.Diagnostics;

namespace FileCache03;

class FileCacheManager : IDisposable
{
    string _cacheDir;
    FileCacheDB _db;

    public FileCacheManager(string dbPath = @".\FileCache.db", string cacheDir = @".\.cache")
    {
        _cacheDir = cacheDir;
        if (!Path.Exists(_cacheDir))
            Directory.CreateDirectory(_cacheDir);
        
        _db = new(dbPath);       
    }

    public MemoryStream Get(string path)
    {
        MemoryStream ms = new();

        FileInfo fi = new(path);
        string lastUpdate = fi.LastWriteTime.ToString("yyyy/MM/dd HH:mm:ss");

        Stopwatch sw = new();
        sw.Start();
        FileCacheEntity? fe = _db.Get(path);
        sw.Stop();
        Debug.Print($"_db.Get() {sw.ElapsedMilliseconds}ms");
        if (fe is not null)
        {
            if (fe.LastUpdate != lastUpdate && fe.Length != fi.Length)
            {
                Console.WriteLine($"削除{path}");
                _db.Delete(path);
                return ms;
            }
            else
            {
                //Console.WriteLine($"成功");
                string cachePath = Path.Join(_cacheDir, fe.GUID);
                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);
        string lastUpdate = fi.LastWriteTime.ToString("yyyy/MM/dd HH:mm:ss");

        Stopwatch sw = new();
        sw.Start();
        _db.Set(path, lastUpdate, fi.Length);
        sw.Stop();
        Debug.Print($"_db.Set() {sw.ElapsedMilliseconds}ms");

        sw.Restart();
        FileCacheEntity? fe = _db.Get(path);
        sw.Stop();
        Debug.Print($"_db.Get() {sw.ElapsedMilliseconds}ms");
        if (fe is null) throw new Exception($"追加に失敗。{path}");

        string cachePath = Path.Join(_cacheDir, fe.GUID);
        using FileStream fs = new (cachePath, FileMode.Create, FileAccess.Write);
        ms.Seek(0, SeekOrigin.Begin);
        ms.CopyTo(fs);
    }
    public void Delete(string path) => _db.Delete(path);

    public void Dispose()
    {
        _db.Dispose();
    }
}

ファイル名:Program.cs

using System.Diagnostics;

namespace FileCache03;

class Program
{
    public static void Main()
    {
        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() 27ms
キャッシュに無し サイズ:6955Byte 時間:36ms

2回目

_db.Get() 2ms
キャッシュに有り サイズ:6955Byte 時間:39ms

前回のプログラムで冗長な部分を一部カットしましたが、それを差し引いても今回のプログラムの方が高速に動作しています。
やはりEntityFrameworkの使い方を誤っているようです。

コメント