5 c# dependency-injection middleware entity-framework-6 asp.net-core-2.0
我编写了一些中间件来记录请求路径并在数据库中查询。我有两个独立的模型。一种用于日志记录,一种用于商业模式。在尝试了一些事情之后,我想出了这个:
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>();
}
}
Run Code Online (Sandbox Code Playgroud)
该模型:
public class MyLoggingModel : DbContext
{
public MyLoggingModel(DbConnectionInfo connection)
: base(connection.ConnectionString)
{
}
public virtual DbSet<Log> Log { get; set; }
}
Run Code Online (Sandbox Code Playgroud)
正如你所看到的,没有什么特别的。它有效,但不完全是我想要的方式。问题可能出在 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();
}
}
Run Code Online (Sandbox Code Playgroud)
MyLoggingModel需要是暂时的才能让它为中间件工作。但这种方法立刻就会产生问题:
System.NotSupportedException:在上一个异步操作完成之前,在此上下文上启动了第二个操作。使用“await”确保在此上下文上调用另一个方法之前所有异步操作已完成。不保证任何实例成员都是线程安全的。
我可以向你保证我确实await到处都添加了。但这并没有解决这个问题。如果我删除异步部分,则会收到此错误:
System.InvalidOperationException:对数据库的更改已成功提交,但更新对象上下文时发生错误。ObjectContext 可能处于不一致的状态。内部异常消息:保存或接受更改失败,因为“MyLoggingModel.Log”类型的多个实体具有相同的主键值。确保显式设置的主键值是唯一的。确保在数据库和实体框架模型中正确配置数据库生成的主键。使用实体设计器进行数据库优先/模型优先配置。使用“HasDatabaseGenerateOption”流畅 API 或 DatabaseGenerateAttribute”进行 Code First 配置。
这就是我想出上面的代码的原因。我本来想对模型使用依赖注入。但我无法使其发挥作用。我也找不到从中间件访问数据库的示例。所以我觉得我可能在错误的地方做这件事。
我的问题:有没有办法使用依赖注入来完成这项工作,或者我不应该访问中间件中的数据库?我想知道,使用 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();
}
}
Run Code Online (Sandbox Code Playgroud)
并在启动中:
services.AddTransient<RequestLog>();
Run Code Online (Sandbox Code Playgroud)
在中间件中:
public LogMiddleware(RequestDelegate next, RequestLog requestLog)
Run Code Online (Sandbox Code Playgroud)
但这与原来的方法没有什么区别,同样的错误。唯一可行的方法(除了非 DI 解决方案)是:
private async Task WriteRequestToLog(HttpContext httpContext)
{
var context = (MyLoggingModel)httpContext.RequestServices.GetService(typeof(MyLoggingModel));
Run Code Online (Sandbox Code Playgroud)
但我不明白为什么会有所不同。
考虑抽象服务背后的数据库上下文,或者为数据库上下文本身创建一个数据库上下文并由中间件使用。
public interface IMyLoggingModel : IDisposable {
DbSet<Log> Log { get; set; }
Task<int> SaveChangesAsync();
//...other needed members.
}
Run Code Online (Sandbox Code Playgroud)
并从抽象中派生出实现。
public class MyLoggingModel : DbContext, IMyLoggingModel {
public MyLoggingModel(DbConnectionInfo connection)
: base(connection.ConnectionString) {
}
public virtual DbSet<Log> Log { get; set; }
//...
}
Run Code Online (Sandbox Code Playgroud)
服务配置似乎已正确完成。根据我的上述建议,需要更新数据库上下文的注册方式。
services.AddTransient<IMyLoggingModel, MyLoggingModel>();
Run Code Online (Sandbox Code Playgroud)
中间件可以通过构造函数注入抽象,也可以直接注入到Invoke方法中。
public class LogMiddleware {
private readonly RequestDelegate _next;
public LogMiddleware(RequestDelegate next) {
_next = next;
}
public async Task Invoke(HttpContext context, IMyLoggingModel db) {
await WriteRequestToLog(context.Request, db);
await _next.Invoke(context);
}
private async Task WriteRequestToLog(HttpRequest request, IMyLoggingModel db) {
using (db) {
db.Log.Add(new Log {
Path = request.Path,
Query = request.QueryString.Value
});
await db.SaveChangesAsync();
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果所有其他方法都失败,请考虑从请求的服务获取上下文,并将其用作服务定位器。
public class LogMiddleware {
private readonly RequestDelegate _next;
public LogMiddleware(RequestDelegate next) {
_next = next;
}
public async Task Invoke(HttpContext context) {
await WriteRequestToLog(context);
await _next.Invoke(context);
}
private async Task WriteRequestToLog(HttpContext context) {
var request = context.Request;
using (var db = context.RequestServices.GetService<IMyLoggingModel>()) {
db.Log.Add(new Log {
Path = request.Path,
Query = request.QueryString.Value
});
await db.SaveChangesAsync();
}
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
9260 次 |
| 最近记录: |