Comparing Nested object properties using C# - c#

I have a method which compares two objects and returns a list of all the property names which are different.
public static IList<string> GetDifferingProperties(object source, object target)
{
var sourceType = source.GetType();
var sourceProperties = sourceType.GetProperties();
var targetType = target.GetType();
var targetProperties = targetType.GetProperties();
var properties = (from s in sourceProperties
from t in targetProperties
where s.Name == t.Name &&
s.PropertyType == t.PropertyType &&
s.GetValue(source,null) != t.GetValue(target,null)
select s.Name).ToList();
return properties;
}
For example if I have two classes as follows:
public class Address
{
public string AddressLine1 { get; set; }
public string AddressLine2 { get; set; }
public string City { get; set; }
public string State { get; set; }
public string Zip { get; set; }
}
public class Employee
{
public string FirstName { get; set; }
public string MiddleName { get; set; }
public string LastName { get; set; }
public Address EmployeeAddress { get; set; }
}
I am trying to compare the following two employee instances:
var emp1Address = new Address();
emp1Address.AddressLine1 = "Microsoft Corporation";
emp1Address.AddressLine2 = "One Microsoft Way";
emp1Address.City = "Redmond";
emp1Address.State = "WA";
emp1Address.Zip = "98052-6399";
var emp1 = new Employee();
emp1.FirstName = "Bill";
emp1.LastName = "Gates";
emp1.EmployeeAddress = emp1Address;
var emp2Address = new Address();
emp2Address.AddressLine1 = "Gates Foundation";
emp2Address.AddressLine2 = "One Microsoft Way";
emp2Address.City = "Redmond";
emp2Address.State = "WA";
emp2Address.Zip = "98052-6399";
var emp2 = new Employee();
emp2.FirstName = "Melinda";
emp2.LastName = "Gates";
emp2.EmployeeAddress = emp2Address;
So when I pass these two employee objects to my GetDifferingProperties method currently it returns FirstName and EmployeeAddress, but it does not tell me which exact property (which in this case is Address1) in the EmployeeAddress has changed. How can I tweak this method to get something like EmployeeAddress.Address1?

It's because you are using != which, for objects, tests the identity of an object rather than its value. The key is to use recursion to generate the list of properties of properties. This will go as deep as you want...
public static IList<string> GetDifferingProperties(object source, object target)
{
var sourceType = source.GetType();
var sourceProperties = sourceType.GetProperties();
var targetType = target.GetType();
var targetProperties = targetType.GetProperties();
var result = new List<string>();
foreach (var property in
(from s in sourceProperties
from t in targetProperties
where s.Name == t.Name &&
s.PropertyType == t.PropertyType &&
!Equals(s.GetValue(source, null), t.GetValue(target, null))
select new { Source = s, Target = t }))
{
// it's up to you to decide how primitive is primitive enough
if (IsPrimitive(property.Source.PropertyType))
{
result.Add(property.Source.Name);
}
else
{
foreach (var subProperty in GetDifferingProperties(
property.Source.GetValue(source, null),
property.Target.GetValue(target, null)))
{
result.Add(property.Source.Name + "." + subProperty);
}
}
}
return result;
}
private static bool IsPrimitive(Type type)
{
return type == typeof(string) || type == typeof(int);
}

I can recommend using http://comparenetobjects.codeplex.com/
This has a possibility to compare nested objects, enums, ILists, etc.
The project is free and easy to use(Just 1 .cs file). Moreover, it is possible to get the values that are different, add properties to ignore, etc.

In principle, you'll need to use the technique you implemented in GetDifferingProperties on the two objects that you want to compare after you get their values (using GetValue in the query). Probably the most straightforward implementation is to make the method recursive:
public static IEnumerable<string> GetDifferingProperties
(object source, object target) {
// Terminate recursion - equal objects don't have any differing properties
if (source == target) return new List<string>();
// Compare properties of two objects that are not equal
var sourceProperties = source.GetType().GetProperties();
var targetProperties = target.GetType().GetProperties();
return
from s in sourceProperties
from t in targetProperties
where s.Name == t.Name && s.PropertyType == t.PropertyType
let sVal = s.GetValue(source, null)
let tVal = t.GetValue(target, null)
// Instead of comparing the objects directly using '==', we run
// the method recursively. If the two objects are equal, it returns
// empty list immediately, otherwise it generates multiple properties
from name in GetDifferingProperties(sVal, tVal)
select name;
}
If you want to use this in practice, you'll probably want to keep track of how to get to the property (this code gives you just a list of property names without information about the object that contains them). You can change the last line from select name to select s.Name + "." + name which will give you a more complete name (e.g. Address.Name if the property that differs is the Name property of the Address member).

One point: Your method is not accounting for actual differences in the the EmployeeAddress properties. Test it and see.
emp2Address.AddressLine1 = emp1Address.AddressLine1;// "Gates Foundation";
emp2Address.AddressLine2 = emp1Address.AddressLine2;// "One Microsoft Way";
emp2Address.City = emp1Address.City;// "Redmond";
emp2Address.State = emp1Address.State;// "WA";
emp2Address.Zip = emp1Address.Zip;// "98052-6399";
The program will still return EmployeeAddress as a non-matching property. However, if you simply set emp2.EmployeeAddress = emp1Address, you don't get the "non-match."
Something something about references...
At any rate, if you want to find what's different about that object, you're going to have to search for what's different about that object.

Related

Get nested type properties using reflection

As part of a test I am trying to compare similar objects using reflection.
The objects may or may not have multiple levels of nested params.
For example:
public class Connection{
public string Ip{get; set}
public string Id{get; set}
public string Key{get; set}
public string Transport{get; set}
public Parameters ParametersObj = new Parameters();
public class Parameters
{
public string AssignedName { get; set; }
public string CategoryType { get; set; }
public string Status { get; set; }
}
};
This is just an example of a class, I need this method to deal with any type of object without knowing the number of depth level.
I am doing something like this after I have made sure the two objects are of the same type.
bool result = true;
foreach (var objParam in firstObj.GetType().GetProperties())
{
var value1 = objParam.GetValue(firstObj);
var value2 = objParam.GetValue(secondObj);
if (value1 == null || value2 == null || !value1.Equals(value2))
{
logger.Error("Property: " + objParam.Name);
logger.Error("Values: " + value1?.ToString() + " and " + value2?.ToString());
result = false;
}
}
return result;
It works perfectly for the first level of params but it ignores completely any nested objects. In this example I would like it to compare the values inside the parameters object and if they are different the log to print error "Property: Parameters.Status".
I would recommend to look into some tool which already does that (do not know one which does exactly that but FluentAssertions for example can handle object graph comparisons). But in the nutshell you can check if type is primitive or overrides Equals and call your method recursively. Something like the following:
bool Compare(object firstObj, object secondObj)
{
if (object.ReferenceEquals(firstObj, secondObj))
{
return true;
}
var type = firstObj.GetType();
var propertyInfos = firstObj.GetType().GetProperties();
foreach (var objParam in propertyInfos)
{
var methodInfo = objParam.PropertyType.GetMethod(nameof(object.Equals), BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, new []{typeof(object)});
var overridesEquals = methodInfo?.GetBaseDefinition().DeclaringType == typeof(object);
var value1 = objParam.GetValue(firstObj);
var value2 = objParam.GetValue(secondObj);
if (value1 == null || value2 == null)
{
// Log
return false;
}
if (object.ReferenceEquals(value1, value2))
{
continue;
}
if (type.IsPrimitive || overridesEquals)
{
if (value1.Equals(value2))
{
continue;
}
// Log?
return false;
}
if (!Compare(value1, value2))
{
// log ?
return false;
}
}
return true;
}
P.S.
Note that Connection.ParametersObj is not a property it is field so it will be ignored by both yours and mine implementations
Consider using source generators instead of reflection.
This does not handle collections.
The problem is that you only do one loop, without looking at the objects within each object. Here's a quick recursive function I threw together. (untested!)
// You can make it non-generic, this just ensures that both arguments are the same type.
static void Go<T>(T left, T right, Action<object, object, PropertyInfo> onFound, int depth = 2)
{
if (left is null)
return;
foreach (var p in left.GetType().GetProperties())
{
var l = p.GetValue(left);
var r = p.GetValue(right);
if (l is null || r is null || !l.Equals(r))
onFound(l, r, p);
if (depth is not 0)
Go(l, r, onFound, depth - 1);
}
}
Usage:
var arg1 = new Connection()
{
ParametersObj = new() { AssignedName = "foo" }
};
var arg2 = new Connection()
{
ParametersObj = new() { AssignedName = "bar" }
};
Go(arg1, arg2, Log); // goes 2 layers deep is you don't specify the last parameter
void Log(object l, object r, PropertyInfo p)
{
logger.Error($"Property: {p.Name}");
logger.Error($"Values: {l} and {r}");
}

C# How to search property name recursively

I need help to solve a problem, my problem is as follows, I have the following object
public class Teste
{
public string Descricao { get; set; }
public Time Time { get; set; }
}
.
public class Time
{
public string Nome { get; set; }
public Time (string nome)
{
Nome = nome;
}
}
I would like to be able to obtain the complete path of a certain property.
var teste = new Teste();
teste.Descricao = "bar";
teste.Time = new Time("foo");
var b = GetProperties(teste, "Nome");
//expected return: "Time.Nome"
I was testing something I arrived at the following method
public static IEnumerable<Tuple<string, string>> GetProperties(object obj, string propertyPath)
{
var objType = obj.GetType();
if (objType.IsValueType || objType.Equals(typeof(string)))
return Enumerable.Repeat(Tuple.Create(propertyPath, obj.ToString()), 1);
else
{
if (obj == null)
return Enumerable.Repeat(Tuple.Create(propertyPath, string.Empty), 1);
else
{
return from prop in objType.GetProperties()
where prop.CanRead && !prop.GetIndexParameters().Any()
let propValue = prop.GetValue(obj, null)
let propType = prop.PropertyType
from nameValPair in GetProperties(propValue, string.Format("{0}.{1}", propertyPath, prop.Name))
select nameValPair;
}
}
}
but it returns everything to me and I would like it to return a specific property.
I think there are some issues with searching properties that come from system modules. You have to decide which properties are worth recursively descending and which ones are not. Also, you'll have to maintain a list of objects that you have already visited to ensure that you do not follow cycles. I think a breadth-first search would be best, but for this example, I'll code a depth-first search. Also, I just return the first match, not all matches, you can adjust as needed. Furthermore, it returns a (mostly useless) string version of the path rather than a list of reflected properties that would be needed to actually access it (You'd have to do reflection again to locate the properties by name to retrieve the value from this "path" string.)
I'll start you off with a basic implementation. Likely someone else can improve upon it.
static string GetPropertyPath(object obj, string name, List<object> visited = null)
{
// does the object have the property?
Type t = obj.GetType();
var properties = t.GetProperties();
foreach (var property in properties) {
if (property.Name == name) {
// that's it!
return name;
}
}
// if we get here, it's because we didn't find the property.
if (visited == null) {
visited = new List<object>();
visited.Add(obj);
}
// Get all the properties of the first object and keep searching,
// keeping track of objects we've visited already.
foreach (var property in properties) {
// Limit which kinds of properties we search
if (object.ReferenceEquals(typeof(Program).Module, property.Module)) {
// get the value of the property
object obj2 = property.GetValue(obj);
// Do not search any previously visited objects
if (!visited.Any(o => object.ReferenceEquals(o, obj2))) {
visited.Add(obj2);
string path = GetPropertyPath(obj2, name, visited);
if (path != null) {
// found it!
return property.Name + "." + path;
}
}
}
}
return null;
}
Example
static void Main(string[] args)
{
var teste = new Teste();
teste.Descricao = "bar";
teste.Time = new Time("foo");
var b = GetPropertyPath(teste, "Nome"); // "Time.Nome"
}

Dynamic creation of an expression query for getting data from Entity Framework

I want to create a query of type Expression that gets some columns of an entity from Entity Framework.
Assume we have two classes like this:
public class Parent
{
public int Id { get; set; }
public string Name { get; set; }
public Child MyChild { get; set; }
}
public class Child
{
public int Id { get; set; }
public string Name { get; set; }
}
And we have an IQueryable list of Parent:
var q = new List<Parent>()
{
new Parent {Id = 1, Name = "a", Number = 1, MyChild=new Child{Id=11,Name="Child_a",Number=2}},
new Parent {Id = 2, Name = "b", Number = 1, MyChild=new Child{Id=22,Name="Child_b",Number=2}},
new Parent {Id = 3, Name = "c", Number = 1, MyChild=new Child{Id=33,Name="Child_c",Number=2}},
}.AsQueryable();
I want to get a list of those properties of q that user determines them. For example user determines that he needs Parent.Name and Parent.MyChils.Name. So I should give the user a list of anonymous type like this:
{"a","Child_a"}
{"b","Child_b"}
{"c","Child_c"}
If the Parent entity doesn't contain any foreign key property (in this example MyChild property) it is so easy to create an expression property that takes some properties of Parent dynamically. I have a code that gets some properties of Person without MyChild property of it:
var columns = new List<string> { "Id", "Name" };
var xParam = Expression.Parameter(typeof(Parent), "x");
var sourceProperties = columns.ToDictionary(name => name,
name => q.ElementType.GetProperty(name));
var dynamicType = LinqRuntimeTypeBuilder.GetDynamicType(sourceProperties.Values);
var bindings =
dynamicType.GetFields()
.Select(p => Expression.Bind(p, Expression.Property(xParam, sourceProperties[p.Name])))
.OfType<MemberBinding>();
var newExpr = Expression.New(dynamicType.GetConstructor(Type.EmptyTypes));
Expression selector = Expression.Lambda(Expression.MemberInit(
Expression.New(dynamicType.GetConstructor(Type.EmptyTypes)), bindings), xParam);
var body = Expression.MemberInit(newExpr, bindings);
var lambda = Expression.Lambda<Func<Parent, dynamic>>(body, xParam);
var t = q.Select(lambda);
(2 used methods are here:)
public static Type GetDynamicType2(Dictionary<string, Type> fields)
{
if (null == fields)
throw new ArgumentNullException("fields");
if (0 == fields.Count)
throw new ArgumentOutOfRangeException("fields", "fields must have at least 1 field definition");
try
{
Monitor.Enter(builtTypes);
string className = "MyDynamicType";
if (builtTypes.ContainsKey(className))
return builtTypes[className];
TypeBuilder typeBuilder = moduleBuilder.DefineType(className,
TypeAttributes.Public | TypeAttributes.Class | TypeAttributes.Serializable);
foreach (var field in fields)
typeBuilder.DefineField(field.Key, field.Value, FieldAttributes.Public);
builtTypes[className] = typeBuilder.CreateType();
return builtTypes[className];
}
catch (Exception ex)
{
}
finally
{
Monitor.Exit(builtTypes);
}
return null;
}
public static Type GetDynamicType(IEnumerable<PropertyInfo> fields)
{
return GetDynamicType(fields.ToDictionary(f => f.Name, f => f.PropertyType));
}
But still I can't get internal properties of MyChild property of Parent.
How to do that?
Since nobody answered my question I tried many different ways to solve it, upshot problem solved utilizing recursive creation of Expression.
For every property of type Class (like MyChild in the question) we most create an Expression. Creation of Expressions most be recursive like this:
private Expression BuildExpression(Expression parentExpression,
Type parentPropertyType, string pathOfChildProperty)
{
string remainPathOfChild;
var childPropertyName =
GetFirstPropertyNameFromPathAndRemainPath(pathOfChildProperty, out remainPathOfChild);
if (string.IsNullOrEmpty(childPropertyName)) return parentExpression;
var childPropInfo = parentPropertyType.GetProperty(childPropertyName);
var childExpression = Expression.Property(parentExpression, childPropInfo);
return !string.IsNullOrEmpty(remainPathOfChild)
? BuildExpressionForInternalProperty(childExpression, childPropInfo.PropertyType, remainPathOfChild)
: childExpression;
}
private string GetFirstPropertyNameFromPathAndRemainPath(string path, out string remainPath)
{
if (string.IsNullOrEmpty(path))
{
remainPath = null;
return null;
}
var indexOfDot = path.IndexOf('.');
if (indexOfDot < 0)
{
remainPath = null;
return path;
}
remainPath = path.Substring(indexOfDot + 1);
return path.Substring(0, indexOfDot);
}
}
Recursive call will stop when remain path of internal property be empty.
In highest level the calling of this method is like this:
var inputParameter = Expression.Parameter(typeof(GrandParent), "x");
var expChildProperty =
BuildExpression(inputParameter,typeof(Parent),"Parent.MyChils.Name");
Finally result expression for above problem is (($x.Parent).MyChils).Name in debug view.

Compare Properties automatically

I want to get the names of all properties that changed for matching objects. I have these (simplified) classes:
public enum PersonType { Student, Professor, Employee }
class Person {
public string Name { get; set; }
public PersonType Type { get; set; }
}
class Student : Person {
public string MatriculationNumber { get; set; }
}
class Subject {
public string Name { get; set; }
public int WeeklyHours { get; set; }
}
class Professor : Person {
public List<Subject> Subjects { get; set; }
}
Now I want to get the objects where the Property values differ:
List<Person> oldPersonList = ...
List<Person> newPersonList = ...
List<Difference> = GetDifferences(oldPersonList, newPersonList);
public List<Difference> GetDifferences(List<Person> oldP, List<Person> newP) {
//how to check the properties without casting and checking
//for each type and individual property??
//can this be done with Reflection even in Lists??
}
In the end I would like to have a list of Differences like this:
class Difference {
public List<string> ChangedProperties { get; set; }
public Person NewPerson { get; set; }
public Person OldPerson { get; set; }
}
The ChangedProperties should contain the name of the changed properties.
I've spent quite a while trying to write a faster reflection-based solution using typed delegates. But eventually I gave up and switched to Marc Gravell's Fast-Member library to achieve higher performance than with normal reflection.
Code:
internal class PropertyComparer
{
public static IEnumerable<Difference<T>> GetDifferences<T>(PropertyComparer pc,
IEnumerable<T> oldPersons,
IEnumerable<T> newPersons)
where T : Person
{
Dictionary<string, T> newPersonMap = newPersons.ToDictionary(p => p.Name, p => p);
foreach (T op in oldPersons)
{
// match items from the two lists by the 'Name' property
if (newPersonMap.ContainsKey(op.Name))
{
T np = newPersonMap[op.Name];
Difference<T> diff = pc.SearchDifferences(op, np);
if (diff != null)
{
yield return diff;
}
}
}
}
private Difference<T> SearchDifferences<T>(T obj1, T obj2)
{
CacheObject(obj1);
CacheObject(obj2);
return SimpleSearch(obj1, obj2);
}
private Difference<T> SimpleSearch<T>(T obj1, T obj2)
{
Difference<T> diff = new Difference<T>
{
ChangedProperties = new List<string>(),
OldPerson = obj1,
NewPerson = obj2
};
ObjectAccessor obj1Getter = ObjectAccessor.Create(obj1);
ObjectAccessor obj2Getter = ObjectAccessor.Create(obj2);
var propertyList = _propertyCache[obj1.GetType()];
// find the common properties if types differ
if (obj1.GetType() != obj2.GetType())
{
propertyList = propertyList.Intersect(_propertyCache[obj2.GetType()]).ToList();
}
foreach (string propName in propertyList)
{
// fetch the property value via the ObjectAccessor
if (!obj1Getter[propName].Equals(obj2Getter[propName]))
{
diff.ChangedProperties.Add(propName);
}
}
return diff.ChangedProperties.Count > 0 ? diff : null;
}
// cache for the expensive reflections calls
private Dictionary<Type, List<string>> _propertyCache = new Dictionary<Type, List<string>>();
private void CacheObject<T>(T obj)
{
if (!_propertyCache.ContainsKey(obj.GetType()))
{
_propertyCache[obj.GetType()] = new List<string>();
_propertyCache[obj.GetType()].AddRange(obj.GetType().GetProperties().Select(pi => pi.Name));
}
}
}
Usage:
PropertyComparer pc = new PropertyComparer();
var diffs = PropertyComparer.GetDifferences(pc, oldPersonList, newPersonList).ToList();
Performance:
My very biased measurements showed that this approach is about 4-6 times faster than the Json-Conversion and about 9 times faster than ordinary reflections. But in fairness, you could probably speed up the other solutions quite a bit.
Limitations:
At the moment my solution doesn't recurse over nested lists, for example it doesn't compare individual Subject items - it only detects that the subjects lists are different, but not what or where. However, it shouldn't be too hard to add this feature when you need it. The most difficult part would probably be to decide how to represent these differences in the Difference class.
We start with 2 simple methods:
public bool AreEqual(object leftValue, object rightValue)
{
var left = JsonConvert.SerializeObject(leftValue);
var right = JsonConvert.SerializeObject(rightValue);
return left == right;
}
public Difference<T> GetDifference<T>(T newItem, T oldItem)
{
var properties = typeof(T).GetProperties();
var propertyValues = properties
.Select(p => new {
p.Name,
LeftValue = p.GetValue(newItem),
RightValue = p.GetValue(oldItem)
});
var differences = propertyValues
.Where(p => !AreEqual(p.LeftValue, p.RightValue))
.Select(p => p.Name)
.ToList();
return new Difference<T>
{
ChangedProperties = differences,
NewItem = newItem,
OldItem = oldItem
};
}
AreEqual just compares the serialized versions of two objects using Json.Net, this keeps it from treating reference types and value types differently.
GetDifference checks the properties on the passed in objects and compares them individually.
To get a list of differences:
var oldPersonList = new List<Person> {
new Person { Name = "Bill" },
new Person { Name = "Bob" }
};
var newPersonList = new List<Person> {
new Person { Name = "Bill" },
new Person { Name = "Bobby" }
};
var diffList = oldPersonList.Zip(newPersonList, GetDifference)
.Where(d => d.ChangedProperties.Any())
.ToList();
Everyone always tries to get fancy and write these overly generic ways of extracting data. There is a cost to that.
Why not be old school simple.
Have a GetDifferences member function Person.
virtual List<String> GetDifferences(Person otherPerson){
var diffs = new List<string>();
if(this.X != otherPerson.X) diffs.add("X");
....
}
In inherited classes. Override and add their specific properties. AddRange the base function.
KISS - Keep it simple. It would take you 10 minutes of monkey work to write it, and you know it will be efficient and work.
I am doing it by using this:
//This structure represents the comparison of one member of an object to the corresponding member of another object.
public struct MemberComparison
{
public static PropertyInfo NullProperty = null; //used for ROOT properties - i dont know their name only that they are changed
public readonly MemberInfo Member; //Which member this Comparison compares
public readonly object Value1, Value2;//The values of each object's respective member
public MemberComparison(PropertyInfo member, object value1, object value2)
{
Member = member;
Value1 = value1;
Value2 = value2;
}
public override string ToString()
{
return Member.name+ ": " + Value1.ToString() + (Value1.Equals(Value2) ? " == " : " != ") + Value2.ToString();
}
}
//This method can be used to get a list of MemberComparison values that represent the fields and/or properties that differ between the two objects.
public static List<MemberComparison> ReflectiveCompare<T>(T x, T y)
{
List<MemberComparison> list = new List<MemberComparison>();//The list to be returned
if (x.GetType().IsArray)
{
Array xArray = x as Array;
Array yArray = y as Array;
if (xArray.Length != yArray.Length)
list.Add(new MemberComparison(MemberComparison.NullProperty, "array", "array"));
else
{
for (int i = 0; i < xArray.Length; i++)
{
var compare = ReflectiveCompare(xArray.GetValue(i), yArray.GetValue(i));
if (compare.Count > 0)
list.AddRange(compare);
}
}
}
else
{
foreach (PropertyInfo m in x.GetType().GetProperties())
//Only look at fields and properties.
//This could be changed to include methods, but you'd have to get values to pass to the methods you want to compare
if (!m.PropertyType.IsArray && (m.PropertyType == typeof(String) || m.PropertyType == typeof(double) || m.PropertyType == typeof(int) || m.PropertyType == typeof(uint) || m.PropertyType == typeof(float)))
{
var xValue = m.GetValue(x, null);
var yValue = m.GetValue(y, null);
if (!object.Equals(yValue, xValue))//Add a new comparison to the list if the value of the member defined on 'x' isn't equal to the value of the member defined on 'y'.
list.Add(new MemberComparison(m, yValue, xValue));
}
else if (m.PropertyType.IsArray)
{
Array xArray = m.GetValue(x, null) as Array;
Array yArray = m.GetValue(y, null) as Array;
if (xArray.Length != yArray.Length)
list.Add(new MemberComparison(m, "array", "array"));
else
{
for (int i = 0; i < xArray.Length; i++)
{
var compare = ReflectiveCompare(xArray.GetValue(i), yArray.GetValue(i));
if (compare.Count > 0)
list.AddRange(compare);
}
}
}
else if (m.PropertyType.IsClass)
{
var xValue = m.GetValue(x, null);
var yValue = m.GetValue(y, null);
if ((xValue == null || yValue == null) && !(yValue == null && xValue == null))
list.Add(new MemberComparison(m, xValue, yValue));
else if (!(xValue == null || yValue == null))
{
var compare = ReflectiveCompare(m.GetValue(x, null), m.GetValue(y, null));
if (compare.Count > 0)
list.AddRange(compare);
}
}
}
return list;
}
Here you have a code which does what you want with Reflection.
public List<Difference> GetDifferences(List<Person> oldP, List<Person> newP)
{
List<Difference> allDiffs = new List<Difference>();
foreach (Person oldPerson in oldP)
{
foreach (Person newPerson in newP)
{
Difference curDiff = GetDifferencesTwoPersons(oldPerson, newPerson);
allDiffs.Add(curDiff);
}
}
return allDiffs;
}
private Difference GetDifferencesTwoPersons(Person OldPerson, Person NewPerson)
{
MemberInfo[] members = typeof(Person).GetMembers();
Difference returnDiff = new Difference();
returnDiff.NewPerson = NewPerson;
returnDiff.OldPerson = OldPerson;
returnDiff.ChangedProperties = new List<string>();
foreach (MemberInfo member in members)
{
if (member.MemberType == MemberTypes.Property)
{
if (typeof(Person).GetProperty(member.Name).GetValue(NewPerson, null).ToString() != typeof(Person).GetProperty(member.Name).GetValue(OldPerson, null).ToString())
{
returnDiff.ChangedProperties.Add(member.Name);
}
}
}
return returnDiff;
}

Is there a way to search a List<T> using a partially populated object?

Would like to be able to populate any properties of an object and search a collection for objects that match the given properties.
class Program
{
static List<Marble> marbles = new List<Marble> {
new Marble {Color = "Red", Size = 3},
new Marble {Color = "Green", Size = 4},
new Marble {Color = "Black", Size = 6}
};
static void Main()
{
var search1 = new Marble { Color = "Green" };
var search2 = new Marble { Size = 6 };
var results = SearchMarbles(search1);
}
public static IEnumerable<Marble> SearchMarbles(Marble search)
{
var results = from marble in marbles
//where ???
//Search for marbles with whatever property matches the populated properties of the parameter
//In this example it would return just the 'Green' marble
select marble;
return results;
}
public class Marble
{
public string Color { get; set; }
public int Size { get; set; }
}
}
Admittedly, it is interesting and take me time. First, you need to get all properties of search object which have value different with default value, this method is generic using reflection:
var properties = typeof (Marble).GetProperties().Where(p =>
{
var pType = p.PropertyType;
var defaultValue = pType.IsValueType
? Activator.CreateInstance(pType) : null;
var recentValue = p.GetValue(search);
return !recentValue.Equals(defaultValue);
});
Then you can use LINQ All to filter:
var results = marbles.Where(m =>
properties.All(p =>
typeof (Marble).GetProperty(p.Name)
.GetValue(m) == p.GetValue(search)));
P.s: This code has been tested
I am going to propose the generic solution which will work with any number of properties and with any object. It will also be usable in Linq-To-Sql context - it will translate well to sql.
First, start by defining function which will test if the given value is to be treated as a non-set, e.g:
static public bool IsDefault(object o)
{
return o == null || o.GetType().IsValueType && Activator.CreateInstance(o.GetType()).Equals(o);
}
Then, we will have a function which constructs a Lambda expression with test against the values of all set properties in search object:
static public Expression<Func<T, bool>> GetComparison<T>(T search)
{
var param = Expression.Parameter(typeof(T), "t");
var props = from p in typeof(T).GetProperties()
where p.CanRead && !IsDefault(p.GetValue(search, null))
select Expression.Equal(
Expression.Property(param, p.Name),
Expression.Constant(p.GetValue(search, null))
);
var expr = props.Aggregate((a, b) => Expression.AndAlso(a, b));
var lambda = Expression.Lambda<Func<T, bool>>(expr, param);
return lambda;
}
We can use it on any IQueryable:
public static IEnumerable<Marble> SearchMarbles (Marble search)
{
var results = marbles.AsQueryable().Where(GetComparison(search));
return results.AsEnumerable();
}
You can use a separate Filter class like this:
class Filter
{
public string PropertyName { get; set; }
public object PropertyValue { get; set; }
public bool Matches(Marble m)
{
var T = typeof(Marble);
var prop = T.GetProperty(PropertyName);
var value = prop.GetValue(m);
return value.Equals(PropertyValue);
}
}
You can use this Filter as follows:
var filters = new List<Filter>();
filters.Add(new Filter() { PropertyName = "Color", PropertyValue = "Green" });
//this is essentially the content of SearchMarbles()
var result = marbles.Where(m => filters.All(f => f.Matches(m)));
foreach (var r in result)
{
Console.WriteLine(r.Color + ", " + r.Size);
}
You could use DependencyProperties to get rid of typing the property name.
Assuming a property is unpopulated if it has the default value (i.e. Color == null and Size == 0):
var results = from marble in marbles
where (marble.Color == search.Color || search.Color == null)
&& (marble.Size == search.Size || search.Size == 0)
select marble;
You could override equals in your Marbles class
public override bool Equals(object obj)
{
var other = obj as Marble;
if (null == other) return false;
return other.Color == this.color && other.size == this.size; // (etc for your other porperties
}
and then you could search by
return marbles.Where(m => search == m);
Using reflection, this method will work on all types, regardless of how many or what type of properties they contain.
Will skip any properties that are not filled out (null for ref type, default value for value type). If it finds two properties that are filled out that do not match returns false. If all filled-out properties are equal returns true.
IsPartialMatch(object m1, object m2)
{
PropertyInfo[] properties = m1.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
object v1 = property.GetValue(m1, null);
object v2 = property.GetValue(m2, null);
object defaultValue = GetDefault(property.PropertyType);
if (v1.Equals(defaultValue) continue;
if (v2.Equals(defaultVAlue) continue;
if (!v1.Equals(v2)) return false;
}
return true;
}
To apply it to your example
public static IEnumerable<Marble> SearchMarbles(Marble search)
{
return marbles.Where(m => IsPartialMatch(m, search))
}
GetDefault() is method from this post, Programmatic equivalent of default(Type)
If you want to avoid targeting specific properties, you could use reflection. Start by defining a function that returns the default value of a type (see here for a simple solution, and here for something more elaborate).
Then, you can write a method on the Marble class, which takes an instance of Marble as a filter:
public bool MatchesSearch(Marble search) {
var t = typeof(Marble);
return !(
from prp in t.GetProperties()
//get the value from the search instance
let searchValue = prp.GetValue(search, null)
//check if the search value differs from the default
where searchValue != GetDefaultValue(prp.PropertyType) &&
//and if it differs from the current instance
searchValue != prp.GetValue(this, null)
select prp
).Any();
}
Then, the SearchMarbles becomes:
public static IEnumerable<Marble> SearchMarbles(Marble search) {
return
from marble in marbles
where marble.MatchesSearch(search)
select marble;
}

Categories