Introduction
Often times, we want to update an object with properties from another object, whether or not the object types are the same. Here is a quick, tested, low footprint project that allows you to update object properties with only those that differ between the source and the destination, without loading a giant one size fits all framework. In addition, the code allows you to specify whether default values are considered, and omission of properties.
Background
This very small project was devised while I was developing on a Web API project, allowing me to pass in partial objects, dynamics, and such in controllers.
Using the Code
A simple example would have us make two classes, we will call them TestClassOne
and TestClassTwo
. We will then call the IntelligentModelUpdater.ApplyOnlyChangedProperties
method with the two instances we created. Keep in mind that the two classes share nothing in common in regard to interfaces or base types, also keep in mind that this is completely irrelevant in every case, the code ignores class signatures.
C#
class Program
{
static void Main(string[] args)
{
TestClassOne objOne = new TestClassOne()
{
Age = 28,
Name = "Jennifer"
};
TestClassTwo objTwo = new TestClassTwo()
{
Age = 34,
Name = "Jennifer"
};
IntelligentModelUpdater.ApplyOnlyChangedProperties(objOne, objTwo);
Console.WriteLine(string.Format("objOne and
objTwo had different age values, objTwo should now have an age of {0} it is now {1}",
objOne.Age, objTwo.Age));
Console.ReadLine();
}
}
class TestClassOne
{
public string Name { get; set; }
public int Age { get; set; }
}
class TestClassTwo
{
public string Name { get; set; }
public int Age { get; set; }
}
VB.NET
Module Main
Sub Main()
Dim objOne As New TestClassOne() With {.Age = 28, .Name = "Jennifer"}
Dim objTwo As New TestClassTwo() With {.Age = 34, .Name = "Jennifer"}
IntelligentModelUpdater.ApplyOnlyChangedProperties(objOne, objTwo)
Console.WriteLine(String.Format("objOne and objTwo had different age values, _
objTwo should now have an age of {0} it is now {1}", objOne.Age, objTwo.Age))
Console.ReadLine()
End Sub
Class TestClassOne
Public Property Name() As String
Get
Return m_Name
End Get
Set(value As String)
m_Name = value
End Set
End Property
Private m_Name As String
Public Property Age() As Integer
Get
Return m_Age
End Get
Set(value As Integer)
m_Age = value
End Set
End Property
Private m_Age As Integer
End Class
Class TestClassTwo
Public Property Name() As String
Get
Return m_Name
End Get
Set(value As String)
m_Name = value
End Set
End Property
Private m_Name As String
Public Property Age() As Integer
Get
Return m_Age
End Get
Set(value As Integer)
m_Age = value
End Set
End Property
Private m_Age As Integer
End Class
End Module
Running the prior code produces the following output:
Quote:
objOne and objTwo had different age values, objTwo should now have an age of 28 it is now 28
We can also alternatively replace objOne
with a dynamic object, that specifies an Age
property. Our program output would be the same, and a quick debug step through would indicate that the Name
property on objTwo
remains to be "Jennifer
" since by default the code does not copy over default values.
C#
dynamic objOne = new { Age = 28 };
TestClassTwo objTwo = new TestClassTwo()
{
Age = 34,
Name = "Jennifer"
};
VB.NET
Dim objOne = New With {.Age = 28}
Dim objTwo As New TestClassTwo() With {.Age = 34, .Name = "Jennifer"}
So with those examples, you get the gest of the feature here. The IntelligentModelUpdater static
class (Module in VB) contains the sole method plus overloads to perform this operation. It is not feature rich but I have included the ability to omit unwanted properties, whether or not to perform recursive changes when complex property types are involved, and whether or not to update the destination object with properties whose values are default.
Let's take a look at an example that uses recursive property comparison and property omission.
C#
static void Main(string[] args)
{
Person objOne = new Person()
{
Age = 21,
Name = "Tyler",
Friends = new List<string>() { "Bill", "Tim", "Rick" },
OwnedVehicle = new Car()
{
Color = "Blue",
MilesDriven = 2000
}
};
Person objTwo = new Person()
{
Age = 34,
Name = "Ashley",
Friends = new List<string>() { "Sarah", "Tyler", "Stephanie" },
OwnedVehicle = new Car()
{
Color = "Light Brown",
MilesDriven = 100000
}
};
IntelligentModelUpdater.ApplyOnlyChangedProperties(objOne, objTwo, (x) => x.Age, (x) => x.Name);
Console.WriteLine(string.Format("Assert that objOne and
objTwo have different ages. {0}", objOne.Age != objTwo.Age));
Console.WriteLine(string.Format("Assert that objOne and
objTwo have different names. {0}", objOne.Name != objTwo.Name));
Console.WriteLine(string.Format("Assert that objOne and objTwo share the same friends. {0}",
objOne.Friends.Equals(objTwo.Friends)));
Console.WriteLine(string.Format("Assert that objOne and
objTwo's OwnedVehicle complex property share the same properties. {0}",
objOne.OwnedVehicle.Color == objTwo.OwnedVehicle.Color
&& objOne.OwnedVehicle.MilesDriven == objTwo.OwnedVehicle.MilesDriven));
Console.ReadLine();
}
VB.NET
Sub Main(args As String())
Dim objOne As New Person() With { _
.Age = 21, _
.Name = "Tyler", _
.Friends = New List(Of String)() From { _
"Bill", _
"Tim", _
"Rick" _
}, _
.OwnedVehicle = New Car() With { _
.Color = "Blue", _
.MilesDriven = 2000 _
} _
}
Dim objTwo As New Person() With { _
.Age = 34, _
.Name = "Ashley", _
.Friends = New List(Of String)() From { _
"Sarah", _
"Tyler", _
"Stephanie" _
}, _
.OwnedVehicle = New Car() With { _
.Color = "Light Brown", _
.MilesDriven = 100000 _
} _
}
IntelligentModelUpdater.ApplyOnlyChangedProperties_
(objOne, objTwo, Function(x) x.Age, Function(x) x.Name)
Console.WriteLine(String.Format("Assert that objOne and _
objTwo have different ages. {0}", objOne.Age <> objTwo.Age))
Console.WriteLine(String.Format("Assert that objOne and _
objTwo have different names. {0}", objOne.Name <> objTwo.Name))
Console.WriteLine(String.Format("Assert that objOne and _
objTwo share the same friends. {0}", objOne.Friends.Equals(objTwo.Friends)))
Console.WriteLine(String.Format("Assert that objOne and _
objTwo's OwnedVehicle complex property share the same properties. {0}", _
objOne.OwnedVehicle.Color = objTwo.OwnedVehicle.Color _
AndAlso objOne.OwnedVehicle.MilesDriven = objTwo.OwnedVehicle.MilesDriven))
Console.ReadLine()
End Sub
The prior code produces the following output:
Quote:
Assert that objOne and objTwo have different ages. True
Assert that objOne and objTwo have different names. True
Assert that objOne and objTwo share the same friends. True
Assert that objOne and objTwo's OwnedVehicle complex property share the same properties. True
An example showing the default behavior when handling default values.
C#
class Program
{
static void Main(string[] args)
{
Task taskOne = new Task()
{
WasComplete = false,
Employee = "Bridget",
Manager = null,
TaskCompletedComments = ""
};
Task taskTwo = new Task()
{
WasComplete = true,
Manager = "Dan",
TaskSize = 10,
TaskCompletedComments = "Task completed but not reviewed."
};
IntelligentModelUpdater.ApplyOnlyChangedProperties(taskOne, taskTwo);
Console.WriteLine("Assert that taskTwo.WasCompleted is false. {0}", taskTwo.WasComplete);
Console.WriteLine("Assert that taskTwo.Employee is now the value 'Bridget'. {0}",
taskTwo.Employee == "Bridget");
Console.WriteLine("Assert that taskTwo.Manager is still
'Dan'. {0}", taskTwo.Manager == "Dan");
Console.WriteLine("Assert that taskTwo.TaskSize remained
at the value of '10'. {0}", taskTwo.TaskSize == 10);
Console.WriteLine("Assert that taskTow.TaskCompletedComments
remained at value 'Task completed but not reviewed.' {0}",
taskTwo.TaskCompletedComments == "Task completed but not reviewed.");
Console.ReadLine();
}
}
class Task
{
public bool WasComplete { get; set; }
public string Employee { get; set; }
public string Manager { get; set; }
[DefaultValue("")]
public string TaskCompletedComments { get; set; }
public int TaskSize { get; set; }
}
VB.NET
Module Main
Sub Main(args As String())
Dim taskOne As New Task() With { _
.WasComplete = False, _
.Employee = "Bridget", _
.Manager = Nothing, _
.TaskCompletedComments = "" _
}
Dim taskTwo As New Task() With { _
.WasComplete = True, _
.Manager = "Dan", _
.TaskSize = 10, _
.TaskCompletedComments = "Task completed but not reviewed." _
}
IntelligentModelUpdater.ApplyOnlyChangedProperties(taskOne, taskTwo)
Console.WriteLine("Assert that taskTwo.WasCompleted is false. _
{0}", taskTwo.WasComplete)
Console.WriteLine("Assert that taskTwo.Employee is now the value _
'Bridget'. {0}", taskTwo.Employee = "Bridget")
Console.WriteLine("Assert that taskTwo.Manager is still _
'Dan'. {0}", taskTwo.Manager = "Dan")
Console.WriteLine("Assert that taskTwo.TaskSize remained at _
the value of '10'. {0}", taskTwo.TaskSize = 10)
Console.WriteLine("Assert that taskTow.TaskCompletedComments remained _
at value 'Task completed but not reviewed.' {0}", _
taskTwo.TaskCompletedComments = "Task completed but not reviewed.")
Console.ReadLine()
End Sub
Class Task
Public Property WasComplete() As Boolean
Get
Return m_WasComplete
End Get
Set(value As Boolean)
m_WasComplete = value
End Set
End Property
Private m_WasComplete As Boolean
Public Property Employee() As String
Get
Return m_Employee
End Get
Set(value As String)
m_Employee = value
End Set
End Property
Private m_Employee As String
Public Property Manager() As String
Get
Return m_Manager
End Get
Set(value As String)
m_Manager = value
End Set
End Property
Private m_Manager As String
<DefaultValue("")> _
Public Property TaskCompletedComments() As String
Get
Return m_TaskCompletedComments
End Get
Set(value As String)
m_TaskCompletedComments = value
End Set
End Property
Private m_TaskCompletedComments As String
Public Property TaskSize() As Integer
Get
Return m_TaskSize
End Get
Set(value As Integer)
m_TaskSize = value
End Set
End Property
Private m_TaskSize As Integer
End Class
End Module
The prior code produces the following output:
Quote:
Assert that taskTwo.WasCompleted is false. True
Assert that taskTwo.Employee is now the value 'Bridget'. True
Assert that taskTwo.Manager is still 'Dan'. True
Assert that taskTwo.TaskSize remained at the value of '10'. True
Assert that taskTow.TaskCompletedComments remained at value 'Task completed but not reviewed.' True
Behavior Highlights
I've designed the code to fit the most IDEAL behavior for a given case. Should a behavior not be ideal for your purposes, take a dive in the source.
- The destination object and source object need not have the same properties. However, there is an argument in the base method which when
true
will indicate to throw an exception should a source property not be present on the destination, but not the other way around. - Bools (
bool
in C#, boolean
in VB) are not checked for default value when doing default value comparisons. This is because of the fundamental nature of the bool
type. - When operating, the code does reflection everytime on both types passed, no type information is cached.
- During the recursive operation, the following types are ignored for property searches: Value Types, Arrays, and anything that implements
IEnumerable
or IEnumerator
. When these types are encountered, they are compared with object.Equals
. - Regarding the last bullet: If you pass in any of the aforementioned types in the method call directly, the code attempts a property scan no matter the object type.
- Omitted properties are always case sensitive if passed using the overload that accepts them as a
string
param array, this is in regards to VB.NET.
File Downloads
The included files are project files for both the VB version and C# version. The source files contain the last example and source for the IntelligentModelUpdater
and utility class that is used to retrieve default values during runtime. The projects are currently configured to .NET Framework version 4.5.1.