如何使用Nhibernate使对象线程安全

时间:2012-06-28 04:39:06

标签: c# .net multithreading nhibernate fluent-nhibernate

我有多个线程使用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;
                        }
                    }
                }
            }
        }
//...

FnhManager:

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

    }
}

3 个答案:

答案 0 :(得分: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)

答案 1 :(得分:1)

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

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

Nhibernate's default approach is Optimistic,如果发生冲突更新,则会抛出您观察到的 StaleObjectStateException

如果您发现Optimistic并发控制不足以满足您的需求,您可以使用NHibernate's Pessimistic Locking mechanism在整个事务期间获取数据库实体的独占锁。

答案 2 :(得分:0)

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