Looping through Generic Class's collection property - c#

Although many questions have been posted, none seem to help me on my issue.
I've started a new Generics / Reflection adventure and I'm just trying to get my head around the syntax and concepts.
I have a Generic class with X amount of properties and one being a collection, all is working fine but I'm having problems extracting the values from the collection props by property name.
foreach (var property in typeof(T).GetProperties())
{
if (property.Name == "Props")
{
foreach (var item in (IEnumerable)property.GetValue(type, null))
{
var propertyName = "";
var newValue = "";
var oldValue = "";
sbDescription.AppendLine(strDescriptionVals
.Replace("{0}", (item.ToString() == "PropertyName") ? item.ToString() : "" + ", "));
sbAllNotes.AppendLine(strAllNotes
.Replace("{0}", (item.ToString() == "PropertyName") ? item.ToString() : "")
.Replace("{1}", (item.ToString() == "NewValue") ? item.ToString() : "")
.Replace("{2}", (item.ToString() == "OldValue") ? item.ToString() : ""));
}
}
}
As you can see I've pinpointed the property Props and now I want to loop through that and pull values by property name.
item.ToString() just prints the namespace of the class property and not the value
Hoping you kind folk can point me in the right direction?

You probably want to recursively "dive" into the items properties.
Note, only conceptual code fragments, no guarantee that it works as I write it here:
void Print(object value, string propertyName)
{
if (property.PropertyType == typeof(string) || property.PropertyType.IsPrimitive)
{
sbAllNotes.AppnedLine(.. propertyName .. value ..);
}
else if ((typeof(IEnumerable).IsAssignableFrom(property.PropertyType)
{
PrintCollectioType((IEnumerable)value);
}
else
{
PrintComplexType(value);
}
}
void PrintCollectioType(IEnumerable collection)
{
foreach (var item in collection)
{
Print(item, "Collection Item");
}
}
void PrintComplexType(IEnumerable collection)
{
foreach(var property in owner.GetType().GetProperties())
{
var propertyName = property.Name;
var propertyValue = property.GetValue(owner);
Print(item, propertyName);
}
}
To print the "Props", do:
var props = typeof(T).GetProperty("Props");
var propsValue = props.GetValue(rootObject);
Print(propsValue, "Root");

After playing with the code and understanding the logic a little more, it occurred to me that is was as simple as "Getting the type and Property" on the Props iteration:
//Get the collection property
foreach (var property in typeof(T).GetProperties())
{
if (property.Name == "Props")
{
foreach (var item in (IEnumerable)property.GetValue(type, null))
{
//Because props is a collection, Getting the type and property on each pass was essential
var propertyName = item.GetType().GetProperty("PropertyName").GetValue(item, null);
var newValue = item.GetType().GetProperty("NewValue").GetValue(item, null);
var oldValue = item.GetType().GetProperty("OldValue").GetValue(item, null);
sbDescription.AppendLine(strDescriptionVals
.Replace("{0}", (propertyName != null) ? propertyName.ToString() : "" + ", "));
sbAllNotes.AppendLine(strAllNotes
.Replace("{0}", (propertyName != null) ? propertyName.ToString() : "")
.Replace("{1}", (newValue != null) ? newValue.ToString() : "")
.Replace("{2}", (oldValue != null) ? oldValue.ToString() : ""));
}
}
}

Related

copy properties c# but check if a property is already set

I working hard on a JSON-Deserializer on my own. Just for training. I am nearly finished, but I have a copying issue.
I already have this:
public void CopyValues<T>(T target, T source)
{
Type t = typeof(T);
var properties = t.GetProperties().Where(prop => prop.CanRead && prop.CanWrite);
foreach (var prop in properties)
{
var value = prop.GetValue(source, null);
if (value != null)
prop.SetValue(target, value, null);
}
}
The Main Problem is here. I have a Property containing 2 Properties. Like content.link and content.value.
If I use the Copy Function it copys right. No discussion. But if I put the copy function into a loop, and the data is filled up, the source having also
"content", but without link and value.
If I copy again, the already correctly filled properties are getting overridden, and the result is that I have only null at conent.link and content.value.
Is there a way, to check if link and value is set to null ?
In order to copy the nested properties, you will need use a recursive function:
public static void DeepCopy<T>(T target, T source)
{
DeepCloneImpl(typeof(T), source, target);
}
public static T DeepClone<T>(T template)
where T : new()
{
return (T)DeepCloneImpl(typeof(T), template);
}
private static object DeepCloneImpl(Type type, object template, object stump = null)
{
if (template == null)
{
return null;
}
var clone = stump ?? Activator.CreateInstance(type);
var clonableProperties = type.GetProperties()
.Where(x => x.GetMethod != null && x.SetMethod != null);
foreach (var property in clonableProperties)
{
var propertyType = property.PropertyType;
if (propertyType.GetTypeInfo().IsValueType || propertyType == typeof(string))
{
var value = property.GetValue(template);
property.SetValue(clone, value);
}
else if (propertyType.GetTypeInfo().IsClass && propertyType.GetConstructor(Type.EmptyTypes) != null)
{
var value = DeepCloneImpl(propertyType, property.GetValue(template));
property.SetValue(clone, value);
}
else if (propertyType.IsArray)
{
var source = property.GetValue(template) as Array;
if (source == null)
{
continue;
}
var elementType = propertyType.GetElementType();
if (elementType.GetTypeInfo().IsValueType || elementType == typeof(string))
{
var copies = Array.CreateInstance(elementType, source.Length);
Array.Copy(source, copies, source.Length);
property.SetValue(clone, copies);
}
else if (elementType.GetTypeInfo().IsClass)
{
var copies = Array.CreateInstance(elementType, source.Length);
for (int i = 0; i < source.Length; i++)
{
var copy = DeepCloneImpl(elementType, source.GetValue(i));
copies.SetValue(copy, i);
}
property.SetValue(clone, copies);
}
}
}
return clone;
}
This should cover most of the use case, however you will have to handle self/circular references.
From what I can gather, the problem is values are being overridden. And from looking at your method, it appears to be doing what you'd expect, it's a soft clone of property values. Depending on what a correctly filled property is in your situation, you'll have to make a comparison that prevents the property from being set again. Perhaps value != null should actually be value == null so that you're only setting the value when there wasn't already one. Because of the generic typing of this method you don't have to worry about property mismatches if that's why you're using != null. Both arguments, source and target, will be of the same type.

Setting all null object parameters to string.empty

I have an object that is contains strings and further objects that contain strings, what i need to do is ensure that the object and any sub objects have an empty string and not a null value, so far this works fine:
foreach (PropertyInfo prop in contact.GetType().GetProperties())
{
if(prop.GetValue(contact, null) == null)
{
prop.SetValue(contact, string.empty);
}
}
the problem is this only works for the objects strings and not the sub-objects strings. Is there a way to also loop over all sub-objects and set their strings to string.Empty if found to be null?
Here's an example of the 'contact' object:
new contact
{
a = "",
b = "",
c = ""
new contact_sub1
{
1 = "",
2 = "",
3 = ""
},
d = ""
}
Basically I also need to check in contact_sub1 for nulls and replace the value with an empty string.
You can modify your current code to get all sub objects and then perform the same check for null string properties.
public void SetNullPropertiesToEmptyString(object root) {
var queue = new Queue<object>();
queue.Enqueue(root);
while (queue.Count > 0) {
var current = queue.Dequeue();
foreach (var property in current.GetType().GetProperties()) {
var propertyType = property.PropertyType;
var value = property.GetValue(current, null);
if (propertyType == typeof(string) && value == null) {
property.SetValue(current, string.Empty);
} else if (propertyType.IsClass && value != null && value != current && !queue.Contains(value)) {
queue.Enqueue(value);
}
}
}
}

EntityFramework Change Tracking not tracking changes

I am using Entity Framework 7 and I need to be able to track changes. I am ultimately doing something like this during the on SaveChanges I am overriding it then at the end using base.SaveChanges():
foreach (var ent in this.ChangeTracker.Entries().Where(p => p.State == EntityState.Deleted || p.State == EntityState.Modified).ToList())
{
// For each changed record, get the audit record entries and add them
foreach (TableChange x in GetTableChangeRecordsForChange(ent, _ChangeUser))
{
this.TableChanges.Add(x);
val = true;
}
}
This ultimately calls out to get table change records:
private IEnumerable<TableChange> GetTableChangeRecordsForChange(EntityEntry dbEntry, string userId)
{
List<TableChange> result = new List<TableChange>();
foreach (var ent in this.ChangeTracker.Entries().Where(p => p.State == EntityState.Deleted || p.State == EntityState.Modified).ToList())
{
// For each changed record, get the audit record entries and add them
foreach (TableChange x in GetTableChangeRecordsForChange(ent, _ChangeUser))
{
this.TableChanges.Add(x);
val = true;
}
}
if (dbEntry.State == EntityState.Modified)
{
foreach (var property in dbEntry.Entity.GetType().GetTypeInfo().DeclaredProperties)
{
// For updates, we only want to capture the columns that actually changed
if (!object.Equals(dbEntry.Property(property.Name).OriginalValue, dbEntry.Property(property.Name).CurrentValue))
{
result.Add(new TableChange()
{
Action = "U",
ColumnName = property.Name,
CreatedBy = userId,
CreatedOn = DateTime.Now,
OldValue = dbEntry.Property(property.Name).OriginalValue.ToString(),
NewValue = dbEntry.Property(property.Name).CurrentValue.ToString(),
TableName = dbEntry.Entity.GetType().GetTypeInfo().Name,
TableID = dbEntry.Property("ID").CurrentValue.ToString()
});
}
}
}
}
Now the issue that I am facing is both OriginalValue & CurrentValue and the new value that was entered. It is not tracking what the original value was.
I do have:
this.ChangeTracker.AutoDetectChangesEnabled = true;
Yet I am still not getting it to provide me the original and current value correctly. Any help is greatly appreciated.
So I figured out my own issue... the issue I was facing was I was attaching the record that needed updating. I ultimately updated the record by creating my Update Function:
public T ADMSUpdate<T>(T entity, T originalEntity)
{
object propertyValue = null;
PropertyInfo[] properties = originalEntity.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
propertyValue = null;
if (null != property.GetSetMethod())
{
PropertyInfo entityProperty = entity.GetType().GetProperty(property.Name);
propertyValue = entity.GetType().GetProperty(property.Name).GetValue(entity, null);
if (null != propertyValue)
{
property.SetValue(originalEntity, propertyValue, null);
}
}
}
return entity;
}

Finding an item in a list in c#

The code below contains a foreach loop that loops through a list of string collection which contains XML. While enumerating through the collection, it reads the question and answer elements and adds them to the list collection. I need to ensure that no repeated question is added to the list collection.
The code below questionnaire.QuestionAnswers.Add(fataQuestionsAnswers) adds the elements to the list collection. The problem that I am facing is that the repeated questions are getting added to the list. I tried to put the following condition that is:
if (questionnaire.QuestionAnswers.Find(a => a.Question != fataQuestionsAnswers.Question) == null)
but that doesn't seem to work.
var fataQuestionnaireData = DbAccess.GetFatcaQuestionnaire(contactId);
if (fataQuestionnaireData != null)
{
var questionnaire = new FatcaQuestionnaire();
foreach (var fatcaQuestionnaire in fataQuestionnaireData)
{
//var QData = GetObjectFromStream<FatcaQuestionnaire>fataQuestionnaireData);
//FatcaQuestionnaire.Deserialize(fataQuestionnaireData);
XDocument doc = XDocument.Parse(fatcaQuestionnaire.ToString(CultureInfo.InvariantCulture));
// w.WriteLine("The value of doc" + doc);
doc.Descendants("QuestionAnswer").ToList().ForEach(questionAnswer =>
{
var fataQuestionsAnswers = new QuestionAnswers();
{
//var questionAnswer = qa.Element("QuestionAnswer");
var questionElement = questionAnswer.Element("Question");
if (questionElement != null )
fataQuestionsAnswers.Question = questionElement.Value;
//if (questionElement != null)
// w.WriteLine("The value of questionElement" + questionElement.Value);
var answerElement = questionAnswer.Element("Answer");
if (answerElement != null)
fataQuestionsAnswers.Answer = answerElement.Value;
//if (answerElement != null)
// w.WriteLine("The value of answerElement" + answerElement.Value);
var sqa = questionAnswer.Element("SubQuestionAnswer");
if (sqa != null)
{
var subQuestionElement = sqa.Element("Question");
if (subQuestionElement != null)
fataQuestionsAnswers.SubQuestionAnswer.Question = subQuestionElement.Value;
//if (subQuestionElement != null)
// w.WriteLine("The value of answerElement" + subQuestionElement.Value);
var subAnswerElement = sqa.Element("Answer");
if (subAnswerElement != null)
fataQuestionsAnswers.SubQuestionAnswer.Answer = subAnswerElement.Value;
//if (subQuestionElement != null)
// w.WriteLine("The value of answerElement" + subQuestionElement.Value);
}
if (questionnaire.QuestionAnswers.Find(a => a.Question != fataQuestionsAnswers.Question) == null)
questionnaire.QuestionAnswers.Add(fataQuestionsAnswers);
//fatcaQuestionsList.Add(fataQuestionsAnswers);
}
fatca.Questionnaire.Add(fataQuestionsAnswers);
});
}
}
You have your condition wrong, you are looking for questionanswers where the question does not match, you should be looking where they do match and checking that the result is null. (switch the != with ==)
It should be
if (questionnaire.QuestionAnswers.Find(a => a.Question == fataQuestionsAnswers.Question) == null)
However I would change it to Any() as it is a bit nearer and easier to read, it returns a true if one of the items in your list matches the condition that you specify.
if(!questionnaire.QuestionAnswers.Any(a => a.Question == fataQuestionAnswers.Question)) {
questionnaire.QuestionAnswers.Add(fataQuestionsAnswers);
}
Use Any instead
if(!questionnaire.QuestionAnswers.Any(a => a.Question == fataQuestionsAnswers.Question))
{
questionnaire.QuestionAnswers.Add(fataQuestionsAnswers);
}
Is it possible there's trailing spaces / casing differences?
if(!questionnaire.QuestionAnswers.Any(a => a.Question.Trim().Equals(fataQuestionsAnswers.Question.Trim(), StringComparison.OrdinalIgnoreCase)))
{
questionnaire.QuestionAnswers.Add(fataQuestionsAnswers);
}

using reflection to compare object in list

I have a 'Invoice' WinForm C# where it contains the usual textboxes and an unbound datagridview.
I used a 'Invoice' class object to store the form fields' values. The datagridview rows are stored to a List < SubInvoice> properties in the 'Invoice'.
public static bool CompareObjects(object original, object altered)
{
Type o = original.GetType();
Type a = altered.GetType();
foreach (PropertyInfo p in o.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
Console.WriteLine("Original: {0} = {1}", p.Name, p.GetValue(original, null));
Console.WriteLine("Altered: {0} = {1}", p.Name, p.GetValue(altered, null));
if (p.GetValue(original, null).ToString() != p.GetValue(altered, null).ToString())
{
//Console.WriteLine("Not Equal");
return false;
}
}
return true;
}
I am using a global class with a method that uses Reflection to loop through the original 'invoice' object and compared it with the altered 'invoice'. If there is any difference, the user will be notified to save the invoice.
The above method works fine with all the properties of 'Invoice' class but I do not know how to check for the List in which it stores the value of each datagridrow into a 'SubInvoice' class object that stored it as an object in the list.
Can someone please give me some help here? I have also checked for similar thread in stackoverflow and other forums but in vain.
Update: I need to create a global generic method that will check for all type of classes. It can be 'Invoice', 'Customer'. The purpose is to track any changes made to the form at any particular instance and prompt the user to save.
thank you all for your help.
I have stumbled into the following article while still eager to create a global generic method through reflection.
http://www.c-sharpcorner.com/UploadFile/1a81c5/custom-extension-method-to-compare-list-in-C-Sharp/
Here's the code if anyone is interested.
public static bool CompareObjects(object original, object altered)
{
bool result = true;
//Get the class
Type o = original.GetType();
Type a = altered.GetType();
//Cycle through the properties.
foreach (PropertyInfo p in o.GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
Console.WriteLine("Original: {0} = {1}", p.Name, p.GetValue(original, null));
Console.WriteLine("Altered: {0} = {1}", p.Name, p.GetValue(altered, null));
if (!p.PropertyType.IsGenericType)
{
if (p.GetValue(original, null) != null && p.GetValue(altered, null) != null)
{
if (!p.GetValue(original, null).ToString().Equals(p.GetValue(altered, null).ToString()))
{
result = false;
break;
}
}
else
{
//If one is null, the other is not
if ((p.GetValue(original, null) == null && p.GetValue(altered, null) != null) || (p.GetValue(original, null) != null && p.GetValue(altered, null) == null))
{
result = false;
break;
}
}
}
}
return result;
}
public static bool CompareLists<T>(this List<T> original, List<T> altered)
{
bool result = true;
if (original.Count != altered.Count)
{
return false;
}
else
{
if (original != null && altered != null)
{
foreach (T item in original)
{
T object1 = item;
T object2 = altered.ElementAt(original.IndexOf(item));
Type objectType = typeof(T);
if ((object1 == null && object2 != null) || (object1 != null && object2 == null))
{
result = false;
break;
}
if (!CompareObjects(object1, object2))
{
result = false;
break;
}
}
}
else
{
if ((original == null && altered != null) || (original != null && altered == null))
{
return false;
}
}
}
return result;
}
Usage: if (!xx.CompareObjects(searchedInvoice, alteredInvoice) | !xx.CompareLists(searchedInvoice.SubInvoice, alteredInvoice.SubInvoice))
If you only whant to inform the user to save the pending changes then you should implement INotifyPropertyChanged instead of doing some black magic.
One trick I've seen before is to mark the objects as serializable, then serialize them in to strings and compare the strings. It may not be the most efficient method, but it is quick, easy, and works for almost any kind of data class. It even works for "complex" nested classes that have references to other classes.
One down-side is that it won't easily tell you what specifically changed, just that something is different.

Categories