我有多个线程有时会使用 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);
}
}
}