Здравствуйте, Alkersan, Вы писали:
Привет, позволю себе вставить несколько коментариев и отзывов
A>Сегодня попробовал реализовать историю изменений. Вот что получилось:
A>DomainObject<T> — взят из статьи на codeproject.com — здесь; он отвечает за управление идентификатором (Id) сущности и позволяет отличать сохраненные (Persistent) объекты, новосозданных (Transient) исходя из значения идентификатора.
Скажи, у тебя заказчик просил идентификатор? Прямо так и сказал, что нужен id ему для каких-то целей?? Я в этом сильно сомневаюсь. Раз так — идентификатор ни при каких обстоятельствах не должен быть в доменном объекте. Домен — это твой бизнес. Если в бизнесе нет понятий идентификатора, следовательно, не должно его быть и у тебя. Для таких вещей, как разделение persistent объектов, есть паттерн UnitOfWork. (а я раньше codeproject.com считал неплохим сайтом..жуть)
A>Этот класс расширяется классом AuditableDomainObject<T>, которым я "помечаю" те объекты, подлежат аудиту. Он содержит такой функционал: коллекцию AuditLog и обертку для нее (собственно сам AuditLog и есть обертка, а сама коллекция это список записей IList<AuditLogEntry>) — в этой коллекции накапливаются изменеия с момента последней транзакции. Также существует коллекция auditableEntitys, в которой хрянятся свойства данного объекта, которые "поддаются" аудиту. Т.е. возможна ситуация, что не за всеми полями объекта Resume нужно следить, тогда такое поле просто не попадает в эту коллекцию, и его измения игнорируются. Метод LogChanges() проходится по этой коллекции полей, выявляет изменившиеся или же новосозданные значения ( поле считается созданным — если объект Transient,т.е. если у него идентификатор имеет значение по умолчанию, в данном случае 0, или же поле считается изменённым, если первоначальное значение не равно текущему, см. далее ).
Вот тут более-менее в тему, однако есть излишки. Вовсе ни к чему хранить множество логов и энтитей. Главное ты уловил — разделить изменяемую часть и не изменяемую. Приведу код попозже, как лучше это сделать
A>Собственно поля полежащие аудиту — это члены класса AuditableEntity<T>. Содержат 2 члена — Original и Current, начальное значение и текущее соответсвенно. На основе их принимается решение — писать ли данное состояние в лог или нет. Также есть строковый член FieldName с "человеческим" названием текущего поля (_FName — это "Фамилия" например). Несколько конструкторов... и метод LogChanges(), который пишет в переданный при создании экземпляр AuditLog ткущее состояние. Этот метод вызывается извне, при опросе коллекции auditableEntitys. В эти моменты и происходит запись в лог.
держать Original и Current не имеет смысла. Что если было много изменений, какое считать Original? Если последнее, то можно просто хранить коллекцию и сортировать их при выгрузке по дате создания, например.
Ты вроде говорил, что у тебя есть разделение по слоям!!! тогда что значем "человеческое" название? то, что хочет видеть на экране пользователь? так пусть это лежит не в домене, а в презентационной логике. Опять же, если это не бизнес — нечего ему тут делать.
ну а теперь немного кода:
public class Resume2 : AuditableObject<AuditableEntity2> {
//non-changed fields
}
public class AuditableEntity2 : ICloneable<AuditableEntity2> {
protected string FName { get; set; }
protected string LName { get; set; }
protected string SName { get; set; }
protected DateTime? BirthDate { get; set; }
public AuditableEntity2 Clone() {
//just clone
return null;
}
}
public abstract class AuditableObject<TAuditable> where TAuditable: ICloneable<TAuditable>, new() {
private readonly List<AuditLog<TAuditable>> history = new List<AuditLog<TAuditable>>() ;
private AuditLog<TAuditable> transient;
public TAuditable this[DateTime timePoint] {
get {
return transient != null
?
transient.Version
:
history.Find(
transaction =>
timePoint >= transaction.EffectiveFrom && timePoint < transaction.EffectiveTo).
Version.Clone();
}
}
public void AddVersion(DateTime effectiveFrom, DateTime effectiveTo) {
transient = new AuditLog<TAuditable>(effectiveFrom, effectiveTo, new TAuditable());
}
public void Commit() {
if (transient != null) {
history.Add(transient);
}
transient = null;
}
}
public interface ICloneable<T> {
T Clone();
}
internal class AuditLog<TAuditable> {
public AuditLog(DateTime effectiveFrom, DateTime effectiveTo, TAuditable version) {
EffectiveFrom = effectiveFrom;
EffectiveTo = effectiveTo;
Version = version;
}
public TAuditable Version { get; private set; }
public DateTime EffectiveFrom { get; private set; }
public DateTime EffectiveTo { get; private set; }
}
и естественно, вопросы коммита в базу доменного объекта можно легко делать с помощью NHibernate — закоммитить только трансиентный лог и все, вы получаете историю. Все данные о истории хранятся в доменном объекте.