MVC:EF6连接池和SQL Server CONTEXT_INFO

本文关键字:Server CONTEXT INFO SQL EF6 连接 MVC | 更新日期: 2023-09-27 17:59:06

在ASP.NET MVC应用程序中,我试图使用SQL Server的CONTEXT_INFO来传递当前登录的用户,因此我的审核触发器不仅记录web服务器登录,还记录网站的登录。

不过,我很难确定当前用户将始终被输入数据库服务器上下文。

在后端,我已经设置好了一切,一个设置上下文的存储过程,一个拉取它的函数和DML触发器来记录,没问题。

应用端的参与度要高一些。我订阅了Database.Connection.StateChange事件,这样我就可以捕获每个新打开的连接并相应地设置此上下文。

此外,为了能够在数据层中检索MVC站点的当前登录ID(它无法访问web项目),我向EF构造函数提供了一个委托,该构造函数将返回用户ID。这也意味着我设置的任何其他外围项目也需要这种依赖性,并且在web开发过程中,它使我无法了解大多数实现细节:

public class CoreContext : DbContext
{
    Func<int> _uidObtainer;
    public CoreContext(Func<int> uidObtainer) : base(nameof(CoreContext)) { construct(uidObtainer); }
    public CoreContext(Func<int> uidObtainer, string connection) : base(connection) { construct(uidObtainer); }
    void construct(Func<int> uidObtainer) {
        // disallow updates of the db from our models
        Database.SetInitializer<CoreContext>(null);
        // catch the connection change so we can update for our userID
        _uidObtainer = uidObtainer;
        Database.Connection.StateChange += connectionStateChanged;
    }
    private void connectionStateChanged(object sender, System.Data.StateChangeEventArgs e) {
        // set our context info for logging
        if (e.OriginalState == System.Data.ConnectionState.Open || 
            e.CurrentState != System.Data.ConnectionState.Open) {
            return;
        }
        int uid = _uidObtainer();
        var conn = ((System.Data.Entity.Core.EntityClient.EntityConnection)sender).StoreConnection;
        var cmd = conn.CreateCommand();
        cmd.CommandText = "audit.SetContext";
        cmd.CommandType = System.Data.CommandType.StoredProcedure;
        cmd.Parameters.Add(new System.Data.SqlClient.SqlParameter("@DomainUserID", uid));
        cmd.ExecuteNonQuery();
    }
    // etc etc...

在我的MVC项目中,我将有如下代码:

context = new Data.CoreContext(() => AppService.UserID());

(使用一个易于访问的方法作为委托传递,然后从HttpContext.Current.User中读取)

这一切都很顺利,除了一个未知的:

我知道EF上下文实例可能跨越多个登录用户,因为它是IIS应用程序池的一部分,而不是每个HttpContext

我对连接池以及如何打开/重新打开连接的了解还不够,因为我知道每次运行StateChange处理程序时,我实际上都会从委托中检索新的UserID。

换句话说:单个连接是否可能在两个独立的HttpContext实例的跨度上打开并使用?我相信是的,因为没有什么可以强制执行的(至少我不知道)。

我可以做些什么来确保每个连接都获得当前的HttpContext

(可能需要注意的是:EF本身之外没有UoW/Repository模式,数据上下文通常每个控制器实例化一次)

MVC:EF6连接池和SQL Server CONTEXT_INFO

我看到:每个控制器一个上下文通常是不正确的。相反,我应该为每个请求使用一个上下文,这(除了其他优点之外)可以确保我的场景也能正确运行。

我找到了这个答案,它解释了背后的原因:每个web请求一个DbContext。。。为什么?

我找到了这个答案,它非常简洁地解释了如何通过BeginRequestEndRequest实现:在ASP.NET MVC(没有IOC容器)中,每个请求一个DbContext

(第二个答案的代码粘贴在下面以防止链接腐烂)

protected virtual void Application_BeginRequest()
{
    HttpContext.Current.Items["_EntityContext"] = new EntityContext();
}
protected virtual void Application_EndRequest()
{
    var entityContext = HttpContext.Current.Items["_EntityContext"] as EntityContext;
    if (entityContext != null)
        entityContext.Dispose();
}

在EntityContext类中。。。

public class EntityContext
{
    public static EntityContext Current
    {
        get { return HttpContext.Current.Items["_EntityContext"] as EntityContext; }
    }
}