Проект я работаю над потребностями некоторый простой аудит, регистрирующийся для того, когда пользователь изменяет их электронную почту, адрес выставления счета, и т.д. Объекты, с которыми мы работаем, прибывают из других источников, один сервис WCF, другой веб-сервис.
Я реализовал следующий метод с помощью отражения для нахождения изменений в свойствах на двух различных объектах. Это генерирует список свойств, которые расходятся во мнениях наряду с их старыми и новыми значениями.
public static IList GenerateAuditLogMessages(T originalObject, T changedObject)
{
IList list = new List();
string className = string.Concat("[", originalObject.GetType().Name, "] ");
foreach (PropertyInfo property in originalObject.GetType().GetProperties())
{
Type comparable =
property.PropertyType.GetInterface("System.IComparable");
if (comparable != null)
{
string originalPropertyValue =
property.GetValue(originalObject, null) as string;
string newPropertyValue =
property.GetValue(changedObject, null) as string;
if (originalPropertyValue != newPropertyValue)
{
list.Add(string.Concat(className, property.Name,
" changed from '", originalPropertyValue,
"' to '", newPropertyValue, "'"));
}
}
}
return list;
}
Я ищу Систему. IComparable, потому что "Все числовые типы (такие как Int32 и Дважды) реализуют IComparable, также, как и Строка, Символ и DateTime". Это казалось лучшим способом найти любое свойство, это не пользовательский класс.
Наслаждение событием PropertyChanged, это сгенерировано WCF или кодом прокси веб-сервиса, звучало хорошим, но не дает мне достаточно информации для моих контрольных журналов (старые и новые значения).
Поиск входа относительно того, если существует лучший способ сделать это, Спасибо!
@Aaronaught, вот некоторый пример кода, который генерирует положительное совпадение на основе выполнения объекта. Равняется:
Address address1 = new Address();
address1.StateProvince = new StateProvince();
Address address2 = new Address();
address2.StateProvince = new StateProvince();
IList list = Utility.GenerateAuditLogMessages(address1, address2);
"[Адрес] StateProvince, измененный от 'MyAccountService. StateProvince' к 'MyAccountService. StateProvince'"
Это - два различных экземпляра класса StateProvince, но значения свойств являются тем же (весь пустой указатель в этом случае). Мы не переопределяем, равняется методу.
IComparable
предназначен для упорядочивания сравнений. Либо используйте вместо него IEquatable
, либо просто используйте статический метод System.Object.Equals
. Последний имеет то преимущество, что он также работает, если объект не является примитивным типом, но все же определяет собственное сравнение на равенство путем переопределения Equals
.
object originalValue = property.GetValue(originalObject, null);
object newValue = property.GetValue(changedObject, null);
if (!object.Equals(originalValue, newValue))
{
string originalText = (originalValue != null) ?
originalValue.ToString() : "[NULL]";
string newText = (newText != null) ?
newValue.ToString() : "[NULL]";
// etc.
}
Это явно не идеально, но если вы делаете это только с классами, которые вы контролируете, то можете быть уверены, что это всегда работает для ваших конкретных нужд.
Существуют и другие методы сравнения объектов (например, контрольные суммы, сериализация и т. Д.), Но это, вероятно, самый надежный, если классы не реализуют последовательно IPropertyChanged
, и вы действительно хотите знать различия .
Обновление для нового примера кода:
Address address1 = new Address();
address1.StateProvince = new StateProvince();
Address address2 = new Address();
address2.StateProvince = new StateProvince();
IList list = Utility.GenerateAuditLogMessages(address1, address2);
Причина, по которой использование object.Equals
в вашем методе аудита приводит к «попаданию», заключается в том, что экземпляры на самом деле не равны!
Конечно, StateProvince
может быть пустым в обоих случаях, но address1
и address2
по-прежнему имеют ненулевые значения для StateProvince
] свойство, и каждый экземпляр отличается. Следовательно, адрес1
и адрес2
имеют разные свойства.
Давайте перевернем это, возьмем этот код в качестве примера:
Address address1 = new Address("35 Elm St");
address1.StateProvince = new StateProvince("TX");
Address address2 = new Address("35 Elm St");
address2.StateProvince = new StateProvince("AZ");
Следует ли считать их равными? Что ж, они будут, используя ваш метод, потому что StateProvince
не реализует IComparable
. Это единственная причина, по которой ваш метод сообщил, что в исходном случае эти два объекта были одинаковыми.Поскольку класс StateProvince
не реализует IComparable
, трекер просто полностью пропускает это свойство. Но эти два адреса явно не равны!
Вот почему я изначально предложил использовать object.Equals
, потому что тогда вы можете переопределить его в методе StateProvince
, чтобы получить лучшие результаты:
public class StateProvince
{
public string Code { get; set; }
public override bool Equals(object obj)
{
if (obj == null)
return false;
StateProvince sp = obj as StateProvince;
if (object.ReferenceEquals(sp, null))
return false;
return (sp.Code == Code);
}
public bool Equals(StateProvince sp)
{
if (object.ReferenceEquals(sp, null))
return false;
return (sp.Code == Code);
}
public override int GetHashCode()
{
return Code.GetHashCode();
}
public override string ToString()
{
return string.Format("Code: [{0}]", Code);
}
}
Как только вы это сделаете , код object.Equals
будет работать отлично. Вместо того, чтобы наивно проверять, действительно ли address1
и address2
имеют одну и ту же ссылку StateProvince
, он фактически проверяет семантическое равенство.
Другой способ обойти это - расширить код отслеживания, чтобы он действительно спускался в подобъекты. Другими словами, для каждого свойства проверьте Type.IsClass
и, необязательно, свойство Type.IsInterface
, и если true
, то рекурсивно вызовите отслеживание изменений в самом свойстве, добавляя к любому рекурсивному возвращаемому результату аудита префикс имени свойства. Таким образом, вы получите изменение для StateProvinceCode
.
Иногда я тоже использую вышеупомянутый подход, но проще просто переопределить Equals
для объектов, для которых вы хотите сравнить семантическое равенство (т.е. аудит), и предоставить соответствующий ToString
переопределение, которое проясняет, что изменилось. Он не масштабируется для глубокого вложения, но я думаю, что это необычно - проводить аудит таким образом.
Последний трюк - определить собственный интерфейс, скажем IAuditable
, который принимает второй экземпляр того же типа в качестве параметра и фактически возвращает список (или перечислимый) всех различия. Он похож на наш переопределенный метод object.Equals
выше, но возвращает больше информации. Это полезно, когда граф объекта действительно сложен и вы знаете, что не можете полагаться на Reflection или Equals
. Вы можете комбинировать это с вышеуказанным подходом; на самом деле все, что вам нужно сделать, это заменить IComparable
на ваш IAuditable
и вызвать метод Audit
, если он реализует этот интерфейс.
Я думаю, что этот метод довольно удобен, он позволяет избежать повторения или добавления чего-либо на занятия. Что еще вы ищете?
Единственная альтернатива - создать словарь состояний для старых и новых объектов и написать для них сравнение. Код для создания словаря состояний может повторно использовать любую сериализацию, которая у вас есть для хранения этих данных в базе данных.
Этот проект на codeplex проверяет почти все типы свойств и может быть настроен по вашему желанию.
Вы можете посмотреть на Microsoft's Testapi У него есть api сравнения объектов, который делает глубокие сравнения. Возможно, для вас это будет излишеством, но посмотреть стоит.
var comparer = new ObjectComparer(new PublicPropertyObjectGraphFactory());
IEnumerable<ObjectComparisonMismatch> mismatches;
bool result = comparer.Compare(left, right, out mismatches);
foreach (var mismatch in mismatches)
{
Console.Out.WriteLine("\t'{0}' = '{1}' and '{2}'='{3}' do not match. '{4}'",
mismatch.LeftObjectNode.Name, mismatch.LeftObjectNode.ObjectValue,
mismatch.RightObjectNode.Name, mismatch.RightObjectNode.ObjectValue,
mismatch.MismatchType);
}