Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VB

Updating Business Models With Only Differing Property Values

5.00/5 (4 votes)
27 Mar 2016CPOL4 min read 10.8K   97  
An automated approach to updating business models intelligently, by updating a destination model with a source model only on properties whose values differ.

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#

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);

        //assert our changes
        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

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)

        'assert our changes
        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#

C#
dynamic objOne = new { Age = 28 };

TestClassTwo objTwo = new TestClassTwo()
{
    Age = 34,
    Name = "Jennifer"
};

VB.NET

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#

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
        }
    };

    //update objTwo with objOne, but ignore the Age and Name properties.
    IntelligentModelUpdater.ApplyOnlyChangedProperties(objOne, objTwo, (x) => x.Age, (x) => x.Name);

    //assert our changes
    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

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 _
              } _
          }

          'update objTwo with objOne, but ignore the Age and Name properties.
          IntelligentModelUpdater.ApplyOnlyChangedProperties_
          (objOne, objTwo, Function(x) x.Age, Function(x) x.Name)

          'assert our changes
          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#

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);

          //assert our changes
          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

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)

           'assert our changes
           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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)