简体   繁体   English

实体框架 - 使用IDbConnectionInterceptor设置session_context

[英]Entity Framework - Setting session_context using IDbConnectionInterceptor

I'm following this tutorial in order to use Row Level security in SQL Server via Entity Framework 6 CodeFirst. 我正在学习本教程 ,以便通过Entity Framework 6 CodeFirst在SQL Server中使用行级安全性。 The tutorial code sample shows how to use IDbConnectionInterceptor and set the current user id in session_context . 教程代码示例演示如何使用IDbConnectionInterceptor并在session_context设置当前用户ID。 To retrieve the user id, it uses static accessor method HttpContext.Current.User.Identity.GetUserId() which is coupled with Asp.Net identity and System.Web namespace. 要检索用户ID,它使用静态访问器方法HttpContext.Current.User.Identity.GetUserId() ,它与Asp.Net标识和System.Web命名空间相结合。

In my multi-tenant web app, I wanted to have the tenantId injected into the DbConnectionInterceptor using Unity (without creating hard-coupling with HttpContext ) and set the tenantId in the session_context . 在我的多租户Web应用程序中,我希望使用Unity将tenantId注入到DbConnectionInterceptor中(不创建与HttpContext硬耦合)并在session_context设置tenantId。 I found out that the DbConnectionInterceptor needs to be registered globally (eg. at application startup) and therefore you cannot have Unity create DbConnectionInterceptor instance per request. 我发现DbConnectionInterceptor需要全局注册(例如,在应用程序启动时),因此每个请求都不能让Unity创建DbConnectionInterceptor实例。

I also have 2 DbContexts in my solution representing 2 different databases (Tenant database and a system database) and I only want to apply session_context to the Tenant database only. 我的解决方案中还有2个DbContexts代表2个不同的数据库(租户数据库和系统数据库),我只想将session_context应用于租户数据库。

It seems that the only option remaining to me is have the tenantId injected into the DbContext isntance via Unity and access the DbContext instance inside the Opened() method of the DbConnectionInterceptor . 似乎剩下的唯一选择是让tenantId通过Unity注入DbContext isntance并访问DbConnectionInterceptorOpened()方法内的DbContext实例。 For this purpose I thought of using the interceptionContext parameter in the Opened() method. 为此,我想到在Opened()方法中使用interceptionContext参数。 interceptionContext has a DbContexts (plural) property. interceptionContext具有DbContexts (复数)属性。 There's no documentation on this so I assumed something like this would work: 没有关于此的文档,所以我假设这样的东西会起作用:

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();
    }
}

My code checks whether the DbContexts collection contains the TenantDataContext as the first element and executes the sp_set_session_context . 我的代码检查DbContexts集合是否包含TenantDataContext作为第一个元素并执行sp_set_session_context But what I'm worried about is whether there's any chance for both DbContexts to be there at the same time? 但我担心的是,DbContexts是否有机会在同一时间出现? If that was the case, the connection to my other database would also set the session_context which I don't need. 如果是这种情况,与我的其他数据库的连接也会设置我不需要的session_context I'm wondering why Microsoft has provided this as a collection property rather than a single DbContext property. 我想知道为什么Microsoft将此作为集合属性而不是单个DbContext属性提供。 This property makes you wonder whether the same connection can be used by multiple DbContexts. 此属性使您想知道多个DbContexts是否可以使用相同的连接。

Is there anyone who has achieved what I want? 有没有人达到我想要的目标? Any explanation on this interceptionContext would also be helpful for me. 对此interceptionContext的任何解释对我也有帮助。

You can use the Connection_StateChaned event of your DbContext if you are using EF like so. 如果您正在使用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();
        }
    }

I realize this is an older question, but figured I would post our solution for those looking for one. 我意识到这是一个较老的问题,但我想我会为那些寻找一个问题的人发布我们的解决方案。 We are using interceptors to Inject a SQLServer session_context statement into the commands/connections running through EF. 我们使用拦截器将SQLServer session_context语句注入到通过EF运行的命令/连接中。

In our case, we had to create Interceptors for DbCommand and DbConnection to handle both EF Linq queries and raw SQL queries that run through Commands. 在我们的例子中,我们必须为DbCommand和DbConnection创建拦截器来处理EF Linq查询和通过命令运行的原始SQL查询。 These Interceptor classes implement IDbCommandInterceptor and IDbConnectionInterceptor respectively. 这些Interceptor类分别实现IDbCommandInterceptor和IDbConnectionInterceptor。

For DbCommandInterceptor, we use the SqlCommand.CommandText to prepend our EXEC sp_set_session_context raw SQL to each command coming through the interceptor. 对于DbCommandInterceptor,我们使用SqlCommand.CommandText将EXEC sp_set_session_context原始SQL添加到通过拦截器的每个命令。

public class SessionContextDbCommandInterceptor : IDbCommandInterceptor

For DbConnectionInterceptor, we implement the Opened method and execute a SqlCommand against the connection that runs our sp_set_session_context SQL. 对于DbConnectionInterceptor,我们实现Opened方法并对运行sp_set_session_context SQL的连接执行SqlCommand。

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

We then created a DbConfiguration class that adds the interceptors within the constructor: 然后我们创建了一个DbConfiguration类,在构造函数中添加了拦截器:

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

Then add this DbConfiguration class to our DbContext class via the DbConfigurationType Attribute as well as to our web.config: 然后通过DbConfigurationType属性以及我们的web.config将此DbConfiguration类添加到我们的DbContext类:

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

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

We inject our DbContexts using Autofac as we normally would and the interceptors are automatically added to the DbContext instances because of the Configuration class. 我们像往常一样使用Autofac注入DbContexts,并且由于Configuration类,拦截器会自动添加到DbContext实例中。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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