如何在 Entity Framework 连接中设置 CONTEXT_INFO

5
我正在一个基于实体框架的应用程序中隔离商店数据到共享数据库。我想使用SQL Server 2016的行级安全性,但是我更喜欢让我的数据库连接都使用单个用户。所以我想将SQL Server的CONTEXT_INFO设置为所有实体框架查询的商店号码。然后行级安全性就可以确定行是否属于CONTEXT_INFO所设定的商店号码。
我想完成这个任务唯一的想法是更新我的DbContext构造函数来创建一个连接,执行SET CONTEXT_INFO语句,然后将连接传递给基本构造函数。我认为我可以添加一个构造函数重载在我的DbContext的部分类中来完成这个任务或修改t4模板以生成原始构造函数。
EF提供了更好的方法吗?我正在使用DB first。

3
SESSION_CONTEXT是更优秀的,因为它可以保存多个值(避免与其他应用程序冲突),存储变体(比原始字节的CONTEXT_INFO进行更安全的转换),并且允许在第一次调用后将值设置为只读(增加安全性)。问题仍然存在(在连接打开后执行sp_set_session_context)。 - Jeroen Mostert
很酷,谢谢你指出来! - xr280xr
@JeroenMostert 为什么 context_info 会与其他应用程序冲突?我认为如果在每个请求中设置 context_info,就不会有问题(即使在 MARS 中也是如此):在 session_context 文档中,也提到了:“SESSION_CONTEXT 的 MARS 行为类似于 CONTEXT_INFO”。 - Mahmoud Moravej
2
@MahmoudMoravej:我所说的“应用程序”是指“目的”,而不是“进程”。SESSION_CONTEXT允许通过唯一键设置值,CONTEXT_INFO是一个二进制块,只能作为整体设置。您只能将CONTEXT_INFO用于一个目的,并且所有代码必须同意;如果新代码/触发器设置了CONTEXT_INFO,则会被覆盖。如果您的代码是CONTEXT_INFO的唯一用户,则没有问题,但是使用SESSION_CONTEXT可以避免任何冲突(因为您始终可以使键唯一)。 - Jeroen Mostert
2个回答

6

看起来实现了IDbConnectionInterceptor的人能够在每次打开连接时执行一些代码。这使我能够执行存储过程来设置CONTEXT_INFOSESSION_CONTEXT变量。我的拦截器如下:

public class MAContextInterceptor : IDbConnectionInterceptor
{
    private static ITenantIDProvider _tenantIDProvider;
    public MAContextInterceptor(ITenantIDProvider tenantIDProvider)
    {
        _tenantIDProvider = tenantIDProvider;
    }

    public void Opened(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
    {
        // Set SESSION_CONTEXT to current tenant ID whenever EF opens a connection
        try
        {
            Guid tenantID = _tenantIDProvider == null ? Guid.Empty : _tenantIDProvider.GetTenantID();

            if (tenantID != Guid.Empty)
            {
                DbCommand cmd = connection.CreateCommand();
                cmd.CommandText = "ma_setcontextinfo";
                cmd.CommandType = System.Data.CommandType.StoredProcedure;
                DbParameter param = cmd.CreateParameter();
                param.ParameterName = "@tenantID";
                param.Value = tenantID;
                cmd.Parameters.Add(param);
                cmd.ExecuteNonQuery();
            }
        }
        catch (System.NullReferenceException)
        {
            //log error
        }
    }
}

由于这是在数据层类库中,我添加了一个ITenantIDProvider,允许上层提供实现以提供租户ID。当应用程序启动时,这最终在我的Global.asax中设置。它还调用System.Data.Entity.Infrastructure.Interception.DbInterception.Add(new MAContextInterceptor(tenantIDProvider));来注册拦截器。
看起来它将适用于所有的DbContexts,我可以看到这可能会有问题,但对于我的目的,这样做很有效。这个页面几乎把所有内容都解释清楚了。我不知道为什么在我提问之前没有出现在任何搜索结果中!

3

您不仅需要运行sp_set_session_context,还需要强制连接保持打开状态,以便在DbContext生命周期内使用。如果DbContext没有使用打开的连接构造,则需要运行:

db.Database.Connection.Open();

以防止EF使用连接池。

不,EF没有更好的方法来处理此问题。

除了您提到的选项外,您还可以具有一个中央DbContext工厂方法。当您需要更改连接字符串或连接到联合(分片)后端时,通常会执行此操作。例如:

public static MyDbContext Connect(string username, bool readOnly) { . . . }


感谢您的输入。这让我朝着正确的方向前进了。我可以在创建时传递一个打开的连接到DbContext,但我并不想做任何如此激烈的事情或阻止连接池。我真正想做的只是在每次打开连接时运行一个命令。我认为我找到了一种方法来做到这一点(请看我的答案)。 - xr280xr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接