0

NHibernate を使用して同じオブジェクト グラフの SaveOrUpdate を高速化する複数のスレッドがあります。私は現在「リクエストごとのセッション」を使用していますが、少なくともそうしていると思います(?)。時々例外が発生します:

StaleObjectStateException 行が別のトランザクションによって更新または削除されました (または、保存されていない値のマッピングが正しくありませんでした):

私のプログラムは、完全なランタイム エンティティを維持するために起動時に完全なデータベースをロードし、その後、SaveOrUpdate 操作のみを行い、まれに削除操作を行います。これらは一般的なユーザー インタラクション プログラムではなく、金融市場データなどのリモート イベントで実行されるロボットです。

古い状態を説明する可能性のある明らかな設計上の欠陥/不十分な慣行はありますか?

リポジトリ:

    public class GenericRepository<T>
    {

        public IList<T> GetAll()
        {
            using (ISession session = FnhManager.OpenSession())
            {
                var instances = session.CreateCriteria(typeof(T)).List<T>();
                return instances;
            }
        }

        public void SaveOrUpdate(IList<T> instances)
        {

            if (instances != null)
            {
                using (ISession session = FnhManager.OpenSession())
                {
                    using (ITransaction transaction = session.BeginTransaction())
                    {
                        try
                        {
                            foreach (var i in instances)
                            {
                                session.SaveOrUpdate(i);
                            }
                            transaction.Commit();

                        }
                        catch (Exception ex)
                        {
                            transaction.Rollback();     
                            Trace.TraceError("GenericRepository.SaveOrUpdate IList<" + typeof(T).ToString() + ">" , ex.ToString());
                            throw;
                        }
                    }
                }
            }
        }
//...

Fnhマネージャー:

public class FnhManager
{

    private static Configuration cfg;
    private static ISessionFactory sessionFactory;

    private static string connectionString;


    private FnhManager(){}


    public static ISession OpenSession()
    {
        return sessionFactory.OpenSession();
    }


    /// <summary>
    /// Pass Any map class, used to locate all maps.
    /// </summary>
    /// <typeparam name="TAnyMap"></typeparam>
    /// <param name="path"></param>
    /// <param name="DbFileName"></param>
    /// <remarks></remarks>
    public static void ConfigureSessionFactory<TAnyMap>(string path, string DbFileName, DatabaseType type)
    {

        connectionString = "Data Source=" + Path.Combine(path, DbFileName);

        switch (type)
        {
            case DatabaseType.SqlCe:
                sessionFactory = CreateSessionFactorySqlCe<TAnyMap>(path,DbFileName);
                break;

            case DatabaseType.SQLite:
                sessionFactory = CreateSessionFactorySQLite<TAnyMap>();
                break;
        }

    }

    private static ISessionFactory CreateSessionFactorySQLite<TMap>()
    {

        Trace.TraceInformation("Creating SessionFactory SQLite for: " + connectionString);

        try
        {
            var fluentConfiguration = Fluently.Configure()
                .Database(SQLiteConfiguration.Standard.ConnectionString(connectionString))
                .Mappings(m => m.FluentMappings.AddFromAssemblyOf<TMap>()
                   .Conventions.Add(FluentNHibernate.Conventions.Helpers.DefaultLazy.Never()))
                   .ExposeConfiguration(c => cfg = c)
                   .Cache(c => c.UseQueryCache());

            sessionFactory = fluentConfiguration.BuildSessionFactory();

            return sessionFactory;
        }
        catch (Exception ex)
        {
            Trace.TraceError("Create SessionFactory Exception: " + ex.ToString());
            throw;
        }


    }

    private static ISessionFactory CreateSessionFactorySqlCe<TMap>( string dbPath, string dbName )
    {

        //Must add SqlCe dll x86+amd64-folders to bin folder.  !!! 

        FileInfo f = new FileInfo(Path.Combine(dbPath, dbName));
        if (!f.Exists)
        {
            var engine = new SqlCeEngine(connectionString);
            engine.CreateDatabase();
        }

        var fluentConfiguration = Fluently.Configure()
            .Database(MsSqlCeConfiguration.Standard.ConnectionString( s => s.Is(connectionString)))
            .Mappings(m => m.FluentMappings.AddFromAssemblyOf<TMap>()
                .Conventions.Add(FluentNHibernate.Conventions.Helpers.DefaultLazy.Never()))
               .ExposeConfiguration(c => cfg = c)
                .Cache(c => c.UseQueryCache());
        sessionFactory = fluentConfiguration.BuildSessionFactory();

        return sessionFactory;
    }

    public static void BuildDatabaseFromSchema()
    {
        SchemaExport e = new SchemaExport(cfg);
        e.Execute(false, true, false);
    }


    public static void ValidateDatabase()
    {

        SchemaValidator validator = new SchemaValidator(cfg);

        try
        {
            validator.Validate();
        }
        catch (HibernateException ex)
        {
            // not valid, try to update
            try
            {

                SchemaUpdate update = new SchemaUpdate(cfg);
                update.Execute(false, true);

            }
            catch (HibernateException e)
            {
                Trace.TraceError("Invalid schema. HibernateException: ", ex.ToString());
            }

        }
        catch (Exception ex)
        {
            // System.Windows.Forms.MessageBox.Show("Invalid schema: Exception: (Should not occur) " + ex.ToString);
        }

    }
}
4

3 に答える 3

2

私の悪い英語と複雑な説明で申し訳ありません。しかし、私はもう一度試します:

MyDomain first = MyDomainDao.Load(1);

MyDomain second = MyDomainDao.Load(1);

first.Name = "juhe";

MyDomainDao.SaveOrUpdate(first);

// Throws an exception, because the 'second' is not 'refreshed'

MyDomainDao.SaveOrUpdate(second); 

コードがこのようなものである場合、この問題が発生します。このロードは異なるスレッドで行うことができます。現在、オブジェクトはすべてのセッション内で 2 つの異なる状態を持っています。

ドメインオブジェクトのバージョン管理についてはどうですか? 私はあなたの実装を正確には知りませんが、エンティティを更新してみてください:

 Session.Refresh(myDomain, LockMode.None)
于 2012-06-29T11:24:48.007 に答える
1

オブジェクトリレーショナルマッピングの範囲では、同時実行制御には2つの主要なアプローチ、OptimisticとPessimisticがあり、これらは通常、アプリケーションデータアクセス層に実装されます。

オプティミスティック同時実行制御では、アプリケーションは同じデータベースエンティティが同時に更新されることを想定していないため、複数のスレッドがロックなしで同時にアクセスできます。ただし、同じバージョンのデータベースエンティティを更新しようとして2つのスレッドがキャッチされた場合、一方のスレッドは操作をロールバックするように強制されます。そうでない場合、一方の更新によって他方のスレッドが上書きされます。

NhibernateのデフォルトのアプローチはOptimisticであり、競合する更新が発生した場合、観察したStaleObjectStateExceptionがスローされます。

楽観的同時実行制御ではニーズが十分でない場合は、NHibernateのペシミスティックロックメカニズムを使用して、トランザクション全体でデータベースエンティティの排他ロックを取得できます。

于 2012-06-28T23:32:32.540 に答える
0

オブジェクトを 2 回 (またはそれ以上) ロードし、最初のオブジェクトを「状態 A」に、2 番目のオブジェクトを「状態 B」に保存すると、例外が発生します。そのため、NHibernate は状態が同じかどうかをチェックします。「状態B」のオブジェクトは(削除のため)もう存在しないか、あなたの場合は更新されています!

于 2012-06-28T09:17:25.833 に答える