Все эти выходные царапал мне голову.
Справочная информация: Проект представляет собой MVC3, C # 4, FluentNHibernate (1.2.0.712), nHibernate (3.1.0.4) и StructureMap (2.6.3.0), все они установлены через NuGet. Это большая кодовая база, которую я не создавал, я просто пытаюсь добавить некоторые функциональные возможности для клиента ...
Существует существующий IPreInsertEventListener, который устанавливает информацию об аудите (CreatedDate, CreatedBy и т. Д.), Где CreatedBy - это пользовательская сущность приложения, которая извлекается из HttpSessionState через некоторый IOC voodoo. Это уже существующее и работающее ...
public class AuditEventListener : IPreInsertEventListener
{
public bool OnPreInsert(PreInsertEvent eventItem)
{
if (eventItem.Entity is IAudit)
{
IAudit auditObject = (IAudit)eventItem.Entity;
Store(eventItem.Persister, eventItem.State, "CreatedDate", DateTime.Now);
Store(eventItem.Persister, eventItem.State, "CreatedBy", GetCurrentUser());
Store(eventItem.Persister, eventItem.State, "LastModifiedDate", DateTime.Now);
Store(eventItem.Persister, eventItem.State, "LastModifiedBy", GetCurrentUser());
auditObject.CreatedBy = GetCurrentUser();
auditObject.CreatedDate = DateTime.Now;
auditObject.LastModifiedBy = GetCurrentUser();
auditObject.LastModifiedDate = DateTime.Now;
}
return false;
}
private Model.WebUser GetCurrentUser()
{
return ObjectFactory.GetInstance<ICurrentUser>().Get();
}
}
Функция GetCurrentUser () - это оболочка для вызова StructureMap ObjectFactory службы, которая ищет в HttpSessionState пользователя, вошедшего в систему. По сути, это делает:
if(HttpContext.Current != null)
return WebUser.GetBy(wu => wu.EmailAddress == HttpContext.Current.Session["Email"].ToString();
else
return null;
Функция Store - это шаблонный код, который устанавливает значения в массиве eventItem.State по их именам из Persister. Опущено, потому что я не думаю, что это является частью проблемы.
Я добавил второй IPreInsertEventListener, предназначенный для создания журнала «История» с полной таблицей, основанного на том, что делает nhibernate.evers (который я не могу использовать здесь из-за проблем с версией). Он в основном проверяет наличие атрибута, отмечающего сохраняемый объект, и, если он его находит, копирует текущие и старые значения в связанный «HistoryModel» и сохраняет его.
public class HistoryEventListener : IPreInsertEventListener
{
public bool OnPreInsert(PreInsertEvent eventItem)
{
var model = TryGetHistoryModel(eventItem.Entity);
if (model != null)
{
MapToHistory(eventItem.Entity, model);
session.Save(model);
}
return false;
}
private static IHistoryModel TryGetHistoryModel(object entity)
{
var att = entity.GetAttribute<HistoryLogAttribute>();
if (att != null)
return ObjectFactory.GetInstance(att.HistoryModelType) as IHistoryModel;
else
return null;
}
}
Функция MapToHistory в основном выполняет наивное отражение на основе отражения в модели, которая передается ей, используя имена, которые она получает из eventItem.Persister, для поиска имен свойств. Например, свойство «ProcessingStatusId» из eventItem.State будет сопоставлено с model.NewProcessingStatusId. Я опускаю это для краткости. Просто предположим, что мелкая копия создается и возвращается.
Слушатели подключены через StructureMap.Configuration.DSL.Registry как часть Fluently.Configure.BuildSessionFactory:
return Fluently.Configure()
.Database(
MsSqlConfiguration.MsSql2008.ShowSql().ConnectionString(
connection => connection.FromConnectionStringWithKey("DefaultConnectionString")))
.Mappings(mapping => mapping.FluentMappings.AddFromAssemblyOf<SiteUser>())
.ExposeConfiguration(cfg =>
{
new SchemaUpdate(cfg).Execute(false, false);
cfg.EventListeners.PreInsertEventListeners = new NHibernate.Event.IPreInsertEventListener[] { new AuditEventListener(), new HistoryEventListener() };
})
.BuildSessionFactory();
Теперь, когда вся преамбула удалена, проблема:
Если бы я должен был создать и сохранить объект, который не помечен HistoryAttribute, HistoryEventListener пропустит его обработку как следует, и все будет работать правильно. Однако если сохраняется объект, помеченный атрибутом, происходит этот процесс:
AuditEventListener запускает для помеченного объекта, устанавливает CreatedBy,
CreatedDate и т. Д. Правильно, потому что HttpContext.Current не является нулевым.
HistoryEventListener срабатывает, создает историю объекта из отмеченного
сущность и вызывает Session.Save.
AuditEventListener запускает для объекта истории, устанавливает CreatedDate, но для CreatedBy устанавливается значение NULL, , поскольку HttpContext.Current равен NULL.
Я должен четко указать, что я ХОЧУ, чтобы шаг # 3 происходил здесь, поэтому таблица истории собирает информацию CreatedDate и CreatedBy. Я мог бы добавить это явно в HistoryEventListener, но тогда я повторюсь.
Я немного погуглил, и я почти уверен, что наличие нескольких прослушивателей событий само по себе здесь не проблема - похоже, рекомендуемый путь.
Вызывает ли действие сохранения формы нового объекта сущности внутри слушателя события какое-то сумасшедшее переключение контекста, которое убивает текущий HttpContext? Это ошибка?
Спасибо.