5

我正在关注本教程,以便通过 Entity Framework 6 CodeFirst 在 SQL Server 中使用行级安全性。教程代码示例展示了如何使用 IDbConnectionInterceptor 并在session_context. HttpContext.Current.User.Identity.GetUserId()要检索用户 ID,它使用与 Asp.Net 身份和 System.Web 命名空间相结合的静态访问器方法。

在我的多租户 Web 应用程序中,我希望将tenantId注入到DbConnectionInterceptor使用 Unity 中(不创建与 的硬耦合HttpContext)并在session_context. 我发现DbConnectionInterceptor需要在全局范围内注册(例如在应用程序启动时),因此您不能让 UnityDbConnectionInterceptor为每个请求创建实例。

我的解决方案中还有 2 个 DbContext,代表 2 个不同的数据库(租户数据库和系统数据库),我只想应用session_context到租户数据库。

看来我剩下的唯一选择是DbContext通过 Unity将tenantId 注入到DbContext实例中Opened(),并在DbConnectionInterceptor. 为此,我想到了interceptionContext在方法中使用参数Opened()interceptionContext有一个DbContexts(复数)属性。没有这方面的文档,所以我认为这样的事情会起作用:

public void Opened(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
{
    var firstDbContext = interceptionContext.DbContexts.FirstOrDefault(d => d is TenantDataContext);
    if (firstDbContext != null)
    {
        var dataContext = firstDbContext as TenantDataContext;
        var tenantId = dataContext.TenantId;

        DbCommand cmd = connection.CreateCommand();
        cmd.CommandText = $"EXEC sp_set_session_context @key=N'TenantId', @value={tenantId};";
        cmd.ExecuteNonQuery();
    }
}

我的代码检查 DbContexts 集合是否包含TenantDataContext作为第一个元素并执行sp_set_session_context. 但我担心的是两个 DbContext 是否有机会同时存在?如果是这种情况,与我的其他数据库的连接也会设置session_context我不需要的。我想知道为什么 Microsoft 将其作为集合属性而不是单个DbContext属性提供。这个属性让你想知道多个 DbContext 是否可以使用同一个连接。

有没有人实现了我想要的?关于这个 interceptionContext 的任何解释对我也有帮助。

4

2 回答 2

3

如果您像这样使用 EF,则可以使用 DbContext 的 Connection_StateChaned 事件。

 static void Main(string[] args)
    {               
        using (var db = new AdventureWorks2016CTP3Entities())
        {
            db.Database.Connection.StateChange += Connection_StateChange;
            db.Database.Log = (log) => System.Diagnostics.Debug.WriteLine(log);

            var purchase = db.SalesOrderHeader.Select(i => i.SalesPersonID);

            foreach (var m in purchase)
            {
                Console.WriteLine(m);
            }
        }

    }

    private static void Connection_StateChange(object sender, System.Data.StateChangeEventArgs e)
    {
        if(e.CurrentState == System.Data.ConnectionState.Open)
        {
            var cmd = (sender as System.Data.SqlClient.SqlConnection).CreateCommand();
            cmd.CommandType = System.Data.CommandType.Text;
            cmd.CommandText = "exec sp_set_session_context 'UserId', N'290'";

            cmd.ExecuteNonQuery();
        }
    }
于 2017-12-29T16:02:52.387 回答
2

我意识到这是一个较老的问题,但我想我会为那些寻找解决方案的人发布我们的解决方案。我们正在使用拦截器将 SQLServer session_context 语句注入到通过 EF 运行的命令/连接中。

在我们的例子中,我们必须为 DbCommand 和 DbConnection 创建拦截器来处理 EF Linq 查询和通过命令运行的原始 SQL 查询。这些 Interceptor 类分别实现 IDbCommandInterceptor 和 IDbConnectionInterceptor。

对于 DbCommandInterceptor,我们使用 SqlCommand.CommandText 将我们的 EXEC sp_set_session_context 原始 SQL 添加到通过拦截器的每个命令中。

public class SessionContextDbCommandInterceptor : IDbCommandInterceptor

对于 DbConnectionInterceptor,我们实现 Opened 方法并对运行 sp_set_session_context SQL 的连接执行 SqlCommand。

public class SessionContextDbConnectionInterceptor : IDbConnectionInterceptor
{
    public void Opened(DbConnection connection, DbConnectionInterceptionContext interceptionContext)
    {...}

然后我们创建了一个在构造函数中添加拦截器的 DbConfiguration 类:

public class SessionContextConfiguration : DbConfiguration
{
    public SessionContextConfiguration()
    {
        AddInterceptor(new SessionContextDbConnectionInterceptor());
        AddInterceptor(new SessionContextDbCommandInterceptor());
    }
}

然后通过 DbConfigurationType 属性将此 DbConfiguration 类添加到我们的 DbContext 类以及我们的 web.config 中:

[DbConfigurationType(typeof(SessionContextConfiguration))]
public class MyContext : DbContext

<entityFramework codeConfigurationType="MyAssembly.SessionContextConfiguration, MyAssembly">

我们像往常一样使用 Autofac 注入我们的 DbContexts,并且由于 Configuration 类,拦截器会自动添加到 DbContext 实例中。

于 2018-09-19T11:49:29.853 回答