0

我有多个线程有时会使用 NHibernate 对同一对象图进行快速 SaveOrUpdate。我目前使用“每个请求的会话”,至少我认为我这样做(?)。有时我会遇到异常:

StaleObjectStateException 行被另一个事务更新或删除(或未保存值映射不正确):

我的程序在启动时加载完整的数据库以维护完整的运行时实体,然后只进行 SaveOrUpdate 操作,并且在极少数情况下进行 Delete 操作。这些不是典型的用户交互程序,而是在金融市场数据等远程事件上运行的机器人。

是否有一些明显的设计缺陷/不良实践可以解释陈旧状态?

存储库:

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

如果你的代码是这样的,你就会遇到这个问题。这种加载可以在不同的线程中,现在对象在每个会话中都有两个不同的状态。

你对域对象进行版本控制怎么样?我不完全了解您的实现,但尝试刷新您的实体:

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

在对象关系映射的范围内,有两种主要的并发控制方法,乐观和悲观,它们通常在应用程序数据访问层中实现。

在乐观并发控制下,您的应用程序不希望同时更新同一个数据库实体,因此允许多个线程同时访问它而无需任何锁定。但是,如果两个线程试图更新同一版本的数据库实体,其中一个将被迫回滚操作,否则一个更新将覆盖另一个的更新。

Nhibernate 的默认方法是 Optimistic,如果发生冲突更新,则会抛出您观察到的StaleObjectStateException 。

如果您发现乐观并发控制不足以满足您的需求,您可以使用NHibernate 的悲观锁定机制在整个事务期间获取数据库实体上的排他锁。

于 2012-06-28T23:32:32.540 回答
0

发生异常是因为您加载一个对象两次(或更多次)并将第一次保存在“状态 A”中,第二次保存在“状态 B”中。因此,NHibernate 将检查状态是否相同。具有“状态 B”的对象不再存在(因为删除),或者在您的情况下,它已更新!

于 2012-06-28T09:17:25.833 回答