2

各テナントに対応するデータベース ユーザーが割り当てられるマルチテナント データベースを設計しています。ユーザーには、テナントに関連付けられたスキーマへのアクセス権と、dbo スキーマ内のオブジェクトへの特定の権利が割り当てられます。

テナントを特定したら、次のような SQL ステートメントを実行して、適切なユーザー コンテキストに切り替えたいと考えています。

EXECUTE AS User = 'Tenant1' WITH NO REVERT

DbContext の Database プロパティの ExecuteSqlCommand を使用してこのコマンドを実行すると、すべてが正しく機能しているように見えます。後でLinqを使用してモデルに変更を加え、メソッドを呼び出すと

myDbContext.SaveChanges();

一連の例外が発生します。

プロバイダー接続でトランザクションを開始中にエラーが発生しました。詳細については、内部例外を参照してください。

内部例外:

現在のコマンドで重大なエラーが発生しました。結果がある場合は、破棄する必要があります。

この方法でユーザーの実行コンテキストを変更することは可能ですか?もしそうなら、それを行う最善の方法は何ですか?

4

2 に答える 2

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

次に、次のようにスキーマの情報にアクセスできます。

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コマンドは、後でlinqtoentityコマンドを実行するために使用される前に接続で発行されます。接続が開いたままである限り、ユーザーのコンテキストはそのまま残ります。私のデータベースでは、テナントのスキーマ名とユーザー名は同じです。

ユーザーの実行コンテキストを複数回変更するとエラーが発生するため、クイッククエリを使用して現在のユーザーコンテキストを判別します。接続を使用して情報を取得するには、小さなエンティティクラスが必要です。

private class UserContext
{
  public string Name { get; set; }
}
于 2012-07-23T00:32:11.470 に答える
0

myDbContext.Submit() または SubmitAll を試しましたか? 正確には覚えていませんが、そのようなものでなければなりません...

于 2012-07-22T17:28:24.907 に答える