DB でファイルの内容をフェッチするカスタム IfileProvider を実装しました。用途の 1 つは、css を取得することです。問題は、型エラーが頻繁に発生することです
InvalidOperationException: 前の操作が完了する前に、このコンテキストで 2 番目の操作が開始されました。これは通常、異なるスレッドが DbContext の同じインスタンスを使用していることが原因です。DbContext でスレッドの問題を回避する方法の詳細については、
原点は、私のスタートアップで依存性注入を使用したことだと思います。すべてのタイプの構成を試しましたが、結果はありません。
私のファイル:
起動
public void ConfigureServices(IServiceCollection services)
{
[...]
services.AddTransient<IAssetDBRepository, AssetDBRepository>();
services.AddTransient<IAssetDBService, AssetDBService>();
services.AddSingleton<CacheMemoryHelper>();
[...]
services.AddDbContext<ApplicationDbContext>(options =>
options.UseNpgsql(
applicationDBConnectionStringDecrypted,
b =>
{
b.MigrationsAssembly(migrationsAssembly);
if (!string.IsNullOrWhiteSpace(applicationDBVersion))
b.SetPostgresVersion(new Version(applicationDBVersion));
}),
ServiceLifetime.Transient,
ServiceLifetime.Transient
) ;
}
[...]
public void Configure(IApplicationBuilder app, CacheMemoryHelper memoryCache)
{
[...]
var assetDbService = app.ApplicationServices.GetService<IServiceScopeFactory>().CreateScope().ServiceProvider.GetRequiredService<IAssetDBService>();
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new DatabaseFileProvider(assetDbService, memoryCache),
RequestPath = new PathString("/" + Constants.PATH_ASSETDB)
});
[...]
}
データベースファイルプロバイダー
public class DatabaseFileProvider : IFileProvider
{
private IAssetDBService _assetdbService;
private readonly CacheMemoryHelper _cache;
public DatabaseFileProvider(IAssetDBService assetService,CacheMemoryHelper memoryCache)
{
_assetdbService = assetService;
_cache = memoryCache;
}
public IDirectoryContents GetDirectoryContents(string subpath)
{
throw new NotImplementedException();
}
public IFileInfo GetFileInfo(string idAsset)
{
var result = new DatabaseFileInfo(_assetdbService, _cache,idAsset);
return result.Exists ? result as IFileInfo : new NotFoundFileInfo(idAsset);
}
public IChangeToken Watch(string filter)
{
return new DatabaseChangeToken(_assetdbService, filter);
}
}
データベースファイル情報
public class DatabaseFileInfo : IFileInfo
{
private readonly string _viewPath;
private byte[] _assetContent;
private DateTimeOffset _lastModified;
private bool _exists;
public DatabaseFileInfo(IAssetDBService assetService, CacheMemoryHelper memoryCache,string viewPath)
{
_viewPath = viewPath;
//Async method
//Task<bool> task = Task.Run<bool>(async () => await GetAsset(assetService, memoryCache,_viewPath));
//var result = task.Result;
GetAsset(assetService, memoryCache, _viewPath);
}
public bool Exists => _exists;
public bool IsDirectory => false;
public DateTimeOffset LastModified => _lastModified;
public long Length
{
get
{
using (var stream = new MemoryStream(_assetContent))
{
return stream.Length;
}
}
}
public string Name => Path.GetFileName(_viewPath);
public string PhysicalPath => null;
public Stream CreateReadStream()
{
return new MemoryStream(_assetContent);
}
private async Task<bool> GetAssetAsync(IAssetDBService assetService, CacheMemoryHelper memoryCache, string viewPath)
{
Asset asset = null;
if (memoryCache == null)
{
asset = await assetService.GetAssetAsync(_viewPath);
}
else
{
asset = memoryCache.Get<Asset>(viewPath);
if (asset == null)
{
asset = await assetService.GetAssetAsync(_viewPath);
if (asset != null)
memoryCache.Set<Asset>(viewPath, asset);
}
}
if (asset != null)
{
var result = await assetService.UpdateDateLastRequestedAssetAsync(asset.Id);
_exists = true;
_assetContent = asset.Content;
_lastModified = asset.LastModified;
}
else
{
_exists = false;
}
return true;
}
private bool GetAsset(IAssetDBService assetService, CacheMemoryHelper memoryCache, string viewPath)
{
Asset asset = null;
if (memoryCache == null)
{
asset = assetService.GetAsset(_viewPath);
}
else
{
asset = memoryCache.Get<Asset>(viewPath);
if (asset == null)
{
asset = assetService.GetAsset(_viewPath);
if (asset != null)
memoryCache.Set<Asset>(viewPath, asset);
}
}
if (asset != null)
{
var result = assetService.UpdateDateLastRequestedAsset(asset.Id);
_exists = true;
_assetContent = asset.Content;
_lastModified = asset.LastModified;
}
else
{
_exists = false;
}
return true;
}
}
DatabaseChangeToken
public class DatabaseChangeToken : IChangeToken
{
private readonly IAssetDBService _assetdbService;
private string _viewPath;
public DatabaseChangeToken(IAssetDBService assetService, string viewPath)
{
_assetdbService = assetService;
_viewPath = viewPath;
}
public bool ActiveChangeCallbacks => false;
public bool HasChanged
{
get
{
Asset asset = _assetdbService.GetAsset(_viewPath);
if (asset == null)
return false;
if (asset.LastModified == null || asset.LastRequested == null)
return false;
var result = Convert.ToDateTime(asset.LastModified) > Convert.ToDateTime(asset.LastRequested);
return result;
}
}
public IDisposable RegisterChangeCallback(Action<object> callback, object state) => EmptyDisposable.Instance;
}
internal class EmptyDisposable : IDisposable
{
public static EmptyDisposable Instance { get; } = new EmptyDisposable();
private EmptyDisposable() { }
public void Dispose() { }
}
IappBuilder を DatabaseFileProvider に直接渡すことで問題を解決しました
app.UseStaticFiles(new StaticFileOptions()
{
FileProvider = new DatabaseFileProvider(app, memoryCache),
RequestPath = new PathString("/" + Constants.PATH_ASSETDB)
});
しかし、これは最善の方法ではないと思います。
アイデアはありますか?
ありがとうございました。