我实现了一个自定义 IfileProvider,它将获取数据库中文件的内容。用途之一是去获取 css。问题是我经常遇到类型错误
InvalidOperationException:在前一个操作完成之前在此上下文上启动了第二个操作。这通常是由使用相同 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;
}
}
数据库变更令牌
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)
});
但我认为这不是最好的方法。
你有想法吗?
谢谢你。