【问题标题】:EXECUTE AS when using a DbContext with Linq将 DbContext 与 Linq 一起使用时执行 AS
【发布时间】:2012-09-03 00:31:30
【问题描述】:

我正在设计一个多租户数据库,其中每个租户都有一个相应的数据库用户。用户被分配了对与租户关联的架构的访问权限以及对 dbo 架构中对象的特定权限。

一旦我确定了租户,我想通过执行如下 SQL 语句来切换到适当的用户上下文:

EXECUTE AS User = 'Tenant1' WITH NO REVERT

当我使用 DbContext 的数据库属性的 ​​ExecuteSqlCommand 执行此命令时,一切似乎都正常工作。当我稍后使用 Linq 对模型进行更改并调用该方法时

myDbContext.SaveChanges();

我得到了一系列异常:

在提供程序上启动事务时发生错误 联系。有关详细信息,请参阅内部异常。

内部例外:

当前命令发生严重错误。结果,如果有的话, 应该被丢弃。

是否可以通过这种方式更改用户的执行上下文,如果可以,最好的方法是什么?

【问题讨论】:

    标签: c# linq entity-framework multi-tenant dbcontext


    【解决方案1】:

    您是否尝试过 myDbContext.Submit() 或 SubmitAll ?记不太清了,应该是这样的吧……

    【讨论】:

    • DbContext类中没有以Submit开头的方法。
    • SubmitChanges 来自LinqToSql。它几乎与 LinqToEntities 中的 SaveChanges 相同
    【解决方案2】:

    我需要的大部分答案都在这里找到:

    http://romiller.com/2011/05/23/ef-4-1-multi-tenant-with-code-first/

    public partial class MyDBContext
    {
      public MyDBContext() : base() { }
      private MyDBContext(DbConnection connection, DbCompiledModel model) : base(connection, model, contextOwnsConnection: false) { }
    
      private static ConcurrentDictionary<Tuple<string, string>, DbCompiledModel> modelCache
          = new ConcurrentDictionary<Tuple<string, string>, DbCompiledModel>();
    
      public static MyDBContext Create(string tenantSchema, DbConnection connection)
      {
        var compiledModel = modelCache.GetOrAdd
        (
          Tuple.Create(connection.ConnectionString, tenantSchema),
          t =>
          {
            var builder = new DbModelBuilder();
            builder.Conventions.Remove<IncludeMetadataConvention>();
            builder.Entity<Location>().ToTable("Locations", tenantSchema);
            builder.Entity<User>().ToTable("Users", tenantSchema);
    
            var model = builder.Build(connection);
            return model.Compile();
          }
        );
    
      var context = new FmsDBContext(connection, compiledModel);
    
      if( !string.IsNullOrEmpty( tenantSchema ) && !tenantSchema.Equals( "dbo", StringComparison.OrdinalIgnoreCase ) )
      {
        var objectContext = ( (IObjectContextAdapter)context ).ObjectContext;
        objectContext.Connection.Open();
        var currentUser = objectContext.ExecuteStoreQuery<UserContext>( "SELECT CURRENT_USER AS Name", null ).FirstOrDefault();
        if( currentUser.Name.Equals( tenantSchema, StringComparison.OrdinalIgnoreCase ) )
        {
          var executeAs = string.Format( "REVERT; EXECUTE AS User = '{0}';", tenantSchema );
          objectContext.ExecuteStoreCommand( executeAs );
        }
      }
    
      return context;
      }
    }
    

    然后你可以像这样访问 Schema 的信息:

    using (var db = MyDBContext.Create( schemaName, dbConn ))
    {
      // ...
    }
    

    然而,这绕过了实际使用数据库用户。我仍在研究如何使用数据库用户的上下文,而不仅仅是指定架构名称。


    更新:

    我终于解决了这里的最后一个障碍。关键是以下代码:

    if( !string.IsNullOrEmpty( tenantSchema ) && !tenantSchema.Equals( "dbo", StringComparison.OrdinalIgnoreCase ) )
      {
        var objectContext = ( (IObjectContextAdapter)context ).ObjectContext;
        objectContext.Connection.Open();
        var currentUser = objectContext.ExecuteStoreQuery<UserContext>( "SELECT CURRENT_USER AS Name", null ).FirstOrDefault();
        if( currentUser.Name.Equals( tenantSchema, StringComparison.OrdinalIgnoreCase ) )
        {
          var executeAs = string.Format( "REVERT; EXECUTE AS User = '{0}';", tenantSchema );
          objectContext.ExecuteStoreCommand( executeAs );
        }
      }
    

    EXECUTE AS 命令在用于执行稍后的 linq to entity 命令之前在连接上发出。只要连接保持打开状态,用户的上下文就会保持不变。在我的数据库中,租户的架构名称和用户名是相同的。

    多次更改用户的执行上下文会导致错误,因此使用快速查询来确定当前的用户上下文。需要一个小型实体类来使用连接检索信息:

    private class UserContext
    {
      public string Name { get; set; }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-09-07
      • 2021-02-09
      • 2010-12-07
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多