我有多个线程使用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);
}
}
}
答案 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”的对象不存在(因为删除)或在您的情况下,它被更新!