与Linq一起使用DbContext时执行AS

时间:2012-07-22 17:24:47

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

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

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

EXECUTE AS User = 'Tenant1' WITH NO REVERT

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

myDbContext.SaveChanges();

我得到一系列例外:

  

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

内部例外:

  

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

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

2 个答案:

答案 0 :(得分:0)

您是否尝试过myDbContext.Submit()或SubmitAll?我不能记得,但它应该是那样的......

答案 1 :(得分:0)

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

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

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

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

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