问题
I'm designing a multi-tenant database where each tenant is given a corresponding database user. The user is assigned access rights to the schema associated with the tenant and specific rights to objects in the dbo schema.
Once I've identified the tenant, I want to switch to the appropriate user context by executing a SQL statement like the following:
EXECUTE AS User = 'Tenant1' WITH NO REVERT
When I execute this command using the ExecuteSqlCommand of the DbContext's Database property, everything seems to work correctly. When I later make changes to the model using Linq and call the method
myDbContext.SaveChanges();
I get a series of exceptions:
An error occurred while starting a transaction on the provider connection. See the inner exception for details.
with an inner exception of:
A severe error occurred on the current command. The results, if any, should be discarded.
Is it possible to change the user's execution context in this way and, if so, what is the best way to do so?
回答1:
Have you tried myDbContext.Submit() or SubmitAll ? I cant remember excatly, but it should be something like that...
回答2:
Most of the answer I needed was found here:
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;
}
}
Then you can access the Schema's information like this:
using (var db = MyDBContext.Create( schemaName, dbConn ))
{
// ...
}
This bypasses actually using the database user however. I'm still working on how to use the database user's context instead of just specifying the schema name.
Update:
I finally solved the last hurdle here. The key was the following code:
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 );
}
}
The EXECUTE AS command is issued on the connection before it is used to perform later linq to entity commands. As long as the connection remains open, the user's context remains in place. In my database the schema name and username for a tenant are the same.
Changing the user's execution context multiple times will cause errors, so a quick query is used to determine the current user context. A small entity class is needed to retrieve the information using the connection:
private class UserContext
{
public string Name { get; set; }
}
来源:https://stackoverflow.com/questions/11602409/execute-as-when-using-a-dbcontext-with-linq