实体框架 - 使用IDbConnectionInterceptor设置session_context

时间:2016-11-30 11:06:54

标签: c# entity-framework row-level-security

我跟随this tutorial,以便通过Entity Framework 6 CodeFirst在SQL Server中使用行级安全性。教程代码示例演示如何使用IDbConnectionInterceptor并在session_context中设置当前用户ID。要检索用户ID,它使用静态访问器方法HttpContext.Current.User.Identity.GetUserId(),它与Asp.Net标识和System.Web命名空间相结合。

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

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

似乎剩下的唯一选择是通过Unity将tenantId注入DbContext isntance并访问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。但我担心的是,DbContexts是否有机会在同一时间出现?如果是这种情况,与我的其他数据库的连接也会设置我不需要的session_context。我想知道为什么Microsoft将此作为集合属性而不是单个DbContext属性提供。此属性使您想知道多个DbContexts是否可以使用相同的连接。

有没有人达到了我的目标?对此interceptionContext的任何解释对我都有帮助。

2 个答案:

答案 0 :(得分:1)

如果您正在使用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();
        }
    }

答案 1 :(得分:1)

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

在我们的案例中,我们必须为DbCommand和DbConnection创建拦截器,以处理EF Linq查询和通过Command运行的原始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属性以及我们的web.config将这个DbConfiguration类添加到我们的DbContext类中:

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

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

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