与Linq一起使用DbContext时执行AS

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

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

EXECUTE AS User = 'Tenant1' WITH NO REVERT 

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

 myDbContext.SaveChanges(); 

我得到一系列例外情况:

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

内部例外:

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

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

你试过myDbContext.Submit()还是SubmitAll? 我不能记得,但它应该是那样的……

我需要的大多数答案都在这里找到:

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, DbCompiledModel> modelCache = new ConcurrentDictionary, 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(); builder.Entity().ToTable("Locations", tenantSchema); builder.Entity().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( "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( "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; } }