1

ASP.NET MVC (C#) アプリケーションの一部として NInject と nHibernate を使用する継承されたアプリケーションに取り組んでいます。現在、変更の監査に関する問題を調べています。各エンティティには、ChangedOn/ChangedBy および CreatedOn/CreatedBy フィールドがあり、データベース列にマップされます。ただし、これらは間違ったユーザー名で埋められるか、ユーザー名がまったくないかのいずれかです。これは間違った方法で構成されているためだと思いますが、問題を解決するために nHibernate と NInject について十分に知らないので、誰かが助けてくれることを願っています. アプリケーションで十分な洞察を提供できるように、いくつかのコード スニペットを以下に示します。

セッション ファクトリとセッションの作成:

public class NHibernateModule : NinjectModule
{
    public override void Load()
    {
        Bind<ISessionFactory>().ToProvider(new SessionFactoryProvider()).InSingletonScope();

        Bind<ISession>().ToProvider(new SessionProvider()).InRequestScope();
        Bind<INHibernateUnitOfWork>().To<NHibernateUnitOfWork>().InRequestScope();
        Bind<User>().ToProvider(new UserProvider()).InRequestScope();
        Bind<IStamper>().ToProvider(new StamperProvider()).InRequestScope();
    }
}

public class SessionProvider : Provider<ISession>
{
    protected override ISession CreateInstance(IContext context)
    {
        // Create session
        var sessionFactory = context.Kernel.Get<ISessionFactory>();            
        var session = sessionFactory.OpenSession();            
        session.FlushMode = FlushMode.Commit;

        return session;
    }
}

public class SessionFactoryProvider : Provider<ISessionFactory>
{
    protected override ISessionFactory CreateInstance(IContext context)
    {
        var connectionString = ConfigurationManager.ConnectionStrings["DefaultConnectionString"].ToString();
        var stamper = context.Kernel.Get<IStamper>();

        return NHibernateHelper.CreateSessionFactory(connectionString, stamper);
    }
}

public class StamperProvider : Provider<IStamper>
{
    protected override IStamper CreateInstance(IContext context)
    {
        System.Security.Principal.IPrincipal user = HttpContext.Current.User;
        System.Security.Principal.IIdentity identity = user == null ? null : user.Identity;
        string name = identity == null ? "Unknown" : identity.Name;

        return new Stamper(name);
    }
}

public class UserProvider : Provider<User>
{
    protected override UserCreateInstance(IContext context)
    {
        var userRepos = context.Kernel.Get<IUserRepository>();

        System.Security.Principal.IPrincipal user = HttpContext.Current.User;
        System.Security.Principal.IIdentity identity = user == null ? null : user.Identity;
        string name = identity == null ? "" : identity.Name;

        var user = userRepos.GetByName(name);
        return user;
    }
}

セッション ファクトリの構成:

public static ISessionFactory CreateSessionFactory(string connectionString, IStamper stamper)
    {
        // Info: http://wiki.fluentnhibernate.org/Fluent_configuration
        return Fluently.Configure()
                .Database(MsSqlConfiguration.MsSql2008
                    .ConnectionString(connectionString))
                .Mappings(m => 
                    {
                        m.FluentMappings
                            .Conventions.Add(PrimaryKey.Name.Is(x => "Id"))
                            .AddFromAssemblyOf<NHibernateHelper>();

                        m.HbmMappings.AddFromAssemblyOf<NHibernateHelper>();
                    })
                // Register 
                .ExposeConfiguration(c => {
                    c.EventListeners.PreInsertEventListeners = 
                        new IPreInsertEventListener[] { new EventListener(stamper) };
                    c.EventListeners.PreUpdateEventListeners =
                        new IPreUpdateEventListener[] { new EventListener(stamper) };
                })
                .BuildSessionFactory();
     }

イベントリスナーからのスニペット:

public bool OnPreInsert(PreInsertEvent e)
{
    _stamper.Insert(e.Entity as IStampedEntity, e.State, e.Persister);
    return false;
}

ご覧のとおり、セッション ファクトリはシングルトン スコープにあります。したがって、イベントリスナーとスタンパーもこのスコープでインスタンス化されます (私はそう思います)。これは、ユーザーがまだログインしていない場合、スタンパーのユーザー名が空の文字列または「不明」に設定されることを意味します。スタンパーを改造することで、この問題を補おうとしました。ユーザー名が null または空かどうかをチェックします。これが true の場合、アクティブなユーザーを見つけようとし、username-property にそのユーザーの名前を入力します。

    private string GetUserName()
    {
        if (string.IsNullOrWhiteSpace(_userName))
        {
            var user = ServiceLocator.Resolve<User>();

            if (user != null)
            {
                _userName = user.UserName;
            }
        }

        return _userName;
    }

ただし、これにより、アプリケーションにもログインし、データベースにログインしている、まったく異なるユーザー名が発生します。これは、トランザクションを開始したユーザーではなく、最後にログインしたユーザーである間違ったアクティブなユーザーを解決するためだと思います。

4

2 に答える 2

4

問題のある部分は次のとおりです。

Bind<ISessionFactory>().
    .ToProvider(new SessionFactoryProvider())
    .InSingletonScope();

Bind<IStamper>()
    .ToProvider(new StamperProvider())
    .InRequestScope();

そして後で:

public class SessionFactoryProvider : Provider<ISessionFactory>
{
    protected override ISessionFactory CreateInstance(IContext context)
    {
        // Unimportant lines omitted
        var stamper = context.Kernel.Get<IStamper>();
        return NHibernateHelper.CreateSessionFactory(connectionString, stamper);
    }
}

public class StamperProvider : Provider<IStamper>
{
    protected override IStamper CreateInstance(IContext context)
    {
        // Unimportant lines omitted
        string name = /* whatever */
        return new Stamper(name);
    }
}

コードで何が起こっているかを分析しましょう。

  • ISessionFactory単一インスタンスとしてバインドされます。プロセスの存続期間中、1 つだけ存在します。これはかなり典型的です。

  • ISessionFactoryで初期化されSessionFactoryProvider、すぐに のインスタンスを取得し、これを定数IStamper引数として渡してセッション ファクトリを初期化します。

  • は、現在のユーザー プリンシパル/IDに設定された定数でクラスを初期IStamper化する によって順に初期化されます。StamperProviderStamper name

これの最終的な結果は、プロセスが生きている限り、すべての「スタンプ」に最初にログインしたユーザーの名前が割り当てられることです。これは匿名ユーザーである可能性もあります。多くの空白のエントリ。

これを書いた人は誰でも、方程式の半分しか正しくありません。はIStamperrequest スコープにバインドされていますが、singletonに提供されています。つまり、作成されるのは 1 つだけです。がリソースを保持していないか、ファイナライザーを持っていないことは幸運です。そうしないと、多くの奇妙なエラーが発生する可能性があります。IStamperStamperObjectDisposedException

これには、次の 3 つの解決策があります。

  1. (推奨) -Stamperクラスを書き直して、静的なユーザー情報で初期化するのではなく、各呼び出しで現在のユーザーを検索します。その後、Stamperクラスはコンストラクター引数を取りません。IStamper InSingletonScopeの代わりに をバインドできますInRequestScope

  2. メソッドで抽象IStamperFactoryを作成し、インスタンスをラップしてそれを実装する具象を作成します。これらを結合します。あなたの具体的な工場を持っています。の代わりにを受け入れて保持するようにセッション ファクトリを変更します。をスタンプする必要があるたびに、ファクトリを使用して新しいインスタンスを取得します。GetStamperStamperFactoryIKernelInSingletonScopereturn kernel.Get<IStamper>()IStamperFactory IStamperIStamper

  3. を に変更ISessionFactoryしますInRequestScope。DB で生成された ID を使用しない場合、パフォーマンスが低下し、ID ジェネレーターが混乱する可能性があるため、お勧めしませんが、監査の問題解決します。

于 2011-08-19T22:35:34.127 に答える
1

アーロノート、あなたの分析は私が疑ったことを正確に説明しています。しかし、私は、より簡単でより簡単なIMHOである4番目の解決策があることを発見しました。の呼び出しOpenSessionがIInterceptorのインスタンスを引数として取るように、sessionproviderを変更しました。結局のところ、イベントリスナーは実際には監査に使用されることは想定されていません(ファビオによれば、それ以外は彼が正しいことです)。

AuditInterceptor実装OnFlushDirty(既存のエンティティを監査するため)および(OnSave新しく作成されたエンティティを監査するため)。以下のSessionProviderように見えます:

public class SessionProvider : Provider<ISession>
{
    protected override ISession CreateInstance(IContext context)
    {
        // Create session
        System.Security.Principal.IPrincipal user = HttpContext.Current.User;
        System.Security.Principal.IIdentity identity = user == null ? null : user.Identity;
        string name = identity == null ? "" : identity.Name;

        var sessionFactory = context.Kernel.Get<ISessionFactory>();
        var session = sessionFactory.OpenSession(new AuditInterceptor(name));            
        session.FlushMode = FlushMode.Commit;

        return session;
    }
}
于 2011-08-22T07:09:24.740 に答える