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