[英]How to access the database in Middleware using Entity Framework 6
I wrote some middleware to log the request path and query in the database. 我写了一些中间件来记录数据库中的请求路径和查询。 I have two seperate models. 我有两个单独的模型。 One for logging and one business model. 一个用于记录和一个商业模型。 After trying a few things I came up with this: 在尝试了几件事之后我想出了这个:
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>();
}
}
The Model: 该模型:
public class MyLoggingModel : DbContext
{
public MyLoggingModel(DbConnectionInfo connection)
: base(connection.ConnectionString)
{
}
public virtual DbSet<Log> Log { get; set; }
}
As you can see nothing special. 你可以看到没什么特别的。 It works, but not quite the way I would have wanted it to. 它有效,但不是我希望它的方式。 The problem lies probably in EF6, not being threadsafe. 问题可能在于EF6,而不是线程安全。
I started with this in Startup: 我从Startup开始:
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
needs to be transient in order to let it work for the middleware. MyLoggingModel
需要是瞬态的,以便让它适用于中间件。 But this method immediately causes problems: 但这种方法会立即引发问题:
System.NotSupportedException: A second operation started on this context before a previous asynchronous operation completed. System.NotSupportedException:在上一个异步操作完成之前,在此上下文中启动了第二个操作。 Use 'await' to ensure that any asynchronous operations have completed before calling another method on this context. 使用'await'确保在此上下文上调用另一个方法之前已完成任何异步操作。 Any instance members are not guaranteed to be thread safe. 任何实例成员都不保证是线程安全的。
I can assure you that I did add await
everywhere. 我可以向你保证,我确实await
各地增加了await
。 But that did not resolve this. 但这并没有解决这个问题。 If I remove the async part then I get this error: 如果我删除异步部分,那么我收到此错误:
System.InvalidOperationException: The changes to the database were committed successfully, but an error occurred while updating the object context. System.InvalidOperationException:已成功提交对数据库的更改,但更新对象上下文时发生错误。 The ObjectContext might be in an inconsistent state. ObjectContext可能处于不一致状态。 Inner exception message: Saving or accepting changes failed because more than one entity of type 'MyLoggingModel.Log' have the same primary key value. 内部异常消息:保存或接受更改失败,因为多个“MyLoggingModel.Log”类型的实体具有相同的主键值。 Ensure that explicitly set primary key values are unique. 确保显式设置的主键值是唯一的。 Ensure that database-generated primary keys are configured correctly in the database and in the Entity Framework model. 确保在数据库和Entity Framework模型中正确配置了数据库生成的主键。 Use the Entity Designer for Database First/Model First configuration. 使用实体设计器进行数据库优先/模型优先配置。 Use the 'HasDatabaseGeneratedOption" fluent API or DatabaseGeneratedAttribute' for Code First configuration. 使用'HasDatabaseGeneratedOption'fluent API或DatabaseGeneratedAttribute'进行Code First配置。
That's why I came up with above code. 这就是我提出上述代码的原因。 I would have wanted to use dependency injection for the model. 我本来想对模型使用依赖注入。 But I cannot make this to work. 但我不能让这个工作。 I also cannot find examples on accessing the database from middleware. 我也找不到从中间件访问数据库的示例。 So I get the feeling that I may be doing this in the wrong place. 所以我觉得我可能在错误的地方做这件事。
My question: is there a way to make this work using dependency injection or am I not supposed to access the database in the middleware? 我的问题:有没有办法使用依赖注入来完成这项工作,或者我不应该访问中间件中的数据库? And I wonder, would using EFCore make a difference? 我想知道,使用EFCore会有所作为吗?
-- update -- - 更新 -
I tried moving the code to a seperate class and inject that: 我尝试将代码移动到一个单独的类并注入:
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();
}
}
And in Startup: 在初创公司:
services.AddTransient<RequestLog>();
And in the middelware: 在middelware中:
public LogMiddleware(RequestDelegate next, RequestLog requestLog)
But this doesn't make a difference with the original approach, same errors. 但这与原始方法没有区别,同样的错误。 The only thing that seems to work (besides the non-DI solution) is: 唯一可行的(除了非DI解决方案)是:
private async Task WriteRequestToLog(HttpContext httpContext)
{
var context = (MyLoggingModel)httpContext.RequestServices.GetService(typeof(MyLoggingModel));
But I do not understand why this would be different. 但我不明白为什么会有所不同。
Consider abstracting the db context behind a service or create one for the db context itself and used by the middleware. 考虑在服务后面抽象db上下文,或者为db上下文本身创建一个并由中间件使用。
public interface IMyLoggingModel : IDisposable {
DbSet<Log> Log { get; set; }
Task<int> SaveChangesAsync();
//...other needed members.
}
and have the implementation derived from the abstraction. 并从抽象派生出实现。
public class MyLoggingModel : DbContext, IMyLoggingModel {
public MyLoggingModel(DbConnectionInfo connection)
: base(connection.ConnectionString) {
}
public virtual DbSet<Log> Log { get; set; }
//...
}
The service configurations appear to be done correctly. 服务配置似乎正确完成。 With my above suggestion it would need to update how the db context is registered. 根据我的上述建议,它需要更新db上下文的注册方式。
services.AddTransient<IMyLoggingModel, MyLoggingModel>();
the middleware can either have the abstraction injected via constructor or directly injected into the Invoke
method. 中间件可以通过构造函数注入抽象,也可以直接注入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();
}
}
}
If all else fails consider getting the context from the request's services, using it as a service locator. 如果所有其他方法都失败,请考虑从请求的服务中获取上下文,并将其用作服务定位器。
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();
}
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.