繁体   English   中英

如何使用Entity Framework 6访问Middleware中的数据库

[英]How to access the database in Middleware using Entity Framework 6

我写了一些中间件来记录数据库中的请求路径和查询。 我有两个单独的模型。 一个用于记录和一个商业模型。 在尝试了几件事之后我想出了这个:

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,而不是线程安全。

我从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需要是瞬态的,以便让它适用于中间件。 但这种方法会立即引发问题:

System.NotSupportedException:在上一个异步操作完成之前,在此上下文中启动了第二个操作。 使用'await'确保在此上下文上调用另一个方法之前已完成任何异步操作。 任何实例成员都不保证是线程安全的。

我可以向你保证,我确实await各地增加了await 但这并没有解决这个问题。 如果我删除异步部分,那么我收到此错误:

System.InvalidOperationException:已成功提交对数据库的更改,但更新对象上下文时发生错误。 ObjectContext可能处于不一致状态。 内部异常消息:保存或接受更改失败,因为多个“MyLoggingModel.Log”类型的实体具有相同的主键值。 确保显式设置的主键值是唯一的。 确保在数据库和Entity Framework模型中正确配置了数据库生成的主键。 使用实体设计器进行数据库优先/模型优先配置。 使用'HasDatabaseGeneratedOption'fluent API或DatabaseGeneratedAttribute'进行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();
    }
}

在初创公司:

services.AddTransient<RequestLog>();

在middelware中:

public LogMiddleware(RequestDelegate next, RequestLog requestLog)

但这与原始方法没有区别,同样的错误。 唯一可行的(除了非DI解决方案)是:

private async Task WriteRequestToLog(HttpContext httpContext)
{
    var context = (MyLoggingModel)httpContext.RequestServices.GetService(typeof(MyLoggingModel));

但我不明白为什么会有所不同。

考虑在服务后面抽象db上下文,或者为db上下文本身创建一个并由中间件使用。

public interface IMyLoggingModel : IDisposable {
    DbSet<Log> Log { get; set; }
    Task<int> SaveChangesAsync();

    //...other needed members.
}

并从抽象派生出实现。

public class MyLoggingModel : DbContext, IMyLoggingModel {
    public MyLoggingModel(DbConnectionInfo connection)
        : base(connection.ConnectionString) {
    }

    public virtual DbSet<Log> Log { get; set; }

    //...
}

服务配置似乎正确完成。 根据我的上述建议,它需要更新db上下文的注册方式。

services.AddTransient<IMyLoggingModel, MyLoggingModel>();

中间件可以通过构造函数注入抽象,也可以直接注入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();
        }
    }
}

如果所有其他方法都失败,请考虑从请求的服务中获取上下文,并将其用作服务定位器。

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM