Compare Properties of classes to identify which have changed - c#

In my web application, I want to notify user when certain things are changed through UI. For example my Project class looks like this
public class Project
{
public string Name { get; set; }
public TaskStatus Status { get; set; }
public string Planner { get; set; }
public DateTime ScheduleStart { get; set; }
public DateTime ScheduleEnd { get; set; }
public double EstimatedCost { get; set; }
public double ActualCost { get; set; }
public string AssignedTo { get; set; }
}
Now I have this information shown up on UI and a particular user having rights to change certain things (e.g. Status, schedule, cost etc.) can change this information. So what I want is that when something is changed by a user, then Emails should be sent to notify Project Manager lets say or anyone interested.
I have all other required code written to send emails and manage rights etc. Now I want to specifically see exactly what things changed for example If only Planner changed, or status changed then email should contain new and old value like TFS generates notifications.
P.S: Above code shows a very simple version of my Project class, actual class has more than 30 attributes. So I was thinking that instead of making comparison of each individual property there should be an easier and generic way that tells me which properties have changed, so that I can notify based on them.

A simple solution based on reflection. Note that it could be optimized, and it doesn't support (at this time) comparing inner collections/objects. The compared object must be POD (Plain Old Data)
public class Project
{
public string Name { get; set; }
public TaskStatus Status { get; set; }
public string Planner { get; set; }
public DateTime ScheduleStart { get; set; }
public DateTime ScheduleEnd { get; set; }
public double EstimatedCost { get; set; }
public double ActualCost { get; set; }
public string AssignedTo { get; set; }
public Project Clone()
{
// If your object has inner collections, or
// references to other objects, you'll have to deep
// clone them ***manually***!!!
return (Project)MemberwiseClone();
}
}
public static class SimpleComparer
{
// Item1: property name, Item2 current, Item3 original
public static List<Tuple<string, object, object>> Differences<T>(T current, T original)
{
var diffs = new List<Tuple<string, object, object>>();
MethodInfo areEqualMethod = typeof(SimpleComparer).GetMethod("AreEqual", BindingFlags.Static | BindingFlags.NonPublic);
foreach (PropertyInfo prop in typeof(T).GetProperties(BindingFlags.Instance | BindingFlags.Public))
{
object x = prop.GetValue(current);
object y = prop.GetValue(original);
bool areEqual = (bool)areEqualMethod.MakeGenericMethod(prop.PropertyType).Invoke(null, new object[] { x, y });
if (!areEqual)
{
diffs.Add(Tuple.Create(prop.Name, x, y));
}
}
return diffs;
}
private static bool AreEqual<T>(T x, T y)
{
return EqualityComparer<T>.Default.Equals(x, y);
}
}
Now, you'll need a Clone() method:
public class Project
{
public string Name { get; set; }
public TaskStatus Status { get; set; }
public string Planner { get; set; }
public DateTime ScheduleStart { get; set; }
public DateTime ScheduleEnd { get; set; }
public double EstimatedCost { get; set; }
public double ActualCost { get; set; }
public string AssignedTo { get; set; }
public Project Clone()
{
// If your object has inner collections, you'll have to deep
// clone them ***manually***!!!
return (Project)MemberwiseClone();
}
}
and then...
var current = new Project();
var original = current.Clone();
current.ActualCost = 10000;
var diffs = SimpleComparer.Differences(current, original);
foreach (var diff in diffs)
{
Console.WriteLine("'{0}' changed from {1} to {2}", diff.Item1, diff.Item3, diff.Item2);
}

I'm assuming that you are referring to property values and not the actual class' properties. To be able to compare which property values have changed, there has to be two versions of the object, say old and updated. I would suggest implementing the IEquatable interface, this comes handy if you have complex objects which in your case a nested class TaskStatus which also have properties of its own that you need to compare. You can also let TaskStatus or other nested classes implement the IEquatable interface such that you don't have to worry about comparing their property values giving you the advantage of just doing a single call to Project's Equals() method. You can have the logic for getting the changes inside the overriden Equals() method.
If you don't want to hardcode each property for comparison, a little reflection would do. :)
public class Project : IEquatable<Project>
{
public string Name { get; set; }
public TaskStatus Status { get; set; }
public string Planner { get; set; }
public DateTime ScheduleStart { get; set; }
public DateTime ScheduleEnd { get; set; }
public double EstimatedCost { get; set; }
public double ActualCost { get; set; }
public string AssignedTo { get; set; }
public bool Equals(Project other)
{
bool flag = true;
if (this.Name != other.Name)
//--Do something
flag = false;
//TaskStatus otherTaskStatus = other.Status;
//flag = other.Status.Equals(otherTaskStatus);//compare nested classes here
return flag;
}
}
public class TaskStatus : IEquatable<TaskStatus>
{
public bool Equals(TaskStatus other)
{
throw new NotImplementedException();
}
}

You can use a class as follows which will store the old and new values of a property each time the property is changed even after updating it on the UI and then the object can be used to retrieve both the values. All you need to do is to create an instance of this class under set method of each property. You can also check whether the value is changed or not before creating the object.
public class PropertyChangingEventArgs : EventArgs
{
public PropertyChangingEventArgs()
{
}
public PropertyChangingEventArgs(string propName, object oldValue, object newValue)
{
PropertyName = propName;
OldValue = oldValue;
NewValue = newValue;
}
public string PropertyName { get; set; }
public object OldValue { get; set; }
public object NewValue { get; set; }
}
On property side, you can do this:
private string family;
public string Family
{
get { return family; }
set
{
if (family != value)
{
PropertyChangingEventArgs e = new PropertyChangingEventArgs("Family", family, value);
OnPropertyChanging(e);
family = value;
OnPropertyChanged("Family");
}
}
}
After updating, you can check which all properties have changed (or you can keep populating a list of changed properties each time a property is changed) and mail the list with old and new values.

Take a look at PropertyChangedEventHandler. I think it should do the trick if I am understanding your question correctly.
https://msdn.microsoft.com/en-us/library/system.componentmodel.inotifypropertychanged.propertychanged(v=vs.110).aspx?cs-save-lang=1&cs-lang=csharp#code-snippet-1

Related

Generate unique string key based on properties of object (cachekey)

I am simply trying to generate a unique cachekey that takes in the object type and property values
GetHashCode returns different results each time so that wont work, so I have to implement a solution but it has to be fast (I can go through the properties and and concat their values to a string but this might be slow and not the best way to go about it)
Nice To Have:
so if 2 different object types have the exact same properties and same values but they are different classes, they should be different cachekeys (chances of this happening are very slim but just in case)
Here is my code
public interface ICachableRequest
{
string GetCacheKey();
}
public class Object1 : ICachableRequest
{
public int IntValue1 { get; set; }
public double DoubleVal1 { get; set; }
public string StringVal1 { get; set; }
public string GetCacheKey()
{
throw new NotImplementedException();
}
}
public class Object2 : ICachableRequest
{
public int SomeIntValue1 { get; set; }
public double SomeOtherDoubleVal1 { get; set; }
public string MoreStringVal1 { get; set; }
public string MoreStringVal2 { get; set; }
public string MoreStringVal3 { get; set; }
public string MoreStringVal4 { get; set; }
public string GetCacheKey()
{
throw new NotImplementedException();
}
}

.net core C# use dynamic property name on EF Core Database first generated model class

I have a user details class
public partial class UserDetails
{
public int? Level { get; set; }
public string Unit { get; set; }
public string Bio { get; set; }
public bool? Gender { get; set; }
public int? Mobile { get; set; }
public string Photo { get; set; }
}
I am writing an update method:
public bool UpdateDetails(string userId, UserProperties updateProperty, string value)
{
switch(updateProperty)
{
case UserProperties.Unit:
details.Unit = value;
break;
case UserProperties.Photo:
details.Photo = value;
break;
default:
throw new Exception("Unknown User Detail property");
}
May I do something like dynamic property in JavaScript?
e.g.
var details = new UserDetails();
details["Unit"] = value;
Update
As of year 2019! How about try to use this new feature?!
DynamicObject
DynamicObject.TrySetMember(SetMemberBinder, Object) Method
I am trying to figure out how to write it.
You can do it via reflection for properties that exist on the object.
C# has a feature called Indexers. You could extend your code like this to allow for the behavior you are expecting.
public partial class UserDetails
{
public int? Level { get; set; }
public string Unit { get; set; }
public string Bio { get; set; }
public bool? Gender { get; set; }
public int? Mobile { get; set; }
public string Photo { get; set; }
// Define the indexer to allow client code to use [] notation.
public object this[string propertyName]
{
get {
PropertyInfo prop = this.GetType().GetProperty(propertyName);
return prop.GetValue(this);
}
set {
PropertyInfo prop = this.GetType().GetProperty(propertyName);
prop.SetValue(this, value);
}
}
}
Other than that, if you don't know the properties at runtime, you can use the dynamic type.
If you don't want to use reflection you can slightly tweak Alens solution to use dictionary to store data.
public class UserDetails
{
private Dictionary<string, object> Items { get; } = new Dictionary<string, object>();
public object this[string propertyName]
{
get => Items.TryGetValue(propertyName, out object obj) ? obj : null;
set => Items[propertyName] = value;
}
public int? Level
{
get => (int?)this["Level"];
set => this["Level"] = value;
}
}
The closest thing would be the ExpandoObject:
https://learn.microsoft.com/en-us/dotnet/api/system.dynamic.expandoobject?view=netframework-4.8
For example:
dynamic sampleObject = new ExpandoObject();
sampleObject.test = "Dynamic Property";
Console.WriteLine(sampleObject.test);

How do you handle NullReferences in EF when creating a new entity in runtime?

I have the following example schema:
public class CounterReading
{
public int CounterReadingId { get; set; }
public virtual Counter Counter { get; set; }
public DateTime Date { get; set; }
public decimal Reading { get; set; }
public CounterReading()
{
Date = DateTime.Now;
}
}
public class Counter
{
[Key, ForeignKey("Meter")]
public int CounterId { get; set; }
public virtual Meter Meter { get; set; }
public virtual ObservableCollection<CounterReading> Readings { get; set; }
[NotMapped]
public CounterReading CurrentReading
{
get
{
if(Readings.Count > 0)
{
return Readings.MaxBy(m => m.Reading);
}
return null;
}
}
}
public abstract class Meter
{
public int MeterId { get; set; }
public string EANNumber { get; set; }
public string MeterNumber { get; set; }
public virtual Premise Premise { get; set; }
}
public class WaterMeter : Meter
{
public virtual Counter Counter { get; set; }
public WaterMeter()
{
Counter = new Counter();
Counter.Readings = new ObservableCollection<CounterReading>();
}
}
And what doesn't work is that my WaterMeter does not have any CounterReadings when I load it from database. This is because I set my Counter in my constructor, to avoid NullReferencesExceptions when I create a new WaterMeter in runtime.
If I remove my WaterMeterconstructor, EF loads my readings just fine. But that means I have loads of NullReferences when using my application without reloading my data each and every time.
What's the best way to solve this?
Edit:
NRE:
Well firstly, there is a disjoint in the code you have in your question and the actual code you are executing (as per the image you added) which is why I couldn't see a problem.
However, from the image you posted it's clear that the problem is the fact that you have Readings as a private field - EF CodeFirst requires navigation properties to be marked as public virtual in order for them to be initialised.
To avoid null reference exception with collection, you should introduce local readonly collection field and initialize it empty collection.
public class Counter
{
private readonly ObservableCollection<CounterReading> readings = new ObservableCollection<CounterReading>();
public virtual ObservableCollection<CounterReading> Readings
{
get { return readings; }
set { readings = value; }
}
[Key, ForeignKey("Meter")]
public int CounterId { get; set; }
public virtual Meter Meter { get; set; }
[NotMapped]
public CounterReading CurrentReading
{
get
{
return Readings.MaxBy(m => m.Reading);
}
}
}
I've got one addition to Evgeny's answer. Instead of always creating a new instance of the collection , you can make it conditionally so it only creates a new instance when this property is actually called:
private readonly ObservableCollection<CounterReading> readings;
public virtual ObservableCollection<CounterReading> Readings
{
get
{
if(_readings == null)
{
_readings = new ObservableCollection<CounterReading>();
}
return readings;
}
set { readings = value; }
}
If you don't need to add logic to the getter and setter, why don't you consider using this:
public virtual ObservableCollection<CounterReading> Readings {get;set;}

C# Accessing a methods value dynamically using a string

I am currently setting some strings via this method:
string marketlabel = allmarketdata.#return.markets.COLXPM.label.ToString();
I would like to set the market label dynamically by having a string for the actual market choice.
string currentMarketSelected= this.marketTextBox.Text; // Specific market: COLXPM
string marketlabel=allmarketdata.#return.markets.currentMarketSelected.label.ToString();
I have been searching for a few hours and probably am not explaining correctly. I tried some stuff with reflections with no success. Basically what I want to do is have a textbox or list which contains all the market names and based on which one is selected start setting the data.
Above is the best type of example of what I want to do even though it is not syntactically possible to use a variable in place.
public class Markets
{
public COLXPM COLXPM { get; set; }
//Lots of markets below here
}
public class COLXPM
{
public string marketid { get; set; }
public string label { get; set; }
public string lasttradeprice { get; set; }
public string volume { get; set; }
public string lasttradetime { get; set; }
public string primaryname { get; set; }
public string primarycode { get; set; }
public string secondaryname { get; set; }
public string secondarycode { get; set; }
public List<Recenttrade> recenttrades { get; set; }
public List<Sellorder> sellorders { get; set; }
public List<Buyorder> buyorders { get; set; }
}
public class Return
{
public Markets markets { get; set; }
}
public class RootObject
{
public int success { get; set; }
public Return #return { get; set; }
}
The proposed solution below that worked
string currentMarketSelected = "DOGEBTC"; // Just selecting one of the markets to test it works
var property = allmarketdata.#return.markets.GetType().GetProperty(currentMarketSelected);
dynamic market = property.GetMethod.Invoke(allmarketdata.#return.markets, null);
string marketlabel = market.label.ToString(); //Gets all my selected market data
Here is a solution using reflection.
string currentMarketSelected= this.marketTextBox.Text; // Specific market: COLXPM
var property = allmarketdata.#return.markets.GetType().GetProperty(currentMarketSelected);
dynamic market = property.GetGetMethod().Invoke(allmarketdata.#return.markets, null);
string marketlabel=market.label.ToString();
You need something like this:
public class Markets
{
public COLXPM this[string key]
{
get
{
COLXPM colxpm;
switch (key)
{
// TODO : use "key" to select instance of COLXPM;
case "example1":
colxpm = ...;
break;
default:
throw new NotSupportedException();
}
return colxpm;
}
}
}
Then you can do something like:
string marketlabel=allmarketdata.#return.markets[currentMarketSelected]label.ToString();
This is an indexed property.

how to recursively call a generic method analyzing properties

I'm creating a method that will analyze an instance of a class that I have created, checking each of the properties on that class for string types and then checking if those string properties are null or empty.
Code:
public class RootClass
{
public string RootString1 { get; set; }
public string RootString2 { get; set; }
public int RootInt1 { get; set; }
public Level1ChildClass1 RootLevel1ChildClass11 { get; set; }
public Level1ChildClass1 RootLevel1ChildClass12 { get; set; }
public Level1ChildClass2 RootLevel1ChildClass21 { get; set; }
}
public class Level1ChildClass1
{
public string Level1String1 { get; set; }
public string Level1String2 { get; set; }
public int Level1Int1 { get; set; }
}
public class Level1ChildClass2
{
public string Level1String1 { get; set; }
public string Level1String2 { get; set; }
public int Level1Int1 { get; set; }
public Level2ChildClass1 Level1Level2ChildClass11 { get; set; }
public Level2ChildClass1 Level1Level2ChildClass12 { get; set; }
public Level2ChildClass2 Level1Level2ChildClass22 { get; set; }
}
public class Level2ChildClass1
{
public string Level2String1 { get; set; }
public string Level2String2 { get; set; }
public int Level2Int1 { get; set; }
}
public class Level2ChildClass2
{
public string Level2String1 { get; set; }
public string Level2String2 { get; set; }
public int Level2Int1 { get; set; }
}
Not all the properties on the class are strings, some of them are instances of other classes, which have their own properties, which also need to be analyzed the same way. Basically, the method will return true if any of the properties are strings with a value on the RootClass or anywhere on sub-levels of the class (for example, if RootLevel1ChildClass11 has a string property with a value).
Here's what I have so far:
public static bool ObjectHasStringData<T>(this T obj)
{
var properties = typeof(T).GetProperties(BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var property in properties)
{
Type propertyType = property.PropertyType;
if (propertyType == typeof(string))
{
try
{
if (!String.IsNullOrEmpty(property.GetValue(obj, null) as string))
return true;
}
catch (NullReferenceException) { } // we want to ignore NullReferenceExceptions
}
else if (!propertyType.IsValueType)
{
try
{
if (ObjectHasStringData(property.GetValue(obj, null)))
return true;
}
catch (NullReferenceException) { } // we want to ignore NullReferenceExceptions
}
}
return false;
}
this works great on the first layer (so any string within the RootClass), but once I start using it recursively on the if (ObjectHasStringData(property.GetValue(obj, null))) line, the return value of property.GetValue() is object, so when calling the method recursively, T is object.
I can get the Type of the current object, but how do I convert the object returned from property.GetValue() to the actual type of the property?
I'd suggest not making that a generic method, just have it accept any object, and use GetType to get the type (unless it's null). The generics doesn't really seem to add anything of value here.
So, remove the type parameter, use obj.GetType(), and don't recurse if the object is null!
Also, (propertyType)obj) won't work, and if it would it would have no use. Casting is only for type safety and determining (at compile time) how to interact with an object. To System.Reflection it doesn't make any difference.
public static bool ObjectHasStringData( this object obj )
{
if( obj == null )
return false;
var properties = obj.GetType().GetProperties(BindingFlags.NonPublic | BindingFlags.Instance);
foreach (var property in properties)
...
}

Categories

Resources