Последние две недели я впервые познакомился с Castle ActiveRecord и с шаблоном ActiveRecord в целом. Я работаю над большой системой, которая часто использует ее, и я обнаружил некоторые странные проблемы с транзакциями SQL (например, приведенные ниже), когда я работаю над ней. Я приведу упрощенную версию той, которая меня полностью озадачила:
ФОН:
У меня есть класс ActiveRecord, назовем его User.
Скажем, у этого пользователя много "домашних" объектов.
[ActiveRecord]
public class User: PersistentBase<User>
{
//...
[PrimaryKey]
public long Id
{
get;
set;
}
/// <summary>
/// Date and time the object was first persisted to the database
/// </summary>
[Property, ValidateNonEmpty]
public DateTime CreationDate
{
get;
set;
}
/// <summary>
/// Date and time the object was last persisted to the database
/// </summary>
[Property, ValidateNonEmpty]
public DateTime ModificationDate
{
get;
set;
}
/// <summary>
/// Property used for optimistic concurrency
/// </summary>
[Version]
public int LockCount { get; set; }
[HasMany(typeof(Pet), Cascade = ManyRelationCascadeEnum.SaveUpdate, Lazy = false, OrderBy = "Id")]
public IList<Pet> Pets { get; private set; }
//...
protected override bool BeforeSave(IDictionary state)
{
bool retval = base.BeforeSave(state);
DateTime now = DateTime.Now;
state["CreationDate"] = now;
state["ModificationDate"] = now;
return retval;
}
/// <summary>
/// Called when a dirty object is going to be updated in the db. Use this
/// hook to update ModificationDate.
/// </summary>
/// <param name="id"></param>
/// <param name="previousState"></param>
/// <param name="currentState"></param>
/// <param name="types"></param>
/// <returns></returns>
protected override bool OnFlushDirty(object id, IDictionary previousState, IDictionary currentState, IType[] types)
{
bool retval = base.OnFlushDirty(id, previousState, currentState, types);
currentState["ModificationDate"] = DateTime.Now;
return retval;
}
}
[ActiveRecord]
public class Pet : PersistentBase<Pet>
{
[PrimaryKey]
public long Id
{
get;
set;
}
/// <summary>
/// Date and time the object was first persisted to the database
/// </summary>
[Property, ValidateNonEmpty]
public DateTime CreationDate
{
get;
set;
}
/// <summary>
/// Date and time the object was last persisted to the database
/// </summary>
[Property, ValidateNonEmpty]
public DateTime ModificationDate
{
get;
set;
}
/// <summary>
/// Property used for optimistic concurrency
/// </summary>
[Version]
public int LockCount { get; set; }
//...
[BelongsTo("OwnerId")]
public User User { get; set; }
//...
protected override bool BeforeSave(IDictionary state)
{
bool retval = base.BeforeSave(state);
DateTime now = DateTime.Now;
state["CreationDate"] = now;
state["ModificationDate"] = now;
return retval;
}
/// <summary>
/// Called when a dirty object is going to be updated in the db. Use this
/// hook to update ModificationDate.
/// </summary>
/// <param name="id"></param>
/// <param name="previousState"></param>
/// <param name="currentState"></param>
/// <param name="types"></param>
/// <returns></returns>
protected override bool OnFlushDirty(object id, IDictionary previousState, IDictionary currentState, IType[] types)
{
bool retval = base.OnFlushDirty(id, previousState, currentState, types);
currentState["ModificationDate"] = DateTime.Now;
return retval;
}
}
Теперь у них обоих есть поля автоматического идентификатора (об этом заботится SQL Server 2005).
ПРОБЛЕМА:
Если я иду дальше и добавляю нового питомца к пользователю, у которого уже есть домашние животные, и сохраняю пользователя, я вижу, запускаю ли я SQL Profiler, чтобы на каждом из этих питомцев было вызвано UPDATE ... но ни один не изменился вообще.
Я бросил точки останова повсюду и обнаружил, что при сохранении пользователя каждому из питомцев вызывается «OnFlushDirty» (опять же, хотя они никогда не менялись).
Внешний процесс, который просматривает (и иногда изменяет) этих пользователей и домашних животных, в итоге вызывает серьезные проблемы с транзакциями, которых можно полностью избежать, если в приведенном выше сценарии ТОЛЬКО будет добавлено добавленное домашнее животное (а не ОБНОВЛЕНИЕ домашних животных, которые были не изменилось).
ВОПРОС:
Я делаю что-то выше, что является большим нет-нет с точки зрения того, чтобы такая ситуация не возникала?
Спасибо за любую помощь, которую вы можете оказать!
* РЕДАКТИРОВАТЬ 1: OnFlushDirty имеет значение null previousState._values *
РЕДАКТИРОВАТЬ: О! Я почти забыл самую странную часть всего!
Когда OnFlushDirty вызывается для этих Pets, предыдущиеState и currentState существуют ... они оба (будучи словарём) имеют внутреннюю переменную _values, которая должна иметь значения предыдущего и текущего состояний ...
... эта переменная заполнена только для currentStates. Переменная "_values" для previousState установлена в "null". Обратите внимание, что это на всех домашних животных, которые существовали раньше. previousState всегда должен быть заполнен чем-то, верно?
* РЕДАКТИРОВАТЬ 2: после замены Auto Properties ... *
Я заменил списки авто-свойств традиционным частным участником со средствами доступа к свойствам. Похоже, это не имеет значения. Я установил NHProfiler в системе и обнаружил, что NHProfiler не может подключиться к моему веб-приложению, если я запускаю его через IIS (я использую IIS7 / Win7 с Visual Studio 2008).
Я решил, что вместо этого попробую перейти на использование сервера разработки ASP.NET в Visual Studio, чтобы увидеть, увидит ли приложение NHProfiler.
Когда я сделал это, произошли две вещи:
1) NHProfiler увидел мое приложение и начал сбор данных
2) Многочисленные ОБНОВЛЕНИЯ, сделанные на детей, исчезли
Однако, возвращаясь к IIS7 / Win7, множественные обновления продолжают происходить.
Значит ли это, что это потенциально какая-то проблема конфигурации? Насколько я знаю, ничто в моей конфигурации не должно меняться, кроме URL-адреса, на который я перехожу (http://localhost в IIS, http://localhost:(some_random_port) с сервером разработки ASP.NET) при использовании другого сервера типы. Так почему же две ситуации выше внезапно меняются?