Merging .net object graph
has anyone come across any scenario wherein you needed to merge one object with another object of same type, merging the complete object graph. for e.g. If i have a person object and one person object is having first name and other the last name, some way to merge both the objects into a single object.
public class Person
{
public Int32 Id { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
}
public class MyClass
{
//both instances refer to the same person, probably coming from different sources
Person obj1 = new Person(); obj1.Id=1; obj1.FirstName = "Tiju";
Person obj2 = new Person(); ojb2.Id=1; obj2.LastName = "John";
//some way of merging both the object
obj1.MergeObject(obj2); //??
//obj1.Id // = 1
//obj1.FirstName // = "Tiju"
//obj1.LastName // = "John"
}
I had come across such type of requirement and I wrote an extension method to do the same.
public static class ExtensionMethods
{
private const string Key = "Id";
public static IList MergeList(this IList source, IList target)
{
Dictionary itemData = new Dictionary();
//fill the dictionary for existing list
string temp = null;
foreach (object item in source)
{
temp = GetKeyOfRecord(item);
if (!String.IsNullOrEmpty(temp))
itemData[temp] = item;
}
//if the same id exists, merge the object, otherwise add to the existing list.
foreach (object item in target)
{
temp = GetKeyOfRecord(item);
if (!String.IsNullOrEmpty(temp) && itemData.ContainsKey(temp))
itemData[temp].MergeObject(item);
else
source.Add(item);
}
return source;
}
private static string GetKeyOfRecord(object o)
{
string keyValue = null;
Type pointType = o.GetType();
if (pointType != null)
foreach (PropertyInfo propertyItem in pointType.GetProperties())
{
if (propertyItem.Name == Key)
{ keyValue = (string)propertyItem.GetValue(o, null); }
}
return keyValue;
}
public static object MergeObject(this object source, object target)
{
if (source != null && target != null)
{
Type typeSource = source.GetType();
Type typeTarget = target.GetType();
//if both types are same, try to merge
if (typeSource != null && typeTarget != null && typeSource.FullName == typeTarget.FullName)
if (typeSource.IsClass && !typeSource.Namespace.Equals("System", StringComparison.InvariantCulture))
{
PropertyInfo[] propertyList = typeSource.GetProperties();
for (int index = 0; index < propertyList.Length; index++)
{
Type tempPropertySourceValueType = null;
object tempPropertySourceValue = null;
Type tempPropertyTargetValueType = null;
object tempPropertyTargetValue = null;
//get rid of indexers
if (propertyList[index].GetIndexParameters().Length == 0)
{
tempPropertySourceValue = propertyList[index].GetValue(source, null);
tempPropertyTargetValue = propertyList[index].GetValue(target, null);
}
if (tempPropertySourceValue != null)
tempPropertySourceValueType = tempPropertySourceValue.GetType();
if (tempPropertyTargetValue != null)
tempPropertyTargetValueType = tempPropertyTargetValue.GetType();
//if the property is a list
IList ilistSource = tempPropertySourceValue as IList;
IList ilistTarget = te开发者_如何学PythonmpPropertyTargetValue as IList;
if (ilistSource != null || ilistTarget != null)
{
if (ilistSource != null)
ilistSource.MergeList(ilistTarget);
else
propertyList[index].SetValue(source, ilistTarget, null);
}
//if the property is a Dto
else if (tempPropertySourceValue != null || tempPropertyTargetValue != null)
{
if (tempPropertySourceValue != null)
tempPropertySourceValue.MergeObject(tempPropertyTargetValue);
else
propertyList[index].SetValue(source, tempPropertyTargetValue, null);
}
}
}
}
return source;
}
}
However, this works when the source property is null, if target has it, it will copy that to source. IT can still be improved to merge when inconsistencies are there e.g. if FirstName="Tiju" and FirstName="John"
Any commments appreciated.
Thanks TJ
Your code looks very yucky. Are you sure you need such a generic model? Are you planning to merge more than Person objects alone? And if so, are the merge requirements exactly the same? If you need to merge other types, changes are that they have to be merged in a different way. Perhaps you should choose a design without reflection. Here is an other idea:
public interface IMergable<T>
{
T MergeWith(T other);
}
public interface IEntity
{
object EntityId { get; }
}
public class Person : IMergable<Person>, IEntity
{
public int Id { get; set; }
object IEntity.EntityId { get { return this.Id; } }
public Person MergeWith(Person other)
{
var mergedperson = new Person();
// Do merging here, and throw InvalidOperationException
// when objects can not be merged.
return mergedPerson;
}
}
Yur extension method would look something like this:
public static IEnumerable<T> MergeList<T>(this IEnumerable<T> left,
IEnumerable<T> right)
where T : IMergable<T>, IEntity
{
return
from leftEntity in left
from rightEntity in right
where leftEntity.EntityId.Equals(rightEntity.EntityId)
select leftEntity.MergeWith(rightEntity);
}
Nice approach Steven,
yes, i will be merging objects of the same type. Yes, i need a more generic model.
The reason being my architecture doesn't allow me inheritance relationships as these objects will be simple DTO's which are only lightweight objects just sent to the UI clint to bind to. so these kind of relationships is out of question.
This logic will be required at the client side and to be exact, my data will be coming from different sources, say one from system1, and another from system2, both will return same type object, with different values, i just want a way to merge both the objects so that the complete information can be shown to the client UI. There will not be any saves from UI back to the system. The code should be generic and should not contain any type specific merge logic. There is no problem when one property is null in one object and not in another. the problem arises when both will have different values of same properties.
for e.g. one source says person lives in USA and other says person lives in France. what to do in such scenario. although this is basically a question to the BA, but maybe some kind of credibility index for each source, mechanism may work.
If Type has a list of other Dto's, then the MergeList will not only merge the matching records, it will also add non matching records to the list.
any comments on making the code nicer :-)
Not a direct answer, but anonymous classes types are worth thinking of:
return new {
FirstName = "Peter",
LastName = "Pen"
};
For more information, this article nicely explains the feature; there's more on msdn and wikipedia.
To sum up:
- They can only inherit from object,
- their only members are private fields each with a matching read/write property.
精彩评论