我编写了一些中间件来记录数据库中的请求路径和查询。我有两种不同的型号。一个用于日志记录,一个用于业务模型。在尝试了一些事情后,我想到了这个:
public class LogMiddleware
{
private readonly RequestDelegate _next;
private readonly DbConnectionInfo _dbConnectionInfo;
public LogMiddleware(RequestDelegate next, DbConnectionInfo dbConnectionInfo)
{
_next = next;
_dbConnectionInfo = dbConnectionInfo;
}
public async Task Invoke(HttpContext httpContext)
{
httpContext.Response.OnStarting( async () =>
{
await WriteRequestToLog(httpContext);
});
await _next.Invoke(httpContext);
}
private async Task WriteRequestToLog(HttpContext httpContext)
{
using (var context = new MyLoggingModel(_dbConnectionInfo))
{
context.Log.Add(new Log
{
Path = request.Path,
Query = request.QueryString.Value
});
await context.SaveChangesAsync();
}
}
}
public static class LogExtensions
{
public static IApplicationBuilder UseLog(this IApplicationBuilder builder)
{
return builder.UseMiddleware<LogMiddleware>();
}
}
型号:
public class MyLoggingModel : DbContext
{
public MyLoggingModel(DbConnectionInfo connection)
: base(connection.ConnectionString)
{
}
public virtual DbSet<Log> Log { get; set; }
}
因为你看不到什么特别的东西。这是可行的,但不是我想要的那样。问题可能出在EF6中,它不是线程安全的。
我在启动时就这样开始了:
public class Startup
{
private IConfigurationRoot _configuration { get; }
public Startup(IHostingEnvironment env)
{
var builder = new ConfigurationBuilder()
.SetBasePath(env.ContentRootPath)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
_configuration = builder.Build();
}
public void ConfigureServices(IServiceCollection services)
{
services.AddOptions();
services.Configure<ApplicationSettings>(_configuration.GetSection("ApplicationSettings"));
services.AddSingleton<ApplicationSettings>();
services.AddSingleton(provider => new DbConnectionInfo { ConnectionString = provider.GetRequiredService<ApplicationSettings>().ConnectionString });
services.AddTransient<MyLoggingModel>();
services.AddScoped<MyModel>();
}
public void Configure(IApplicationBuilder app)
{
app.UseLog();
app.UseStaticFiles();
app.UseMvc();
}
}
MyLoggingModel
需要是暂时的,以便让它为中间件工作。但这种方法会立即引发问题:
系统NotSupportedException:对此启动了第二个操作
上一个异步操作完成之前的上下文。使用
“等待”以确保任何异步操作都已完成
在此上下文上调用其他方法之前。任何实例成员
不保证线程安全。
我可以向你保证,我确实补充了
await
处处但这并没有解决这个问题。如果删除异步部分,则会出现以下错误:
系统InvalidOperationException:对数据库的更改为
已成功提交,但更新时出错
对象上下文。ObjectContext可能处于不一致的状态。
内部异常消息:保存或接受更改失败,因为
“MyLoggingModel”类型的多个实体。“日志”具有相同的
主键值。确保显式设置的主键值为
唯一的确保配置了数据库生成的主键
在数据库和实体框架模型中正确。使用
数据库优先/模型优先配置的实体设计器。使用
'HasDatabaseGeneratedOption“fluent API或DatabaseGeneratedAttribute'
对于代码优先配置。
这就是我提出上述代码的原因。我本想对模型使用依赖注入。但我不能让它起作用。我也找不到从中间件访问数据库的示例。所以我觉得我可能在错误的地方做这件事。
我的问题是:有没有一种方法可以使用依赖注入来实现这一点,或者我不应该访问中间件中的数据库?我想知道,使用EFCore会有什么不同吗?
--更新--
我尝试将代码移动到一个单独的类并注入:
public class RequestLog
{
private readonly MyLoggingModel _context;
public RequestLog(MyLoggingModel context)
{
_context = context;
}
public async Task WriteRequestToLog(HttpContext httpContext)
{
_context.EventRequest.Add(new EventRequest
{
Path = request.Path,
Query = request.QueryString.Value
});
await _context.SaveChangesAsync();
}
}
启动时:
services.AddTransient<RequestLog>();
在中间件中:
public LogMiddleware(RequestDelegate next, RequestLog requestLog)
但这与最初的方法没有区别,相同的错误。除了非DI解决方案之外,唯一有效的方法是:
private async Task WriteRequestToLog(HttpContext httpContext)
{
var context = (MyLoggingModel)httpContext.RequestServices.GetService(typeof(MyLoggingModel));
但我不明白为什么会有不同。