I have the following classes,
User:
public class User:Domain
{
[Sortable]
public string FirstName { get; set; }
[Sortable]
public string LastName { get; set; }
[NestedSortable]
public Profile Profile { get; set; }
}
Profile:
public class Profile : Domain
{
[Sortable]
public string BrandName { get; set; }
[NestedSortable]
public Client Client { get; set; }
[NestedSortable]
public ProfileContact ProfileContact { get; set; }
}
Client:
public class Client : Domain
{
[Sortable]
public string Name { get; set; }
}
Profile Contact:
public class ProfileContact : Domain
{
[Sortable]
public string Email { get; set; }
}
I'm writing a recursive function to get all the properties decorated with [Sortable] attribute. This works well when I have a single [NestedSortableProperty] but fails when I have more than one.
Here is my recursive function:
private static IEnumerable<SortTerm> GetTermsFromModel(
Type parentSortClass,
List<SortTerm> sortTerms,
string parentsName = null,
bool hasNavigation = false)
{
if (sortTerms is null)
{
sortTerms = new List<SortTerm>();
}
sortTerms.AddRange(parentSortClass.GetTypeInfo()
.DeclaredProperties
.Where(p => p.GetCustomAttributes<SortableAttribute>().Any())
.Select(p => new SortTerm
{
ParentName = parentSortClass.Name,
Name = hasNavigation ? $"{parentsName}.{p.Name}" : p.Name,
EntityName = p.GetCustomAttribute<SortableAttribute>().EntityProperty,
Default = p.GetCustomAttribute<SortableAttribute>().Default,
HasNavigation = hasNavigation
}));
var complexSortProperties = parentSortClass.GetTypeInfo()
.DeclaredProperties
.Where(p => p.GetCustomAttributes<NestedSortableAttribute>().Any());
if (complexSortProperties.Any())
{
foreach (var parentProperty in complexSortProperties)
{
var parentType = parentProperty.PropertyType;
if (string.IsNullOrWhiteSpace(parentsName))
{
parentsName = parentType.Name;
}
else
{
parentsName += $".{parentType.Name}";
}
return GetTermsFromModel(parentType, sortTerms, parentsName, true);
}
}
return sortTerms;
}
this happens because of the return statement inside the foreach loop. How to rewrite this? with this example I need to get a list of FirstName,LastName,BrandName,Name and Email. But I'm getting only the first four properties except Email.
Now that the above issue is resolved by removing the return statement as posted in my own answer below and also following #Dialecticus comments to use yield return. so I strike and updated the question.
Now I'm running into another issue. The parent class name is wrongly assigned if a class has multiple [NestedSortable] properties.
This method is called first time with User class like var declaredTerms = GetTermsFromModel(typeof(User), null);
Example,
After the first call, the parentsName parameter will be null and [Sortable] properties in User class don't have any effect.
Now for the [NestedSortable] Profile property in User class, the parentsName will be Profile and so the [Sortable] properties in Profile class will have Name as Profile.BrandName and so on.
Name property in final list to be as follows,
Expected Output:
FirstName, LastName, Profile.BrandName, Profile.Client.Name, Profile.ProfileContact.Email
But Actual Output:
FirstName, LastName, Profile.BrandName, Profile.Client.Name, Profile.Client.ProfileContact.Email
Please assist on how to fix this.
Thanks,
Abdul
after some debugging, I removed the return statement inside foreach loop and that fixed the first issue.
changed from,
return GetTermsFromModel(parentType, sortTerms, parentsName, true);
to,
GetTermsFromModel(parentType, sortTerms, parentsName, true);
Then as per #Dialecticus comments removed passing sortTerms as input parameter and removed the parameter inside the code and changed sortTerms.AddRange(...) to yield return.
changed from,
sortTerms.AddRange(parentSortClass.GetTypeInfo()
.DeclaredProperties
.Where(p => p.GetCustomAttributes<SortableAttribute>().Any())
.Select(p => new SortTerm
{
ParentName = parentSortClass.Name,
Name = hasNavigation ? $"{parentsName}.{p.Name}" : p.Name,
EntityName = p.GetCustomAttribute<SortableAttribute>().EntityProperty,
Default = p.GetCustomAttribute<SortableAttribute>().Default,
HasNavigation = hasNavigation
}));
to,
foreach (var p in properties)
{
yield return new SortTerm
{
ParentName = parentSortClass.Name,
Name = hasNavigation ? $"{parentsName}.{p.Name}" : p.Name,
EntityName = p.GetCustomAttribute<SortableAttribute>().EntityProperty,
Default = p.GetCustomAttribute<SortableAttribute>().Default,
HasNavigation = hasNavigation
};
}
also for complex properties, changed from,
GetTermsFromModel(parentType, sortTerms, parentsName, true);
to,
var complexProperties = GetTermsFromModel(parentType, parentsName, true);
foreach (var complexProperty in complexProperties)
{
yield return complexProperty;
}
And for the final issue I'm facing with the name, adding the below code after the inner foreach loop fixed it,
parentsName = parentsName.Replace($".{parentType.Name}", string.Empty);
So here is the complete updated working code:
private static IEnumerable<SortTerm> GetTermsFromModel(
Type parentSortClass,
string parentsName = null,
bool hasNavigation = false)
{
var properties = parentSortClass.GetTypeInfo()
.DeclaredProperties
.Where(p => p.GetCustomAttributes<SortableAttribute>().Any());
foreach (var p in properties)
{
yield return new SortTerm
{
ParentName = parentSortClass.Name,
Name = hasNavigation ? $"{parentsName}.{p.Name}" : p.Name,
EntityName = p.GetCustomAttribute<SortableAttribute>().EntityProperty,
Default = p.GetCustomAttribute<SortableAttribute>().Default,
HasNavigation = hasNavigation
};
}
var complexSortProperties = parentSortClass.GetTypeInfo()
.DeclaredProperties
.Where(p => p.GetCustomAttributes<NestedSortableAttribute>().Any());
if (complexSortProperties.Any())
{
foreach (var parentProperty in complexSortProperties)
{
var parentType = parentProperty.PropertyType;
//if (string.IsNullOrWhiteSpace(parentsName))
//{
// parentsName = parentType.Name;
//}
//else
//{
// parentsName += $".{parentType.Name}";
//}
var complexProperties = GetTermsFromModel(parentType, string.IsNullOrWhiteSpace(parentsName) ? parentType.Name : $"{parentsName}.{parentType.Name}", true);
foreach (var complexProperty in complexProperties)
{
yield return complexProperty;
}
//parentsName = parentsName.Replace($".{parentType.Name}", string.Empty);
}
}
}
Related
I have the following model that has these fields:
[Key]
public string Id { get; set; }
[IsSearchable]
public string Code{ get; set; }
[IsSearchable]
public string Name { get; set; }
[IsSearchable]
public string Address { get; set; }
[IsSearchable]
public string PostCode { get; set; }
[IsFilterable]
public int? Setting{ get; set; }
[IsFilterable, IsSortable]
public Location Location { get; set; }
I am writing a method to compare whether values in a database match this model. So far it looks like this:
private bool CompareEquality(Index resultBody, Type indexType)
{
var properties = indexType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
List<PropertyInfo> searchableProperties = new List<PropertyInfo>();
List<PropertyInfo> filterableProperties = new List<PropertyInfo>();
List<PropertyInfo> sortableProperties = new List<PropertyInfo>();
if (properties.Count() == resultBody.Fields.Count)
{
foreach (var property in properties)
{
var isSearchableAttribute = property.GetCustomAttribute<IsSearchableAttribute>();
var isFilterableAttribute = property.GetCustomAttribute<IsFilterableAttribute>();
var isSortableAttribute = property.GetCustomAttribute<IsSortableAttribute>();
if (isSearchableAttribute != null)
{
searchableProperties.Add(property);
}
if (isFilterableAttribute != null)
{
filterableProperties.Add(property);
}
if (isSortableAttribute != null)
{
sortableProperties.Add(property);
}
}
CheckAttributeEquality(searchableProperties, filterableProperties, sortableProperties);
}
return false;
}
The CheckAttributeEquality method:
private bool CheckAttributeEquality(List<PropertyInfo> searchableProperties, List<PropertyInfo> filterableProperties, List<PropertyInfo> sortableProperties)
{
if (searchableProperties.Count == 4 && filterableProperties.Count == 2 && sortableProperties.Count == 1)
{
CheckPropertyFields(searchableProperties, filterableProperties, sortableProperties);
return true;
}
return false;
}
As I started to write a method to check that the field names match, like so:
foreach (var property in searchableProperties)
{
if (property.Name == "Id" ||)
{
...
}
if (property.Name == "Code")
{
...
}
// etc
I realised how messy and long-winded this whole approach is. I am not hugely experienced in C# and would appreciate any advice as to how I can refactor this up a little bit? I want to check for attribute and name matches.
you could use the Typedescriptor (using System.ComponentModel) for that. Try this:
var pdc = TypeDescriptor.GetProperties( this ); //or your model object, if its not "this"
foreach (var property in searchableProperties)
{
var descriptors = pdc[ property.Name ];
// check if your searchable descriptor is there, and do error handling
}
Once it works, you could also try to solve it with LINQ.
I am trying to build a basic dependency injection container to give myself a better understand of how they work. One of the parts of my container is mapping an alias to a class name. This class can then be mapped recursively to produce a hierarchical list. It must also handle cases where it is mapped to itself.
For example say I have the following mapping:
var map = new List<Map>() {
{ new Map("Foo", "Foo") },
{ new Map("foo", "Foo") }
};
Where Map is:
public class Map {
public string Name { get; set; }
public string Class { get; set; }
public Map(string name, string #class) {
Name = name;
Class = #class;
}
}
I'd like to write a method (GetMap) which will return the root (name) and the highest level parent (class) for a given name. For example say I said:
var map = GetMap(map, "Foo");
var map2 = GetMap(map, "foo");
Both variables would return a new Map with the name "foo" and class "Foo". Here's another example to further explain. Given the following map:
var map = new List<Map>() {
{ new Map("Foo", "FooExtended") },
{ new Map("foo", "Foo") }
};
It would produce a new Map with the Name "foo" and class "FooExtended".
Here is my first attempt:
private Map GetMap(List<Map> map, string #class, string name = null) {
var item = map.SingleOrDefault(m => m.Name == #class);
if (item != null && #class != item.Class) {
return GetMap(map, item.Class, #class);
} else {
return new Map(name ?? #class, #class);
}
}
This correctly returns a Map with the correct class for both examples. However the name returned is "Foo" for both examples. This should be "foo" since that is the root of the hierarchy.
I hope I've explained that well enough. I'd appreciate it if someone could show me what I am doing wrong. Thanks
Try this
public Map GetMap(List<Map> map, string name, string finalName = null) {
var item = map.SingleOrDefault(m => m.Name == name);
if (item != null && name != item.Class) {
return GetMap(map, item.Class, finalName ?? name); // <- for you, name ?? #class
} else {
return new Map(finalName ?? name, name);
}
}
You need to pass the name (or in my case finalName) along the entire execution path
EDIT - This isn't a full solution but I am leaving it
I renamed the parameters in my test so it was easier for me to read, sorry about that.
My entire LinqPad code
void Main()
{
var map = new List<Map>() {
{ new Map("FooExtended", "FooExtended2") },
{ new Map("Foo", "FooExtended") },
{ new Map("foo", "Foo") }
};
var newMap = GetMap(map, "foo");
newMap.Dump(); // Gives Map("foo", "FooExtended2")
}
public class Map {
public string Name { get; set; }
public string Class { get; set; }
public Map(string name, string #class) {
Name = name;
Class = #class;
}
}
private static Map GetMap(List<Map> map, string name, string finalName = null) {
var item = map.SingleOrDefault(m => m.Name == name);
if (item != null && name != item.Class) {
return GetMap(map, item.Class, finalName ?? name);
} else {
return new Map(finalName ?? name, name);
}
}
EDIT AGAIN - Actual answer based on comments
Final answer, GetMap will return the name of the highest type (EG, foo) and the class of the lowest (EG Foo)
private static Map GetMap(List<Map> map, string name) {
return new Map(GetMapName(map, name), GetMapClass(map, name));
}
private static string GetMapName(List<Map> map, string name) {
var item = map.SingleOrDefault(m => m.Name != name && m.Class == name);
return item != null ? GetMapName(map, item.Name) : name;
}
private static string GetMapClass(List<Map> map, string name) {
var item = map.SingleOrDefault(m => m.Name == name && m.Class != name);
return item != null ? GetMapClass(map, item.Class) : name;
}
I used the code I posted above and I get "foo", "FooExtended2" no matter what name or class I use.
Hope that helps.
I have a class, which is created and populated from an xml string, I've simplified it for example purposes:
[XmlRoot("Person")]
public sealed class Person
{
[XmlElement("Name")]
public string Name { get; set; }
[XmlElement("Location")]
public string Location { get; set; }
[XmlElement("Emails", Type = typeof(PersonEmails)]
public PersonEmails Emails { get; set; }
}
public class PersonEmails
{
[XmlElement("Email", Type = typeof(PersonEmail))]
public PersonEmail[] Emails { get; set; }
}
public class PersonEmail
{
[XmlAttribute("Type")]
public string Type { get; set; }
[XmlText]
public string Value { get; set; }
}
To extract the information, I'm trying to load them into another class, which is simply:
public class TransferObject
{
public string Name { get; set; }
public ObjectField[] Fields { get; set; }
}
public class ObjectField
{
public string Name { get; set; }
public string Value { get; set; }
}
I'm only populating "Fields" from the other object, which would simply be (Name = "Location", Value = "London"), but for Emails, (Name = "Email"+Type, Value = jeff#here.com)
Currently I can populate all the other fields, but I'm stuck with Emails, and knowing how to dig deep enough to be able to use reflection (or not) to get the information I need. Currently I'm using:
Person person = Person.FromXmlString(xmlString);
List<ObjectField> fields = new List<ObjectField>();
foreach (PropertyInfo pinfo in person.getType().GetProperties()
{
fields.Add(new ObjectField { Name = pinfo.Name, Value = pinfo.getValue(person, null).ToString();
}
How can I expand on the above to add all my emails to the list?
You are trying to type cast a complex values type to string value so you lost the data. Instead use following code:
class Program
{
static void Main(string[] args)
{
Person person = new Person();
person.Name = "Person One";
person.Location = "India";
person.Emails = new PersonEmails();
person.Phones = new PersonPhones();
person.Emails.Emails = new PersonEmail[] { new PersonEmail() { Type = "Official", Value = "xyz#official.com" }, new PersonEmail() { Type = "Personal", Value = "xyz#personal.com" } };
person.Phones.Phones = new PersonPhone[] { new PersonPhone() { Type = "Official", Value = "789-456-1230" }, new PersonPhone() { Type = "Personal", Value = "123-456-7890" } };
List<ObjectField> fields = new List<ObjectField>();
fields = GetPropertyValues(person);
}
static List<ObjectField> GetPropertyValues(object obj)
{
List<ObjectField> propList = new List<ObjectField>();
foreach (PropertyInfo pinfo in obj.GetType().GetProperties())
{
var value = pinfo.GetValue(obj, null);
if (pinfo.PropertyType.IsArray)
{
var arr = value as object[];
for (var i = 0; i < arr.Length; i++)
{
if (arr[i].GetType().IsPrimitive)
{
propList.Add(new ObjectField() { Name = pinfo.Name + i.ToString(), Value = arr[i].ToString() });
}
else
{
var lst = GetPropertyValues(arr[i]);
if (lst != null && lst.Count > 0)
propList.AddRange(lst);
}
}
}
else
{
if (pinfo.PropertyType.IsPrimitive || value.GetType() == typeof(string))
{
propList.Add(new ObjectField() { Name = pinfo.Name, Value = value.ToString() });
}
else
{
var lst = GetPropertyValues(value);
if (lst != null && lst.Count > 0)
propList.AddRange(lst);
}
}
}
return propList;
}
}
Check this snippet out:
if(pinfo.PropertyType.IsArray)
{
// Grab the actual instance of the array.
// We'll have to use it in a few spots.
var array = pinfo.GetValue(personObject);
// Get the length of the array and build an indexArray.
int length = (int)pinfo.PropertyType.GetProperty("Length").GetValue(array);
// Get the "GetValue" method so we can extact the array values
var getValue = findGetValue(pinfo.PropertyType);
// Cycle through each index and use our "getValue" to fetch the value from the array.
for(int i=0; i<length; i++)
fields.Add(new ObjectField { Name = pinfo.Name, Value = getValue.Invoke(array, new object[]{i}).ToString();
}
// Looks for the "GetValue(int index)" MethodInfo.
private static System.Reflection.MethodInfo findGetValue(Type t)
{
return (from mi in t.GetMethods()
where mi.Name == "GetValue"
let parms = mi.GetParameters()
where parms.Length == 1
from p in parms
where p.ParameterType == typeof(int)
select mi).First();
}
You can definately do it with Reflection... You can take advantage of the fact that a Type can tell you if it's an array or not (IsArray)... and then take advantage of the fact that an Array has a method GetValue(int index) that will give you a value back.
Per your comment
Because Emails is a property within a different class, recursion should be used. However the trick is knowing when to go to the next level. Really that is up to you, but
if it were me, I would use some sort of Attribute:
static void fetchProperties(Object instance, List<ObjectField> fields)
{
foreach(var pinfo in instance.GetType().GetProperties())
{
if(pinfo.PropertyType.IsArray)
{
... // Code described above
}
else if(pinfo.PropertyType.GetCustomAttributes(typeof(SomeAttribute), false).Any())
// Go the next level
fetchProperties(pinfo.GetValue(instance), fields);
else
{
... // Do normal code
}
}
}
I am using the entity framework to create an audit trail. Rather than audit every property, I thought I would create a custom attribute
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false)]
public class DoNotAudit : Attribute
{
}
Then I would apply this to my model
[Table("AuditZone")]
public class AuditZone
{
public AuditZone()
{
AuditZoneUploadedCOESDetails = new List<UploadedCOESDetails>();
AuditZonePostcode = new List<Postcodes>();
}
[Key]
public int Id { get; set; }
public string Description { get; set; }
public bool Valid { get; set; }
public DateTime CreatedDate { get; set; }
public int? CreatedBy { get; set; }
[DoNotAudit]
public DateTime? ModifiedDate { get; set; }
public int? ModifiedBy { get; set; }
public virtual UserProfile CreatedByUser { get; set; }
public virtual UserProfile ModifiedByUser { get; set; }
public virtual ICollection<UploadedCOESDetails> AuditZoneUploadedCOESDetails { get; set; }
public virtual ICollection<Postcodes> AuditZonePostcode { get; set; }
}
Then in my code for the audit trail
// This is overridden to prevent someone from calling SaveChanges without specifying the user making the change
public override int SaveChanges()
{
throw new InvalidOperationException("User ID must be provided");
}
public int SaveChanges(int userId)
{
// Get all Added/Deleted/Modified entities (not Unmodified or Detached)
foreach (var ent in this.ChangeTracker.Entries().Where(p => p.State == System.Data.EntityState.Added || p.State == System.Data.EntityState.Deleted || p.State == System.Data.EntityState.Modified))
{
// For each changed record, get the audit record entries and add them
foreach (AuditLog x in GetAuditRecordsForChange(ent, userId))
{
this.AuditLogs.Add(x);
}
}
// Call the original SaveChanges(), which will save both the changes made and the audit records
return base.SaveChanges();
}
private List<AuditLog> GetAuditRecordsForChange(DbEntityEntry dbEntry, int userId)
{
List<AuditLog> result = new List<AuditLog>();
DateTime changeTime = DateTime.UtcNow;
// Get the Table() attribute, if one exists
//TableAttribute tableAttr = dbEntry.Entity.GetType().GetCustomAttributes(typeof(TableAttribute), false).SingleOrDefault() as TableAttribute;
TableAttribute tableAttr = dbEntry.Entity.GetType().GetCustomAttributes(typeof(TableAttribute), true).SingleOrDefault() as TableAttribute;
// Get table name (if it has a Table attribute, use that, otherwise get the pluralized name)
string tableName = tableAttr != null ? tableAttr.Name : dbEntry.Entity.GetType().Name;
// Get primary key value (If you have more than one key column, this will need to be adjusted)
var keyNames = dbEntry.Entity.GetType().GetProperties().Where(p => p.GetCustomAttributes(typeof(KeyAttribute), false).Count() > 0).ToList();
string keyName = keyNames[0].Name;
var test = dbEntry.Entity.GetType().GetProperties().Where(p => p.GetCustomAttributes(typeof(DoNotAudit), false).Count() > 0).ToList();
// //dbEntry.Entity.GetType().GetProperties().Single(p => p.GetCustomAttributes(typeof(KeyAttribute), false).Count() > 0).Name;
if (dbEntry.State == System.Data.EntityState.Added)
{
// For Inserts, just add the whole record
// If the entity implements IDescribableEntity, use the description from Describe(), otherwise use ToString()
foreach (string propertyName in dbEntry.CurrentValues.PropertyNames)
{
result.Add(new AuditLog()
{
AuditLogId = Guid.NewGuid(),
UserId = userId,
EventDateUTC = changeTime,
EventType = "A", // Added
TableName = tableName,
RecordId = dbEntry.CurrentValues.GetValue<object>(keyName).ToString(),
ColumnName = propertyName,
NewValue = dbEntry.CurrentValues.GetValue<object>(propertyName) == null ? null : dbEntry.CurrentValues.GetValue<object>(propertyName).ToString()
}
);
}
}
else if (dbEntry.State == System.Data.EntityState.Deleted)
{
// Same with deletes, do the whole record, and use either the description from Describe() or ToString()
result.Add(new AuditLog()
{
AuditLogId = Guid.NewGuid(),
UserId = userId,
EventDateUTC = changeTime,
EventType = "D", // Deleted
TableName = tableName,
RecordId = dbEntry.OriginalValues.GetValue<object>(keyName).ToString(),
ColumnName = "*ALL"//,
// NewValue = (dbEntry.OriginalValues.ToObject() is IDescribableEntity) ? (dbEntry.OriginalValues.ToObject() as IDescribableEntity).Describe() : dbEntry.OriginalValues.ToObject().ToString()
}
);
}
else if (dbEntry.State == System.Data.EntityState.Modified)
{
foreach (string propertyName in dbEntry.OriginalValues.PropertyNames)
{
var doNotAUditDefined = dbEntry.Property(propertyName).GetType().GetCustomAttributes(typeof(DoNotAudit), false);
// var test1 = dbEntry.Property(propertyName).GetType().Where(p => p.GetCustomAttributes(typeof(DoNotAudit), false).Count() > 0).ToList();
// var test = dbEntry.Entity.GetType().GetProperties().Where(p => p.GetCustomAttributes(typeof(DoNotAudit), false).Count() > 0).ToList();
// For updates, we only want to capture the columns that actually changed
if (!object.Equals(dbEntry.OriginalValues.GetValue<object>(propertyName), dbEntry.CurrentValues.GetValue<object>(propertyName)))
{
result.Add(new AuditLog()
{
AuditLogId = Guid.NewGuid(),
UserId = userId,
EventDateUTC = changeTime,
EventType = "M", // Modified
TableName = tableName,
RecordId = dbEntry.OriginalValues.GetValue<object>(keyName).ToString(),
ColumnName = propertyName,
OriginalValue = dbEntry.OriginalValues.GetValue<object>(propertyName) == null ? null : dbEntry.OriginalValues.GetValue<object>(propertyName).ToString(),
NewValue = dbEntry.CurrentValues.GetValue<object>(propertyName) == null ? null : dbEntry.CurrentValues.GetValue<object>(propertyName).ToString()
}
);
}
}
}
// Otherwise, don't do anything, we don't care about Unchanged or Detached entities
return result;
}
In the modified section I have the following line of code
var doNotAUditDefined = dbEntry.Property(propertyName).GetType().GetCustomAttributes(typeof(DoNotAudit), false);
WHen I step through the code, even for the modifiedDate property this is shown as empty. How can that be? any help is appreciated
Thanks
What you get with the following code:
dbEntry.Property(propertyName).GetType()
is the type of the modified property, like DateTime? in the case of ModifiedType. So there is no attribute defined on the DateTime? class. (As the attribute is defined in your AuditZone class)
What I would do is to save the list of properties that should not be audited before entering into the modified part of your audit code (at least before looping the list of modified properties). Then as looping through the modified properties, check if the property name is in the list of properties excluded from audit. Something like this:
var auditExcludedProps = dbEntry.Entity.GetType()
.GetProperties()
.Where(p => p.GetCustomAttributes(typeof(DoNotAudit), false).Any())
.Select(p => p.Name)
.ToList();
foreach (string propertyName in dbEntry.OriginalValues.PropertyNames)
{
var doNotAUditDefined = auditExcludedProps.Contains(propertyName);
...
}
You may want to double check that dbEntry.Entity.GetType() returns your class AuditZone and the list auditExcludedProps contains the ModifiedDate property.
Hope it helps!
I am trying update a number of properties of one object from another and I wind up repeating this same code over and over again (i am showing an example with Name and LastName but i have 15 other properties with similar code).
But its important to Note that its NOT all properties so i can't blindly just copy everything.
public class Person
{
public bool UpdateFrom(Person otherPerson)
{
if (!String.IsNullOrEmpty(otherPerson.Name))
{
if (Name!= otherPerson.Name)
{
change = true;
Name = otherPerson.Name;
}
}
if (!String.IsNullOrEmpty(otherPerson.LastName))
{
if (LastName!= otherPerson.LastName)
{
change = true;
LastName = otherPerson.LastName;
}
}
return change;
}
}
is there a more elegant way to writing this code?
You could use an Expression to define which field you want to access, the code to handle the updates would look like this:-
Person one = new Person {FirstName = "First", LastName = ""};
Person two = new Person {FirstName = "", LastName = "Last"};
Person three = new Person ();
bool changed = false;
changed = SetIfNotNull(three, one, p => p.FirstName) || changed;
changed = SetIfNotNull(three, one, p => p.LastName) || changed;
changed = SetIfNotNull(three, two, p => p.FirstName) || changed;
changed = SetIfNotNull(three, two, p => p.LastName) || changed;
Note that the order in the || expression matters since .NET will short-circuit the evaluation if it can. Or as Ben suggests in the comments below, use changed |= ... as a simpler alternative.
The SetIfNotNull method relies on this other method that does a bit of Expression magic to convert a getter ino a setter.
/// <summary>
/// Convert a lambda expression for a getter into a setter
/// </summary>
public static Action<T, U> GetSetter<T, U>(Expression<Func<T, U>> expression)
{
var memberExpression = (MemberExpression)expression.Body;
var property = (PropertyInfo)memberExpression.Member;
var setMethod = property.GetSetMethod();
var parameterT = Expression.Parameter(typeof(T), "x");
var parameterU = Expression.Parameter(typeof(U), "y");
var newExpression =
Expression.Lambda<Action<T, U>>(
Expression.Call(parameterT, setMethod, parameterU),
parameterT,
parameterU
);
return newExpression.Compile();
}
public static bool SetIfNotNull<T> (T destination, T source,
Expression<Func<T, string>> getter)
{
string value = getter.Compile()(source);
if (!string.IsNullOrEmpty(value))
{
GetSetter(getter)(destination, value);
return true;
}
else
{
return false;
}
}
Using Func and Action delegates you can do it like this:
public class Person
{
public string Name { get; set; }
public string LastName { get; set; }
public bool UpdateFrom(Person otherPerson)
{
bool change = false;
change = Check(otherPerson.Name, p => p.Name, (p, val) => p.Name = val);
change = change ||
Check(otherPerson.LastName, p => p.LastName, (p, val) => p.LastName = val);
return change;
}
public bool Check(string value, Func<Person, string> getMember, Action<Person, string> action)
{
bool result = false;
if (!string.IsNullOrEmpty(value))
{
if (getMember(this) != value)
{
result = true;
action(this, value);
}
}
return result;
}
}
You can use reflecton to do it.. here's an example implementation (need to add extra code to handle arrays etc.)
public class Person
{
public bool UpdateFromOther(Person otherPerson)
{
var properties =
this.GetType()
.GetProperties(
BindingFlags.Instance | BindingFlags.Public | BindingFlags.SetProperty
| BindingFlags.GetProperty);
var changed = properties.Any(prop =>
{
var my = prop.GetValue(this);
var theirs = prop.GetValue(otherPerson);
return my != null ? !my.Equals(theirs) : theirs != null;
});
foreach (var propertyInfo in properties)
{
propertyInfo.SetValue(this, propertyInfo.GetValue(otherPerson));
}
return changed;
}
public string Name { get; set; }
}
[Test]
public void Test()
{
var instance1 = new Person() { Name = "Monkey" };
var instance2 = new Person() { Name = "Magic" };
var instance3 = new Person() { Name = null};
Assert.IsFalse(instance1.UpdateFromOther(instance1), "No changes should be detected");
Assert.IsTrue(instance2.UpdateFromOther(instance1), "Change is detected");
Assert.AreEqual("Monkey",instance2.Name, "Property updated");
Assert.IsTrue(instance3.UpdateFromOther(instance1), "Change is detected");
Assert.AreEqual("Monkey", instance3.Name, "Property updated");
}
This is just my comment typed out, you can refer to the comments to your question about further details about this technique.
Define this class:
[AttributeUsage(AttributeTargets.Property)]
public sealed class CloningAttribute : Attribute
{
}
In your Person class:
[Cloning] // <-- applying the attribute only to desired properties
public int Test { get; set; }
public bool Clone(Person other)
{
bool changed = false;
var properties = typeof(Person).GetProperties();
foreach (var prop in properties.Where(x => x.GetCustomAttributes(typeof(CloningAttribute), true).Length != 0))
{
// get current values
var myValue = prop.GetValue(this, null);
var otherValue = prop.GetValue(other, null);
if (prop.PropertyType == typeof(string))
{
// special treatment for string:
// ignore if null !!or empty!!
if (String.IsNullOrEmpty((string)otherValue))
{
continue;
}
}
else
{
// do you want to copy if the other value is null?
if (otherValue == null)
{
continue;
}
}
// compare and only check 'changed' if they are different
if (!myValue.Equals(otherValue))
{
changed = true;
prop.SetValue(this, otherValue, null);
}
}
return changed;
}
You can create generic rewriting tool with will look on properties with particular attribute:
public class Updater
{
public static bool Update(object thisObj, object otherObj)
{
IEnumerable<PropertyInfo> props = thisObj.GetType().GetProperties().Where(
prop => Attribute.IsDefined(prop, typeof(UpdateElementAttribute)));
bool change = false;
foreach (var prop in props)
{
object value = prop.GetValue(otherObj);
if (value != null && (value is string || string.IsNullOrWhiteSpace((string)value)))
{
if (!prop.GetValue(thisObj).Equals(value))
{
change = true;
prop.SetValue(thisObj, value);
}
}
}
return change;
}
}
And then just use it:
public class Person
{
public bool UpdateFrom(Person otherPerson)
{
return Updater.Update(this, otherPerson);
}
[UpdateElement]
public string Name { get; set; }
[UpdateElement]
public string LastName { get; set; }
}