This library offers methods returning a collection of objects containing information concerning any deltas, i.e., changes, on the properties of an object. The collection can be generated by providing two instances of an object, or an instance of an object and a JObject
containing entries keyed to properties on the object. This can be useful for creating audit logs or generating SQL. For example, if you have a PATCH
endpoint receiving an instance of an object and you want to generate SQL updating only the changed properties of the object, this library provides a list of objects--one for each changed property--containing information that can be used to create that SQL.
Compatible with the following:
- .NET Framework 4.5
- .NET Framework 4.6
- .NET Framework 4.6.1
- .NET Standard 1.6
- .NET Standard 2.0
This library contains an overloaded extension method GetDeltaObjects
off of T
that accepts either another instance of T
or a JObject
. In addition, the library offers a class called DeltaObjectEngine
implementing IDeltaObjectEngine
, which offers the overloaded method GetDeltaObjects
providing functionality identical to the extension method just described. Using the interface rather than the extension method offers the possibility of mocking the functionality in tests.
If an object of type T
is passed as the final argument, the return type is a List<DeltaObject>
. If an object of type JObject
is passed as the final argument, the return type is a DeltaGroup
.
A DeltaObject
has the following public properties:
PropertyName
The name of the property.PropertyAlias
The alias of the property. This defaults to the property's name, and can be set using theDeltaObjectAlias
attribute, discussed below.OriginalValue
The original value of the property.NewValue
The new value of the property.StringifiedOriginalValue
The original value of the property as astring
.StringifiedNewValue
The new value of the property as astring
.ValueConversionStatus
The status of the conversion of the new value of the property into the property's type. This is anenum
with fields ofSuccess
andFail
. Note that this property is only relevant when aDeltaObject
is calculated using aJObject
, since it is only in that situation where it is possible that a new value could not be converted into the property's type.
A DeltaObject
is generated only for non-indexed properties of the following types:
- primitives
decimal
string
DateTime
DateTimeOffset
TimeSpan
Guid
- nullables, e.g.,
int?
,DateTime?
- enums
Any property on an object not among the above types will be ignored by the delta-object generator. In addition, you may add attributes to properties or to a class to have the delta-object generator ignore certain properties in certain situations, discussed below in the attributes section.
A DeltaGroup
has the following properties:
ValueConversionStatus
The status of the conversions of the new values of the properties into their associated types. This is anenum
with fields ofNoneFailed
,SomeFailed
, andAllFailed
. The value isNoneFailed
when no values failed to be converted or when there are no deltas. The value isSomeFailed
when some values failed to be converted and some conversions succeeded. The value isAllFailed
when all values failed to be converted.DeltaObjects
AList<DeltaObject>
whereValueConversionStatus
on eachDeltaObject
isSuccess
.DeltaObjectsValueConversionFail
AList<DeltaObject>
whereValueConversionStatus
on eachDeltaObject
isFail
. When the value ofValueConversionStatus
is anything other thanNoneFailed
, thenDeltaObjectsValueConversionFail
will not be empty.
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Transactions { get; set; }
public DateTime DateOfBirth { get; set; }
}
var originalCustomer = new Customer
{
FirstName = "originalFirstName",
LastName = "originalLastName",
Transactions = 30,
DateOfBirth = new DateTime(1919, 10, 10)
};
var updatedCustomer = new Customer
{
FirstName = "newFirstName",
LastName = "originalLastName",
Transactions = 95,
DateOfBirth = new DateTime(2009, 2, 3)
};
var deltaObjects = originalCustomer.GetDeltaObjects(updatedCustomer);
foreach (var deltaObject in deltaObjects)
{
Console.WriteLine(
$"Property name: {deltaObject.PropertyName}\n" +
$"Property alias: {deltaObject.PropertyAlias}\n" +
$"Original value: {deltaObject.OriginalValue}\n" +
$"New value: {deltaObject.NewValue}\n\n" +
$"********************************************\n");
}
The above code will print the following to the console:
Property name: FirstName
Property alias: FirstName
Original value: originalFirstName
New value: newFirstName
********************************************
Property name: Transactions
Property alias: Transactions
Original value: 30
New value: 95
********************************************
Property name: DateOfBirth
Property alias: DateOfBirth
Original value: 10/10/1919 12:00:00 AM
New value: 2/3/2009 12:00:00 AM
********************************************
As you can see, a DeltaObject
was generated for each property on Customer
that had a changed value.
public class Customer
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Transactions { get; set; }
public DateTime DateOfBirth { get; set; }
}
var originalCustomer = new Customer
{
FirstName = "originalFirstName",
LastName = "originalLastName",
Transactions = 30,
DateOfBirth = new DateTime(1919, 10, 10)
};
var newCustomer = new
{
FirstName = "newFirstName",
LastName = "originalLastName",
Transactions = "fifty",
DateOfBirth = "December 8, 1979"
};
var newCustomerJObject = JObject.FromObject(newCustomer);
var customerDeltaGroup = originalCustomer.GetDeltaObjects(newCustomerJObject);
Console.WriteLine($"Group value conversion status: {customerDeltaGroup.ValueConversionStatus}\n" +
$"********************************************\n");
foreach (var deltaObject in customerDeltaGroup.DeltaObjects)
{
Console.WriteLine(
$"Property name: {deltaObject.PropertyName}\n" +
$"Property alias: {deltaObject.PropertyAlias}\n" +
$"Original value: {deltaObject.OriginalValue}\n" +
$"New value: {deltaObject.NewValue}\n" +
$"Value conversion status: {deltaObject.ConversionStatus}\n\n" +
$"********************************************\n");
}
foreach (var deltaObject in customerDeltaGroup.DeltaObjectsValueConversionFail)
{
Console.WriteLine(
$"Property name: {deltaObject.PropertyName}\n" +
$"Property alias: {deltaObject.PropertyAlias}\n" +
$"Original value: {deltaObject.OriginalValue}\n" +
$"New value: {deltaObject.NewValue}\n" +
$"Value conversion status: {deltaObject.ConversionStatus}\n\n" +
$"********************************************\n");
}
The above code will print the following to the console:
Group value conversion status: SomeFailed
********************************************
Property name: FirstName
Property alias: FirstName
Original value: originalFirstName
New value: newFirstName
Value conversion status: Success
********************************************
Property name: DateOfBirth
Property alias: DateOfBirth
Original value: 10/10/1919 12:00:00 AM
New value: 12/8/1979 12:00:00 AM
Value conversion status: Success
********************************************
Property name: Transactions
Property alias: Transactions
Original value: 30
New value: fifty
Value conversion status: Fail
********************************************
Notice that the conversion status of the new value for the Transactions
property is Fail
because the string
"fifty" cannot be converted into an int
.
Properties on an object are associated with JObject
properties without regard to casing. Therefore, the above example would produce the same result if the property on the JObject
were firstname
rather than FirstName
.
This attribute can be applied to a property. It assigns a value to the PropertyAlias
property of the DeltaObject
. For example:
public class Customer
{
public string FirstName { get; set; }
[DeltaObjectAlias("last_name")]
public string LastName { get; set; }
}
var originalCustomer = new Customer
{
FirstName = "originalFirstName",
LastName = "originalLastName"
};
var newCustomerWithAlias = new Customer
{
FirstName = "newFirstName",
LastName = "newLastName"
};
var deltaObjects = originalCustomer.GetDeltaObject(newCustomerWithAlias);
foreach (var deltaObject in deltaObjects)
{
Console.WriteLine(
$"Property name: {deltaObject.PropertyName}\n" +
$"Property alias: {deltaObject.PropertyAlias}\n" +
$"Original value: {deltaObject.OriginalValue}\n" +
$"New value: {deltaObject.NewValue}\n\n" +
$"********************************************\n");
}
The above code will print the following to the console:
Property name: FirstName
Property alias: FirstName
Original value: originalFirstName
New value: newFirstName
********************************************
Property name: LastName
Property alias: last_name
Original value: originalLastName
New value: newLastName
********************************************
As you can see, the LastName
property has an alias of last_name
in its DeltaObject
, and any property without a specified alias will have an alias on its DeltaObject
equal to the name of the property. This can be useful if you want to generate SQL and the property name differs from the database column.
This attribute can be applied to a property to force the delta-object generator to ignore that property in all cases.
This attribute can be applied to a property to force the delta-object generator to ignore that property when its value is equal to the property type's default value. This attribute can also be applied to a class, which will force the delta-object generator to ignore all properties on the class whose value is default.
The delta-object generator uses reflection to get information about a type and, for the sake of performance, caches the results in a static class. Below are some performance measurements observed on an i7-6700 4.00GHz CPU without running operations in parallel (note that the "Properties on Object" column counts only those properties that the delta-object generator does not ignore):
Number of Objects | Properties on Object | Seconds to Generate Delta Objects |
---|---|---|
1000 | 4 | 0.011 |
1 million | 4 | 3.150 |
1 million | 20 | 11.857 |